diff --git a/.travis.yml b/.travis.yml index e11b6483d..de9805450 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,3 +7,5 @@ perl: branches: only: - master + - stable + diff --git a/Build.PL b/Build.PL index 26c6f6f9b..ca98b89cb 100644 --- a/Build.PL +++ b/Build.PL @@ -17,7 +17,6 @@ my %prereqs = qw( Module::Build::WithXSpp 0.14 Moo 1.003001 Scalar::Util 0 - Storable 0 Test::Harness 0 Test::More 0 IO::Scalar 0 @@ -28,7 +27,8 @@ my %recommends = qw( XML::SAX::ExpatXS 0 ); -my $gui = defined $ARGV[0] && $ARGV[0] eq '--gui'; +my $gui = grep { $_ eq '--gui' } @ARGV; +my $xs_only = grep { $_ eq '--xs' } @ARGV; if ($gui) { %prereqs = qw( Wx 0.9918 @@ -38,6 +38,8 @@ if ($gui) { Wx::GLCanvas 0 OpenGL 0 ); +} elsif ($xs_only) { + %prereqs = %recommends = (); } my @missing_prereqs = (); @@ -124,9 +126,7 @@ EOF # with current perl binary if (-e './xs/Build') { if ($^O eq 'MSWin32') { - system 'cd', 'xs'; - system 'Build', 'distclean'; - system 'cd', '..'; + system '.\xs\Build', 'distclean'; } else { system './xs/Build', 'distclean'; } diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f7a594ea8..88f501882 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -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 {}; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index ae166b6c5..82c54b584 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -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; diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 514aec7c7..e2334c65e 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -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 { diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index c128d0eb3..43a88120d 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -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); diff --git a/lib/Slic3r/Fill/Concentric.pm b/lib/Slic3r/Fill/Concentric.pm index 0bb1f5061..79e6d4a6d 100644 --- a/lib/Slic3r/Fill/Concentric.pm +++ b/lib/Slic3r/Fill/Concentric.pm @@ -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; diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index e5c515bcc..6e665a70b 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -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)} ], )}; } diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 854d15fe4..d8ba2870e 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -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 diff --git a/lib/Slic3r/Format/AMF.pm b/lib/Slic3r/Format/AMF.pm index d881148f3..4a2c1a61e 100644 --- a/lib/Slic3r/Format/AMF.pm +++ b/lib/Slic3r/Format/AMF.pm @@ -37,10 +37,14 @@ sub write_file { printf $fh qq{ Slic3r %s\n}, $Slic3r::VERSION; for my $material_id (sort keys %{ $model->materials }) { my $material = $model->materials->{$material_id}; - printf $fh qq{ \n}, $material_id; + printf $fh qq{ \n}, $material_id; for (keys %{$material->attributes}) { printf $fh qq{ %s\n}, $_, $material->attributes->{$_}; } + my $config = $material->config; + foreach my $opt_key (@{$config->get_keys}) { + printf $fh qq{ %s\n}, $opt_key, $config->serialize($opt_key); + } printf $fh qq{ \n}; } my $instances = ''; diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index ed7984f4c..8ebc56787 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -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; } diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 1d1952309..82c576f93 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -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) { diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 08de360ec..33465eecc 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -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) diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index d2a7c184e..40d930320 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -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) = @_; diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm index e9f36ba70..0f511a6cd 100644 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -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"; } diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 20a993044..4d152a4a7 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -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) = @_; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e21213e29..b15efcc1c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -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}); diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 1f71da7f2..0f63f2479 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -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; diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index 9422ab547..8f39b2b1a 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -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; diff --git a/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm new file mode 100644 index 000000000..5bbe05198 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/OverrideSettingsPanel.pm @@ -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; diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index f66d1c116..a2a0a6c90 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -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); } diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm index c1fee5439..064b3495e 100644 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ b/lib/Slic3r/GUI/SimpleTab.pm @@ -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}}; diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 371698e0a..0665ba3ce 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -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, diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index cce81e693..ecf99591a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -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", diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 859992f36..252aac6d2 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -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; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index a75f85379..7cd431254 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -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; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 4df77cae2..7305f93cf 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -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{ \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{ \n}, $layer_id, unscale($layer->slice_z); my @current_layer_slices = (); - for my $obj_idx (0 .. $#{$self->objects}) { - my $layer = $self->objects->[$obj_idx]->layers->[$layer_id] or next; - - # sort slices so that the outermost ones come first - my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices}; - foreach my $copy (@{$self->objects->[$obj_idx]->_shifted_copies}) { - foreach my $slice (@slices) { - my $expolygon = $slice->clone; - $expolygon->translate(@$copy); - $print_polygon->($expolygon->contour, 'contour'); - $print_polygon->($_, 'hole') for @{$expolygon->holes}; - push @current_layer_slices, $expolygon; - } + # sort slices so that the outermost ones come first + my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices}; + foreach my $copy (@{$layer->object->copies}) { + foreach my $slice (@slices) { + my $expolygon = $slice->clone; + $expolygon->translate(@$copy); + $print_polygon->($expolygon->contour, 'contour'); + $print_polygon->($_, 'hole') for @{$expolygon->holes}; + push @current_layer_slices, $expolygon; } } # generate support material - if ($self->has_support_material && $layer_id > 0) { + if ($self->has_support_material && $layer->id > 0) { my (@supported_slices, @unsupported_slices) = (); foreach my $expolygon (@current_layer_slices) { my $intersection = intersection_ex( @@ -620,26 +622,50 @@ sub make_skirt { $self->skirt->clear; # method must be idempotent + # First off we need to decide how tall the skirt must be. + # The skirt_height option from config is expressed in layers, but our + # object might have different layer heights, so we need to find the print_z + # of the highest layer involved. + # Note that unless skirt_height == -1 (which means it's printed on all layers) + # the actual skirt might not reach this $skirt_height_z value since the print + # order of objects on each layer is not guaranteed and will not generally + # include the thickest object first. It is just guaranteed that a skirt is + # prepended to the first 'n' layers (with 'n' = skirt_height). + # $skirt_height_z in this case is the highest possible skirt height for safety. + my $skirt_height_z = -1; + foreach my $object (@{$self->objects}) { + my $skirt_height = ($self->config->skirt_height == -1) + ? scalar(@{$object->layers}) + : min($self->config->skirt_height, scalar(@{$object->layers})); + + my $highest_layer = $object->layers->[$skirt_height-1]; + $skirt_height_z = max($skirt_height_z, $highest_layer->print_z); + } + # collect points from all layers contained in skirt height my @points = (); - foreach my $obj_idx (0 .. $#{$self->objects}) { - my $object = $self->objects->[$obj_idx]; + foreach my $object (@{$self->objects}) { + my @object_points = (); - # get skirt layers - my $skirt_height = ($self->config->skirt_height == -1) - ? 1 + $#{$object->layers} - : 1 + min($self->config->skirt_height-1, $#{$object->layers}+1); - - my @layer_points = ( - map @$_, map @$_, map @{$object->layers->[$_]->slices}, 0..($skirt_height-1), - ); - if (@{ $object->support_layers }) { - my @support_layers = map $object->support_layers->[$_], 0..min($self->config->skirt_height-1, $#{$object->support_layers}); - push @layer_points, - (map @{$_->polyline}, map @{$_->support_fills}, grep $_->support_fills, @support_layers), - (map @{$_->polyline}, map @{$_->support_interface_fills}, grep $_->support_interface_fills, @support_layers); + # get object layers up to $skirt_height_z + foreach my $layer (@{$object->layers}) { + last if $layer->print_z > $skirt_height_z; + push @object_points, map @$_, map @$_, @{$layer->slices}; + } + + # get support layers up to $skirt_height_z + foreach my $layer (@{$object->support_layers}) { + last if $layer->print_z > $skirt_height_z; + push @object_points, map @{$_->polyline}, @{$layer->support_fills} if $layer->support_fills; + push @object_points, map @{$_->polyline}, @{$layer->support_interface_fills} if $layer->support_interface_fills; + } + + # repeat points for each object copy + foreach my $copy (@{$object->_shifted_copies}) { + my @copy_points = map $_->clone, @object_points; + $_->translate(@$copy) for @copy_points; + push @points, @copy_points; } - push @points, map move_points($_, @layer_points), @{$object->_shifted_copies}; } return if @points < 3; # at least three points required for a convex hull @@ -977,11 +1003,15 @@ sub write_gcode { $self->total_extruded_volume(0); foreach my $extruder_id (@{$self->extruders}) { my $extruder = $gcodegen->extruders->{$extruder_id}; - $self->total_used_filament($self->total_used_filament + $extruder->absolute_E); - $self->total_extruded_volume($self->total_extruded_volume + $extruder->extruded_volume); + # the final retraction doesn't really count as "used filament" + my $used_filament = $extruder->absolute_E + $extruder->retract_length; + my $extruded_volume = $extruder->extruded_volume($used_filament); printf $fh "; filament used = %.1fmm (%.1fcm3)\n", - $extruder->absolute_E, $extruder->extruded_volume/1000; + $used_filament, $extruded_volume/1000; + + $self->total_used_filament($self->total_used_filament + $used_filament); + $self->total_extruded_volume($self->total_extruded_volume + $extruded_volume); } # append full config diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 4b1738723..68c31969f 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -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), diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index d58a05d68..712d09ab0 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -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; diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 40b0c7c9f..1aa7dd27d 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -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')), }, diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 2e88268a7..8d5c93da3 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -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.6074333190918,36.0009002685547],[507.163818359375,51.6074333190918,36.0009002685547],[490.337799072266,51.4824333190918,24.0011005401611],[489.391204833984,51.4824333190918,36.0009002685547],[492.06201171875,51.7324333190918,36.0009002685547],[490.337799072266,51.4824333190918,36.0009002685547],[513.233764648438,51.2324333190918,24.0011005401611],[513.233764648438,51.2324333190918,36.0009002685547],[504.773803710938,51.4824333190918,36.0009002685547],[504.773803710938,51.4824333190918,24.0011005401611],[489.266998291016,51.2324333190918,24.0011005401611],[489.266998291016,51.2324333190918,36.0009002685547],[490.253601074219,55.4658317565918,25.5011005401611],[499.528594970703,81.599235534668,30.0011005401611],[498.20361328125,77.8658294677734,31.5009002685547],[515.358764648438,55.4658317565918,28.5011005401611],[515.358764648438,55.4658317565918,25.5011005401611],[495.246795654297,61.0124320983887,36.0009002685547],[490.253601074219,55.4658317565918,34.5009002685547],[490.253601074219,55.4658317565918,36.0009002685547],[494.228607177734,66.6658325195312,24.0011005401611],[499.068786621094,67.5192337036133,24.0011005401611],[498.20361328125,77.8658294677734,25.5011005401611],[498.20361328125,77.8658294677734,24.0011005401611],[506.608795166016,67.5192337036133,36.0009002685547],[509.09521484375,64.7458343505859,36.0009002685547],[507.618804931641,77.8658294677734,34.5009002685547],[507.618804931641,77.8658294677734,36.0009002685547],[510.385406494141,61.0124320983887,24.0011005401611],[515.358764648438,55.4658317565918,24.0011005401611],[489.32861328125,47.7324333190918,31.5009002685547],[492.95361328125,47.7324333190918,33.5634994506836],[489.32861328125,47.7324333190918,34.5009002685547],[489.32861328125,47.7324333190918,28.5011005401611],[489.32861328125,47.7324333190918,25.5011005401611],[492.95361328125,47.7324333190918,26.4385013580322],[492.95361328125,47.7324333190918,30.5635013580322],[492.95361328125,47.7324333190918,32.0634994506836],[492.95361328125,47.7324333190918,31.3135013580322],[492.95361328125,47.7324333190918,35.4384994506836],[489.32861328125,47.7324333190918,36.0009002685547],[492.95361328125,47.7324333190918,34.3134994506836],[492.95361328125,47.7324333190918,34.6884994506836],[492.95361328125,47.7324333190918,27.9385013580322],[492.95361328125,47.7324333190918,28.6885013580322],[492.95361328125,47.7324333190918,29.0635013580322],[489.32861328125,47.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,24.5635013580322],[492.95361328125,47.7324333190918,25.6885013580322],[492.95361328125,47.7324333190918,25.3135013580322],[492.95361328125,47.7324333190918,24.1885013580322],[492.95361328125,47.7324333190918,24.0011005401611],[513.443786621094,50.7324333190918,24.0011005401611],[492.95361328125,47.7324333190918,35.8134994506836],[492.95361328125,47.7324333190918,36.0009002685547],[513.443786621094,50.7324333190918,36.0009002685547],[506.350402832031,51.4824333190918,36.0009002685547],[506.350402832031,51.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,24.0011005401611],[492.638793945312,48.4824333190918,24.0011005401611],[492.743804931641,48.2324333190918,36.0009002685547],[492.638793945312,48.4824333190918,36.0009002685547],[490.089599609375,50.9824333190918,36.0009002685547],[490.089599609375,50.9824333190918,24.0011005401611],[510.342010498047,51.7324333190918,30.0011005401611],[499.068786621094,67.5192337036133,36.0009002685547],[494.228607177734,66.6658325195312,36.0009002685547],[499.375183105469,76.9058380126953,24.0011005401611],[506.421813964844,76.9058380126953,36.0009002685547],[506.608795166016,67.5192337036133,24.0011005401611],[505.728607177734,65.7524337768555,24.0011005401611],[509.09521484375,64.7458343505859,24.0011005401611],[506.701995849609,62.8258323669434,30.0011005401611],[505.728607177734,65.7524337768555,27.0011005401611],[501.835388183594,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,27.0011005401611],[494.228607177734,66.6658325195312,30.0011005401611],[495.553588867188,70.3992309570312,28.5011005401611],[492.903594970703,62.9324340820312,28.5011005401611],[495.553588867188,70.3992309570312,31.5009002685547],[492.903594970703,62.9324340820312,31.5009002685547],[511.488800048828,66.6658325195312,24.0011005401611],[511.488800048828,66.6658325195312,30.0011005401611],[512.778564453125,62.9324340820312,28.5011005401611],[515.358764648438,55.4658317565918,31.5009002685547],[507.618804931641,77.8658294677734,31.5009002685547],[510.198791503906,70.3992309570312,28.5011005401611],[511.488800048828,66.6658325195312,36.0009002685547],[512.778564453125,62.9324340820312,31.5009002685547],[510.198791503906,70.3992309570312,31.5009002685547],[502.788818359375,57.7058334350586,24.0011005401611],[497.195404052734,57.7058334350586,30.0011005401611],[492.903594970703,62.9324340820312,34.5009002685547],[492.903594970703,62.9324340820312,36.0009002685547],[495.553588867188,70.3992309570312,24.0011005401611],[496.725189208984,69.4392318725586,24.0011005401611],[495.553588867188,70.3992309570312,25.5011005401611],[495.246795654297,61.0124320983887,24.0011005401611],[492.903594970703,62.9324340820312,25.5011005401611],[492.903594970703,62.9324340820312,24.0011005401611],[495.553588867188,70.3992309570312,36.0009002685547],[496.725189208984,69.4392318725586,36.0009002685547],[495.553588867188,70.3992309570312,34.5009002685547],[510.198791503906,70.3992309570312,36.0009002685547],[509.002014160156,69.4392318725586,36.0009002685547],[510.198791503906,70.3992309570312,34.5009002685547],[512.778564453125,62.9324340820312,25.5011005401611],[512.778564453125,62.9324340820312,24.0011005401611],[510.198791503906,70.3992309570312,24.0011005401611],[509.002014160156,69.4392318725586,24.0011005401611],[510.198791503906,70.3992309570312,25.5011005401611],[510.385406494141,61.0124320983887,36.0009002685547],[512.778564453125,62.9324340820312,34.5009002685547],[512.778564453125,62.9324340820312,36.0009002685547],[496.840393066406,51.2324333190918,36.0009002685547],[498.981994628906,51.7324333190918,36.0009002685547],[498.981994628906,51.7324333190918,33.0009002685547],[506.555206298828,51.7324333190918,24.0011005401611],[506.555206298828,51.7324333190918,27.0011005401611],[503.82861328125,47.7324333190918,30.7509002685547],[507.45361328125,47.7324333190918,32.8134994506836],[503.82861328125,47.7324333190918,33.7509002685547],[503.82861328125,47.7324333190918,29.2511005401611],[503.82861328125,47.7324333190918,26.2511005401611],[507.45361328125,47.7324333190918,27.1885013580322],[493.921813964844,57.2792320251465,36.0009002685547],[491.425201416016,54.5058326721191,36.0009002685547],[497.195404052734,57.7058334350586,24.0011005401611],[496.418792724609,60.052433013916,24.0011005401611],[509.188812255859,60.052433013916,36.0009002685547],[511.675415039062,57.2792320251465,24.0011005401611],[514.161987304688,54.5058326721191,24.0011005401611],[507.45361328125,47.7324333190918,34.3134994506836],[503.82861328125,47.7324333190918,35.2509002685547],[507.45361328125,47.7324333190918,25.6885013580322],[503.82861328125,47.7324333190918,24.7511005401611],[500.20361328125,47.7324333190918,31.6885013580322],[500.20361328125,47.7324333190918,28.3135013580322],[500.20361328125,47.7324333190918,30.1885013580322],[507.45361328125,47.7324333190918,29.8135013580322],[507.45361328125,47.7324333190918,31.3135013580322],[507.45361328125,47.7324333190918,30.5635013580322],[503.82861328125,47.7324333190918,36.0009002685547],[507.45361328125,47.7324333190918,35.4384994506836],[507.45361328125,47.7324333190918,35.0634994506836],[507.45361328125,47.7324333190918,28.6885013580322],[507.45361328125,47.7324333190918,29.4385013580322],[503.82861328125,47.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,34.6884994506836],[500.20361328125,47.7324333190918,33.1884994506836],[500.20361328125,47.7324333190918,33.9384994506836],[500.20361328125,47.7324333190918,25.3135013580322],[500.20361328125,47.7324333190918,26.8135013580322],[500.20361328125,47.7324333190918,26.0635013580322],[500.20361328125,47.7324333190918,30.9385013580322],[500.20361328125,47.7324333190918,35.0634994506836],[500.20361328125,47.7324333190918,35.4384994506836],[500.20361328125,47.7324333190918,29.0635013580322],[500.20361328125,47.7324333190918,29.4385013580322],[500.20361328125,47.7324333190918,24.9385013580322],[500.20361328125,47.7324333190918,24.5635013580322],[507.45361328125,47.7324333190918,24.1885013580322],[507.45361328125,47.7324333190918,24.0011005401611],[513.86376953125,49.7324333190918,24.0011005401611],[507.45361328125,47.7324333190918,35.8134994506836],[507.45361328125,47.7324333190918,36.0009002685547],[513.86376953125,49.7324333190918,36.0009002685547],[500.20361328125,47.7324333190918,24.1885013580322],[500.20361328125,47.7324333190918,24.0011005401611],[502.988800048828,49.7324333190918,24.0011005401611],[500.20361328125,47.7324333190918,35.8134994506836],[500.20361328125,47.7324333190918,36.0009002685547],[502.988800048828,49.7324333190918,36.0009002685547],[504.755187988281,62.8258323669434,27.0011005401611],[499.205383300781,51.2324333190918,36.0009002685547],[498.786193847656,51.1074333190918,36.0009002685547],[502.358795166016,51.2324333190918,36.0009002685547],[499.205383300781,51.2324333190918,24.0011005401611],[502.358795166016,51.2324333190918,24.0011005401611],[498.786193847656,51.1074333190918,24.0011005401611],[502.568786621094,50.7324333190918,24.0011005401611],[505.931213378906,51.3574333190918,24.0011005401611],[509.503601074219,51.4824333190918,24.0011005401611],[502.568786621094,50.7324333190918,36.0009002685547],[505.931213378906,51.3574333190918,36.0009002685547],[509.503601074219,51.4824333190918,36.0009002685547],[499.048583984375,50.4824333190918,36.0009002685547],[492.428588867188,48.9824333190918,36.0009002685547],[499.048583984375,50.4824333190918,24.0011005401611],[492.428588867188,48.9824333190918,24.0011005401611],[506.088806152344,50.9824333190918,24.0011005401611],[506.036010742188,51.1074333190918,24.0011005401611],[506.088806152344,50.9824333190918,36.0009002685547],[506.036010742188,51.1074333190918,36.0009002685547],[498.891204833984,50.8574333190918,36.0009002685547],[498.943786621094,50.7324333190918,36.0009002685547],[498.891204833984,50.8574333190918,24.0011005401611],[498.943786621094,50.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,24.0011005401611],[499.783813476562,48.7324333190918,24.0011005401611],[499.573608398438,49.2324333190918,36.0009002685547],[499.783813476562,48.7324333190918,36.0009002685547],[506.403594970703,50.2324333190918,24.0011005401611],[506.298797607422,50.4824333190918,24.0011005401611],[506.403594970703,50.2324333190918,36.0009002685547],[506.298797607422,50.4824333190918,36.0009002685547],[501.228607177734,81.599235534668,27.0011005401611],[502.928588867188,81.599235534668,24.0011005401611],[499.2587890625,49.9824333190918,36.0009002685547],[499.363800048828,49.7324333190918,36.0009002685547],[499.2587890625,49.9824333190918,24.0011005401611],[499.363800048828,49.7324333190918,24.0011005401611],[496.695404052734,56.2124328613281,27.0011005401611],[496.195404052734,54.7190322875977,24.0011005401611],[509.851989746094,53.2258338928223,27.0011005401611],[493.464782714844,51.1074333190918,36.0009002685547],[493.464782714844,51.1074333190918,24.0011005401611],[502.768798828125,51.7324333190918,24.0011005401611],[500.215789794922,51.3574333190918,24.0011005401611],[497.628601074219,51.2324333190918,24.0011005401611],[502.768798828125,51.7324333190918,36.0009002685547],[500.215789794922,51.3574333190918,36.0009002685547],[497.628601074219,51.2324333190918,36.0009002685547],[507.033813476562,48.7324333190918,24.0011005401611],[506.823791503906,49.2324333190918,24.0011005401611],[507.033813476562,48.7324333190918,36.0009002685547],[506.823791503906,49.2324333190918,36.0009002685547],[494.4501953125,51.1074333190918,24.0011005401611],[494.4501953125,51.1074333190918,36.0009002685547],[500.807006835938,51.3574333190918,36.0009002685547],[503.591186523438,51.4824333190918,36.0009002685547],[503.591186523438,51.4824333190918,24.0011005401611],[500.807006835938,51.3574333190918,24.0011005401611],[505.728607177734,65.7524337768555,36.0009002685547],[505.728607177734,65.7524337768555,33.0009002685547],[499.221801757812,72.2124328613281,36.0009002685547],[501.835388183594,71.6058349609375,36.0009002685547],[506.515197753906,72.2124328613281,24.0011005401611],[503.781982421875,71.6058349609375,24.0011005401611],[503.781982421875,71.6058349609375,27.0011005401611],[499.888793945312,65.7524337768555,24.0011005401611],[495.695404052734,53.2258338928223,33.0009002685547],[516.648803710938,51.7324333190918,30.0011005401611],[498.20361328125,77.8658294677734,28.5011005401611],[505.585388183594,57.7058334350586,33.0009002685547],[508.871795654297,56.2124328613281,33.0009002685547],[499.992004394531,57.7058334350586,27.0011005401611],[504.628601074219,81.599235534668,33.0009002685547],[500.861999511719,62.8258323669434,33.0009002685547],[496.878601074219,74.1324310302734,27.0011005401611],[496.878601074219,74.1324310302734,33.0009002685547],[491.57861328125,59.199031829834,27.0011005401611],[490.253601074219,55.4658317565918,28.5011005401611],[491.57861328125,59.199031829834,33.0009002685547],[514.068786621094,59.199031829834,27.0011005401611],[514.068786621094,59.199031829834,33.0009002685547],[508.908813476562,74.1324310302734,27.0011005401611],[507.618804931641,77.8658294677734,28.5011005401611],[508.908813476562,74.1324310302734,33.0009002685547],[491.271789550781,50.9824333190918,36.0009002685547],[490.877807617188,50.9824333190918,36.0009002685547],[491.271789550781,50.9824333190918,24.0011005401611],[490.877807617188,50.9824333190918,24.0011005401611],[495.213806152344,50.9824333190918,36.0009002685547],[493.636993408203,50.9824333190918,36.0009002685547],[495.213806152344,50.9824333190918,24.0011005401611],[493.636993408203,50.9824333190918,24.0011005401611],[503.985412597656,51.4824333190918,36.0009002685547],[503.985412597656,51.4824333190918,24.0011005401611],[511.675415039062,57.2792320251465,36.0009002685547],[493.921813964844,57.2792320251465,24.0011005401611],[502.768798828125,51.7324333190918,30.0011005401611],[506.555206298828,51.7324333190918,30.0011005401611],[498.981994628906,51.7324333190918,30.0011005401611],[492.848815917969,50.9824333190918,24.0011005401611],[492.848815917969,50.9824333190918,36.0009002685547],[500.861999511719,68.6792297363281,36.0009002685547],[500.861999511719,68.6792297363281,24.0011005401611],[496.878601074219,74.1324310302734,24.0011005401611],[496.878601074219,74.1324310302734,36.0009002685547],[504.755187988281,68.6792297363281,24.0011005401611],[504.755187988281,68.6792297363281,36.0009002685547],[508.908813476562,74.1324310302734,36.0009002685547],[508.908813476562,74.1324310302734,24.0011005401611],[505.728607177734,65.7524337768555,30.0011005401611],[504.755187988281,68.6792297363281,30.0011005401611],[503.781982421875,71.6058349609375,30.0011005401611],[500.861999511719,68.6792297363281,30.0011005401611],[499.888793945312,65.7524337768555,30.0011005401611],[501.835388183594,71.6058349609375,30.0011005401611],[491.57861328125,59.199031829834,24.0011005401611],[491.57861328125,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,36.0009002685547],[514.068786621094,59.199031829834,24.0011005401611],[511.07861328125,47.7324333190918,34.8759002685547],[511.07861328125,47.7324333190918,31.8759002685547],[514.70361328125,47.7324333190918,33.9384994506836],[511.07861328125,47.7324333190918,25.1261005401611],[514.70361328125,47.7324333190918,26.0635013580322],[511.07861328125,47.7324333190918,28.1261005401611],[502.788818359375,57.7058334350586,30.0011005401611],[502.048583984375,79.8324356079102,36.0009002685547],[514.70361328125,47.7324333190918,30.9385013580322],[511.07861328125,47.7324333190918,30.3759002685547],[514.70361328125,47.7324333190918,29.0635013580322],[511.07861328125,47.7324333190918,29.6261005401611],[496.57861328125,47.7324333190918,31.1259002685547],[496.57861328125,47.7324333190918,32.6259002685547],[496.57861328125,47.7324333190918,34.1259002685547],[496.57861328125,47.7324333190918,28.8761005401611],[496.57861328125,47.7324333190918,27.3761005401611],[496.57861328125,47.7324333190918,25.8761005401611],[496.57861328125,47.7324333190918,29.6261005401611],[514.70361328125,47.7324333190918,35.4384994506836],[511.07861328125,47.7324333190918,35.6259002685547],[514.70361328125,47.7324333190918,24.5635013580322],[511.07861328125,47.7324333190918,24.3761005401611],[496.57861328125,47.7324333190918,34.8759002685547],[496.57861328125,47.7324333190918,25.1261005401611],[496.57861328125,47.7324333190918,35.6259002685547],[496.57861328125,47.7324333190918,24.3761005401611],[511.07861328125,47.7324333190918,36.0009002685547],[511.07861328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,30.1885013580322],[514.70361328125,47.7324333190918,35.8134994506836],[514.70361328125,47.7324333190918,29.8135013580322],[514.70361328125,47.7324333190918,24.1885013580322],[496.57861328125,47.7324333190918,36.0009002685547],[496.57861328125,47.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,24.0011005401611],[510.238800048828,49.7324333190918,36.0009002685547],[514.70361328125,47.7324333190918,24.0011005401611],[514.70361328125,47.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,36.0009002685547],[496.158813476562,48.7324333190918,24.0011005401611],[502.808807373047,62.8258323669434,30.0011005401611],[509.608795166016,51.2324333190918,24.0011005401611],[509.608795166016,51.2324333190918,36.0009002685547],[491.641204833984,50.8574333190918,24.0011005401611],[495.423797607422,50.4824333190918,36.0009002685547],[495.423797607422,50.4824333190918,24.0011005401611],[491.641204833984,50.8574333190918,36.0009002685547],[495.528594970703,50.2324333190918,24.0011005401611],[492.0087890625,49.9824333190918,24.0011005401611],[509.818786621094,50.7324333190918,24.0011005401611],[495.948608398438,49.2324333190918,36.0009002685547],[495.528594970703,50.2324333190918,36.0009002685547],[495.948608398438,49.2324333190918,24.0011005401611],[509.818786621094,50.7324333190918,36.0009002685547],[492.0087890625,49.9824333190918,36.0009002685547],[491.956207275391,50.1074333190918,24.0011005401611],[491.956207275391,50.1074333190918,36.0009002685547],[502.928588867188,81.599235534668,30.0011005401611],[491.851013183594,50.3574333190918,36.0009002685547],[491.851013183594,50.3574333190918,24.0011005401611],[496.195404052734,54.7190322875977,30.0011005401611],[509.361999511719,54.7190322875977,30.0011005401611],[488.632598876953,51.7256317138672,30.0011005401611],[488.632598876953,51.7256317138672,29.5091018676758],[488.632598876953,51.7188339233398,24.0011005401611],[488.632598876953,51.7256317138672,27.4929008483887],[488.632598876953,51.7324333190918,30.0011005401611],[488.632598876953,51.7324333190918,29.0175018310547],[488.632598876953,51.7324333190918,24.9847011566162],[488.632598876953,51.7324333190918,24.0011005401611],[488.632598876953,51.7188339233398,30.0011005401611],[488.632598876953,51.7176322937012,24.0011005401611],[488.632598876953,51.7182312011719,30.0011005401611],[488.632598876953,51.7176322937012,30.0011005401611],[488.632598876953,51.715030670166,24.0011005401611],[488.632598876953,51.7162322998047,30.0011005401611],[488.632598876953,50.761833190918,24.0011005401611],[488.632598876953,50.7578315734863,24.0011005401611],[488.632598876953,50.7598342895508,30.0011005401611],[488.632598876953,50.7522315979004,24.0011005401611],[488.632598876953,49.7838325500488,24.0011005401611],[488.632598876953,50.2680320739746,30.0011005401611],[488.632598876953,51.7046318054199,24.0011005401611],[488.632598876953,51.709831237793,30.0011005401611],[488.632598876953,50.9120330810547,24.0011005401611],[488.632598876953,50.8882331848145,24.0011005401611],[488.632598876953,50.9002304077148,30.0011005401611],[488.632598876953,47.7324333190918,24.0370998382568],[488.632598876953,48.5612335205078,30.0011005401611],[488.632598876953,47.7324333190918,24.0011005401611],[488.632598876953,47.7324333190918,24.1091003417969],[488.632598876953,48.5612335205078,30.0189018249512],[488.632598876953,47.7324333190918,25.3211002349854],[488.632598876953,48.5612335205078,30.0551013946533],[488.632598876953,47.7324333190918,25.4651012420654],[488.632598876953,48.5612335205078,30.6609001159668],[488.632598876953,47.7324333190918,25.5371017456055],[488.632598876953,48.5612335205078,30.7329006195068],[488.632598876953,47.7324333190918,25.6091003417969],[488.632598876953,48.5612335205078,30.7689018249512],[488.632598876953,47.7324333190918,25.8971004486084],[488.632598876953,48.5612335205078,30.8051013946533],[488.632598876953,47.7324333190918,28.321102142334],[488.632598876953,48.5612335205078,30.9491004943848],[488.632598876953,47.7324333190918,28.4651012420654],[488.632598876953,48.5612335205078,32.1609001159668],[488.632598876953,47.7324333190918,28.5371017456055],[488.632598876953,48.5612335205078,32.2329025268555],[488.632598876953,47.7324333190918,28.6811008453369],[488.632598876953,48.5612335205078,32.2689018249512],[488.632598876953,47.7324333190918,31.1049003601074],[488.632598876953,48.5612335205078,32.3411026000977],[488.632598876953,47.7324333190918,31.3929004669189],[488.632598876953,49.3900299072266,36.0009002685547],[488.632598876953,47.7324333190918,31.536901473999],[488.632598876953,47.7324333190918,31.6809005737305],[488.632598876953,47.7324333190918,34.1049003601074],[488.632598876953,47.7324333190918,34.3929023742676],[488.632598876953,47.7324333190918,34.464900970459],[488.632598876953,47.7324333190918,34.5369033813477],[488.632598876953,47.7324333190918,34.6809005737305],[488.632598876953,47.7324333190918,35.8929023742676],[488.632598876953,47.7324333190918,35.964900970459],[488.632598876953,47.7324333190918,36.0009002685547],[488.632598876953,50.8816299438477,24.0011005401611],[488.632598876953,50.8850326538086,30.0011005401611],[488.632598876953,49.7480316162109,24.0011005401611],[488.632598876953,49.7426300048828,24.0011005401611],[488.632598876953,49.745231628418,30.0011005401611],[488.632598876953,49.7592315673828,24.0011005401611],[488.632598876953,49.7536315917969,30.0011005401611],[488.632598876953,49.3900299072266,24.0011005401611],[488.632598876953,49.5664329528809,30.0011005401611],[488.632598876953,50.8786315917969,24.0011005401611],[488.632598876953,50.7764320373535,24.0011005401611],[488.632598876953,50.8274307250977,30.0011005401611],[488.632598876953,50.7550315856934,30.0011005401611],[488.632598876953,50.7692337036133,30.0011005401611],[488.632598876953,50.9284324645996,24.0011005401611],[488.632598876953,50.9202308654785,30.0011005401611],[488.632598876953,51.1788330078125,24.0011005401611],[488.632598876953,51.139232635498,24.0011005401611],[488.632598876953,51.1590309143066,30.0011005401611],[488.632598876953,51.2324333190918,24.0011005401611],[488.632598876953,51.2056312561035,30.0011005401611],[488.632598876953,51.4340324401855,24.0011005401611],[488.632598876953,51.3946304321289,24.0011005401611],[488.632598876953,51.4142303466797,30.0011005401611],[488.632598876953,51.4498329162598,24.0011005401611],[488.632598876953,51.5772323608398,30.0011005401611],[488.632598876953,51.4418334960938,30.0011005401611],[488.632598876953,51.3136329650879,30.0011005401611],[488.632598876953,49.7714309692383,30.0011005401611],[488.632598876953,51.0338325500488,30.0011005401611],[488.632598876953,50.8816299438477,30.0011005401611],[488.632598876953,50.8800315856934,30.0011005401611],[488.632598876953,51.7188339233398,36.0009002685547],[488.632598876953,51.7176322937012,36.0009002685547],[488.632598876953,49.3900299072266,30.0011005401611],[488.632598876953,50.7522315979004,30.0011005401611],[488.632598876953,50.7522315979004,36.0009002685547],[488.632598876953,49.7426300048828,30.0011005401611],[488.632598876953,49.7426300048828,36.0009002685547],[488.632598876953,49.7480316162109,30.0011005401611],[488.632598876953,49.7480316162109,36.0009002685547],[488.632598876953,51.715030670166,30.0011005401611],[488.632598876953,51.715030670166,36.0009002685547],[488.632598876953,50.7578315734863,30.0011005401611],[488.632598876953,50.7578315734863,36.0009002685547],[488.632598876953,50.761833190918,30.0011005401611],[488.632598876953,50.761833190918,36.0009002685547],[488.632598876953,50.8882331848145,30.0011005401611],[488.632598876953,50.8882331848145,36.0009002685547],[488.632598876953,49.7592315673828,30.0011005401611],[488.632598876953,49.7592315673828,36.0009002685547],[488.632598876953,51.1788330078125,30.0011005401611],[488.632598876953,51.1788330078125,36.0009002685547],[488.632598876953,50.9120330810547,30.0011005401611],[488.632598876953,50.9120330810547,36.0009002685547],[488.632598876953,51.4498329162598,30.0011005401611],[488.632598876953,51.4498329162598,36.0009002685547],[488.632598876953,51.7046318054199,30.0011005401611],[488.632598876953,51.7046318054199,36.0009002685547],[488.632598876953,51.2324333190918,30.0011005401611],[488.632598876953,51.2324333190918,36.0009002685547],[488.632598876953,51.3946304321289,30.0011005401611],[488.632598876953,51.3946304321289,36.0009002685547],[488.632598876953,51.4340324401855,30.0011005401611],[488.632598876953,51.4340324401855,36.0009002685547],[488.632598876953,49.7838325500488,30.0011005401611],[488.632598876953,49.7838325500488,36.0009002685547],[488.632598876953,50.7764320373535,30.0011005401611],[488.632598876953,50.7764320373535,36.0009002685547],[488.632598876953,51.139232635498,30.0011005401611],[488.632598876953,51.139232635498,36.0009002685547],[488.632598876953,50.9284324645996,30.0011005401611],[488.632598876953,50.9284324645996,36.0009002685547],[488.632598876953,50.8816299438477,36.0009002685547],[488.632598876953,50.8786315917969,30.0011005401611],[488.632598876953,50.8786315917969,36.0009002685547],[488.632598876953,51.7324333190918,35.0173034667969],[488.632598876953,51.7324333190918,36.0009002685547],[488.632598876953,51.7324333190918,30.9847011566162],[517.188415527344,51.7140884399414,24.0011005401611],[517.188415527344,51.7140884399414,36.0009002685547],[517.188415527344,50.4475173950195,24.0011005401611],[517.188415527344,51.7324333190918,35.3734130859375],[517.188415527344,51.7324333190918,36.0009002685547],[517.188415527344,51.7324333190918,34.1185760498047],[517.188415527344,51.7324333190918,31.88330078125],[517.188415527344,51.7324333190918,30.0011005401611],[517.188415527344,51.7324333190918,28.1187744140625],[517.188415527344,51.7324333190918,25.8834266662598],[517.188415527344,51.7324333190918,24.6285915374756],[517.188415527344,51.7324333190918,24.0011005401611],[517.188415527344,47.7324333190918,24.0600452423096],[517.188415527344,47.7324333190918,24.0011005401611],[517.188415527344,50.4475173950195,36.0009002685547],[517.188415527344,47.7324333190918,24.1779975891113],[517.188415527344,47.7324333190918,24.6498031616211],[517.188415527344,47.7324333190918,28.7625770568848],[517.188415527344,47.7324333190918,29.7061901092529],[517.188415527344,47.7324333190918,29.9420928955078],[517.188415527344,47.7324333190918,30.0600452423096],[517.188415527344,47.7324333190918,30.2959480285645],[517.188415527344,47.7324333190918,31.2395629882812],[517.188415527344,47.7324333190918,35.3521995544434],[517.188415527344,47.7324333190918,35.8240051269531],[517.188415527344,47.7324333190918,35.9419555664062],[517.188415527344,47.7324333190918,36.0009002685547] + ]; + $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,116],[339,116,345],[76,353,352],[379,76,352],[388,77,351],[178,351,77],[179,120,354],[378,354,120],[177,200,175],[351,350,177],[389,200,350],[177,350,200],[354,180,352],[180,175,204],[379,352,204],[180,204,352],[182,181,206],[177,182,351],[388,351,206],[182,206,351],[180,354,183],[183,199,181],[378,199,354],[183,354,199],[91,109,338],[178,338,109],[76,75,353],[179,353,75],[389,350,110],[109,110,350],[390,391,392],[393,394,395],[224,122,389],[122,175,201],[365,388,205],[205,207,181],[66,340,396],[68,396,340],[184,396,342],[185,342,396],[66,396,67],[184,67,396],[68,69,396],[185,396,69],[219,111,387],[111,160,187],[366,386,191],[191,193,114],[150,272,280],[102,272,150],[151,277,274],[103,151,274],[161,374,117],[116,117,374],[366,61,386],[106,386,61],[111,187,387],[186,387,187],[56,188,374],[190,374,188],[191,386,193],[192,193,386],[331,375,194],[196,194,375],[28,34,375],[65,375,34],[219,387,113],[112,113,387],[224,389,123],[110,123,389],[51,55,379],[76,379,55],[24,197,378],[199,378,197],[122,201,389],[200,389,201],[333,379,202],[204,202,379],[205,388,207],[206,207,388],[365,27,388],[77,388,27],[162,378,121],[120,121,378],[162,30,25],[30,29,25],[51,53,54],[303,60,59],[28,29,33],[29,397,33],[161,58,52],[53,52,58],[21,84,19],[84,344,19],[46,49,43],[49,269,43],[208,316,209],[210,209,316],[327,313,211],[212,211,313],[36,35,369],[210,369,35],[37,158,368],[212,368,158],[6,8,313],[36,313,8],[326,38,316],[37,316,38],[392,391,398],[399,398,391],[394,400,395],[401,395,400],[390,214,391],[214,213,234],[393,395,218],[218,239,216],[402,230,403],[230,215,245],[125,124,131],[404,125,403],[405,406,231],[231,248,217],[129,137,127],[407,406,129],[130,127,139],[402,130,408],[194,195,331],[159,331,195],[115,189,56],[188,56,189],[14,219,220],[113,220,219],[45,50,366],[61,366,50],[221,366,222],[191,222,366],[17,23,219],[111,219,23],[118,198,24],[197,24,198],[202,203,333],[163,333,203],[40,224,225],[123,225,224],[12,13,365],[27,365,13],[22,365,223],[205,223,365],[42,44,224],[122,224,44],[399,391,234],[214,234,391],[401,239,395],[218,395,239],[214,390,226],[226,238,227],[218,228,393],[228,229,243],[401,399,233],[233,235,213],[392,409,390],[410,390,409],[394,393,411],[412,411,393],[402,403,131],[125,131,403],[405,137,406],[129,406,137],[405,408,139],[130,139,408],[230,245,403],[404,403,245],[231,406,248],[407,248,406],[232,254,216],[402,408,232],[413,404,244],[244,246,215],[414,247,407],[247,217,249],[133,126,136],[415,133,413],[141,143,128],[416,414,141],[410,238,390],[226,390,238],[412,393,243],[228,243,393],[233,399,235],[234,235,399],[237,260,236],[238,410,237],[417,260,410],[237,410,260],[239,401,240],[233,240,401],[242,241,257],[243,242,412],[418,412,257],[242,257,412],[401,419,399],[398,399,419],[417,410,420],[409,420,410],[400,421,401],[419,401,421],[418,422,412],[411,412,422],[413,135,404],[125,404,135],[414,407,142],[129,142,407],[130,402,132],[131,132,402],[133,136,413],[135,413,136],[423,147,415],[133,415,147],[137,405,138],[139,138,405],[141,414,143],[142,143,414],[424,416,144],[141,144,416],[405,254,408],[232,408,254],[244,404,246],[245,246,404],[247,249,407],[248,407,249],[232,250,402],[230,402,250],[415,413,251],[244,251,413],[252,236,266],[251,252,415],[423,415,266],[252,266,415],[231,253,405],[254,405,253],[416,255,414],[247,414,255],[256,263,241],[255,416,256],[424,263,416],[256,416,263],[257,258,418],[425,418,258],[260,417,261],[426,261,417],[422,418,427],[427,259,91],[420,428,417],[428,1,262],[147,423,148],[429,148,423],[263,424,264],[264,295,265],[266,267,423],[267,268,297],[144,145,424],[430,424,145],[49,431,269],[166,269,431],[82,431,83],[49,83,431],[84,85,431],[166,431,85],[82,344,431],[84,431,344],[432,278,90],[10,90,278],[433,0,281],[39,281,0],[362,361,434],[435,271,359],[270,359,271],[436,361,275],[273,275,361],[360,437,359],[277,287,276],[151,278,277],[280,279,289],[150,280,281],[436,438,439],[439,285,140],[90,92,432],[440,432,92],[282,272,291],[441,282,442],[284,293,274],[443,438,284],[278,432,286],[286,299,265],[281,288,433],[288,268,301],[0,433,89],[444,89,433],[435,445,442],[445,134,283],[439,446,436],[361,436,446],[442,290,435],[271,435,290],[438,436,292],[275,292,436],[445,435,447],[359,447,435],[286,287,278],[277,278,287],[288,281,289],[280,289,281],[145,152,430],[443,430,152],[148,429,154],[441,154,429],[424,430,294],[294,307,276],[423,296,429],[296,279,305],[425,440,100],[92,100,440],[290,442,291],[282,291,442],[292,293,438],[284,438,293],[298,320,241],[432,440,298],[300,236,322],[433,300,444],[426,101,444],[89,444,101],[107,448,302],[302,79,54],[78,31,343],[107,78,448],[75,79,448],[302,448,79],[78,343,448],[75,448,343],[427,418,259],[425,259,418],[428,262,417],[426,417,262],[437,449,359],[447,359,449],[434,361,450],[446,450,361],[32,33,397],[78,33,32],[53,303,54],[302,54,303],[152,153,443],[438,443,153],[429,304,441],[282,441,304],[430,443,306],[284,306,443],[154,441,155],[442,155,441],[298,299,432],[286,432,299],[300,433,301],[288,301,433],[185,451,308],[308,74,7],[73,15,337],[185,73,451],[35,74,451],[308,451,74],[73,337,451],[35,451,337],[158,452,310],[310,72,42],[70,22,341],[158,70,452],[66,72,452],[310,452,72],[70,341,452],[66,452,341],[313,327,314],[315,314,327],[316,317,326],[318,326,317],[15,156,311],[356,311,156],[7,312,157],[358,157,312],[211,9,327],[364,327,9],[38,326,94],[363,94,326],[294,295,424],[264,424,295],[296,423,297],[267,297,423],[262,149,426],[101,426,149],[258,319,425],[440,425,319],[261,426,321],[444,321,426],[259,425,146],[100,146,425],[306,307,430],[294,430,307],[304,429,305],[296,305,429],[319,320,440],[298,440,320],[321,444,322],[300,322,444],[445,283,442],[155,442,283],[439,438,285],[153,285,438],[17,68,18],[21,18,68],[46,184,47],[221,47,184],[102,95,363],[94,363,95],[9,11,364],[103,364,11],[6,323,357],[370,357,323],[371,324,355],[208,355,324],[270,363,325],[326,325,363],[327,364,328],[273,328,364],[0,2,39],[12,39,2],[90,93,91],[40,91,93],[14,16,17],[73,17,16],[45,309,7],[308,7,309],[12,71,39],[70,39,71],[40,41,42],[310,42,41],[97,98,63],[14,63,98],[3,5,7],[45,7,5],[118,377,329],[330,329,377],[331,372,332],[86,332,372],[333,376,334],[335,334,376],[115,373,336],[169,336,373],[167,166,380],[330,380,166],[80,81,382],[335,382,81],[86,385,81],[168,81,385],[169,384,82],[87,82,384],[159,88,372],[87,372,88],[163,164,376],[167,376,164],[24,26,377],[80,377,26],[56,57,373],[168,373,57],[32,397,30],[29,30,397],[58,60,53],[303,53,60],[205,181,119],[118,119,181],[163,175,165],[122,165,175],[453,454,455],[454,456,455],[457,455,456],[458,455,457],[459,455,458],[460,455,459],[461,462,463],[464,465,466],[467,468,469],[470,471,472],[465,473,474],[475,476,477],[478,479,480],[481,482,478],[483,484,481],[485,486,483],[487,488,485],[489,490,487],[491,492,489],[493,494,491],[495,496,493],[497,498,495],[499,500,497],[501,502,499],[503,504,501],[505,504,503],[506,504,505],[507,504,506],[508,504,507],[509,504,508],[510,504,509],[511,504,510],[512,504,511],[513,504,512],[514,504,513],[476,515,516],[517,518,519],[520,517,521],[518,522,523],[522,480,479],[524,525,526],[468,470,527],[525,467,528],[529,475,530],[531,532,533],[534,531,535],[536,537,538],[473,539,540],[539,536,541],[537,534,542],[471,520,543],[532,529,544],[545,524,546],[453,461,547],[463,464,548],[523,549,504],[527,550,551],[519,552,553],[521,554,555],[466,556,557],[469,558,559],[528,560,561],[477,562,563],[543,564,565],[535,566,567],[530,568,569],[540,570,571],[474,572,573],[542,574,575],[538,576,577],[541,578,579],[472,580,581],[526,582,583],[533,584,585],[544,586,587],[516,545,588],[588,589,590],[455,460,4],[591,592,63],[462,455,4],[592,547,63],[547,548,63],[465,462,4],[548,557,63],[127,124,501],[127,501,499],[505,503,124],[124,126,507],[124,507,506],[509,508,126],[126,134,512],[126,512,511],[510,509,126],[128,127,493],[128,493,491],[497,495,127],[489,487,128],[140,128,483],[140,483,481],[487,485,128],[478,480,140],[480,522,140],[514,513,134],[504,514,134],[551,581,437],[471,470,434],[445,447,555],[445,555,553],[134,445,553],[134,553,504],[446,439,518],[446,518,517],[439,140,522],[439,522,518],[515,476,358],[563,588,356],[557,573,63],[473,465,4],[437,360,559],[437,559,551],[360,371,561],[360,561,559],[362,434,470],[362,470,468],[370,362,468],[370,468,467],[499,497,127],[506,505,124],[495,493,127],[513,512,134],[481,478,140],[447,449,565],[447,565,555],[450,446,517],[450,517,520],[356,156,569],[356,569,563],[157,358,476],[157,476,475],[357,370,467],[357,467,525],[371,355,583],[371,583,561],[460,459,4],[63,62,593],[63,593,591],[62,4,459],[62,459,458],[532,531,104],[531,534,104],[567,585,105],[575,567,105],[4,3,539],[4,539,473],[536,539,3],[97,63,573],[97,573,571],[571,579,97],[99,97,579],[99,579,577],[105,99,577],[105,577,575],[96,104,534],[96,534,537],[3,96,537],[3,537,536],[503,501,124],[508,507,126],[491,489,128],[511,510,126],[485,483,128],[434,450,520],[434,520,471],[449,437,581],[449,581,565],[156,105,585],[156,585,587],[587,569,156],[104,157,529],[104,529,532],[475,529,157],[590,583,355],[355,356,588],[355,588,590],[358,357,524],[358,524,515],[525,524,357],[458,457,62],[457,593,62],[479,478,482],[479,504,549],[479,482,504],[482,481,484],[472,551,550],[581,551,472],[482,484,504],[484,483,486],[523,553,552],[504,553,523],[540,573,572],[571,573,540],[544,585,584],[587,585,544],[542,577,576],[575,577,542],[526,590,589],[583,590,526],[535,575,574],[567,575,535],[533,567,566],[585,567,533],[538,579,578],[577,579,538],[543,581,580],[565,581,543],[477,569,568],[563,569,477],[530,587,586],[569,587,530],[541,571,570],[579,571,541],[528,583,582],[561,583,528],[591,453,592],[547,592,453],[521,565,564],[555,565,521],[474,557,556],[573,557,474],[516,563,562],[588,563,516],[519,555,554],[553,555,519],[527,559,558],[551,559,527],[469,561,560],[559,561,469],[462,461,455],[453,455,461],[461,463,547],[548,547,463],[465,464,462],[463,462,464],[464,466,548],[557,548,466],[469,560,467],[528,467,560],[472,550,470],[527,470,550],[474,556,465],[466,465,556],[477,568,475],[530,475,568],[516,562,476],[477,476,562],[519,554,517],[521,517,554],[521,564,520],[543,520,564],[523,552,518],[519,518,552],[479,549,522],[523,522,549],[526,589,524],[589,546,524],[527,558,468],[469,468,558],[528,582,525],[526,525,582],[530,586,529],[544,529,586],[533,566,531],[535,531,566],[535,574,534],[542,534,574],[538,578,536],[541,536,578],[540,572,473],[474,473,572],[541,570,539],[540,539,570],[542,576,537],[538,537,576],[543,580,471],[472,471,580],[544,584,532],[533,532,584],[524,545,515],[516,515,545],[545,546,588],[589,588,546],[453,591,454],[593,454,591],[484,486,504],[486,485,488],[486,488,504],[488,487,490],[488,490,504],[490,489,492],[490,492,504],[492,491,494],[492,494,504],[494,493,496],[494,496,504],[496,495,498],[496,498,504],[498,497,500],[498,500,504],[500,499,502],[500,502,504],[501,504,502],[454,593,456],[457,456,593],[594,595,596],[597,598,594],[599,597,594],[600,599,594],[601,600,594],[602,601,594],[603,602,594],[604,603,594],[605,604,594],[606,607,608],[609,606,608],[610,609,608],[611,610,608],[612,611,608],[613,612,608],[614,613,608],[615,614,608],[616,615,608],[617,616,608],[618,617,608],[619,618,608],[620,619,608],[596,608,607],[595,594,598],[608,596,595],[605,594,91],[91,338,602],[91,602,603],[598,597,1],[594,596,91],[608,595,1],[595,598,1],[616,617,392],[610,611,394],[419,421,613],[419,613,614],[422,427,607],[422,607,606],[427,91,596],[427,596,607],[428,420,619],[428,619,620],[1,428,620],[1,620,608],[420,409,618],[420,618,619],[411,422,606],[411,606,609],[398,419,614],[398,614,615],[421,400,612],[421,612,613],[409,392,617],[409,617,618],[394,411,609],[394,609,610],[604,605,91],[338,1,599],[338,599,600],[392,398,615],[392,615,616],[400,394,611],[400,611,612],[603,604,91],[601,602,338],[597,599,1],[600,601,338] + ]; + } 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.0150547027588,2.67489433288574],[13.0999994277954,19.0100555419922,2.67489433288574],[15.9489994049072,19.3670558929443,9.41489505767822],[15.9489994049072,19.2490558624268,9.41489505767822],[15.75,17.8080558776855,9.41489505767822],[15.6639995574951,19.5710544586182,9.41489505767822],[15.5709991455078,17.9260559082031,9.41489505767822],[15.8769998550415,18.690055847168,9.41489505767822],[15.8499994277954,18.8170547485352,9.41489505767822],[15.9459991455078,18.5520553588867,9.41489505767822],[15.914999961853,17.6890544891357,9.41489505767822],[15.3999996185303,19.4290542602539,9.41489505767822],[15.3099994659424,19.339054107666,9.41489505767822],[15.3729991912842,18.0440559387207,9.41489505767822],[15.4579992294312,19.5170555114746,9.41489505767822],[15.5469999313354,19.5820541381836,9.41489505767822],[13.2309989929199,19.7610549926758,9.41489505767822],[13.168999671936,19.7360553741455,9.41489505767822],[13.096999168396,19.0140552520752,9.41489505767822],[13.1999988555908,18.9870548248291,9.41489505767822],[15.1399993896484,19.2080554962158,9.41489505767822],[15.0159997940063,19.1600551605225,9.41489505767822],[14.9859991073608,18.2770557403564,9.41489505767822],[15.1749992370605,18.1690559387207,9.41489505767822],[15.9039993286133,19.1320552825928,9.41489505767822],[15.8949995040894,19.4460544586182,9.41489505767822],[15.8769998550415,19.0420551300049,9.41489505767822],[12.2169990539551,20.6500549316406,9.41489505767822],[11.9379997253418,20.6810550689697,9.41489505767822],[11.8629989624023,19.2130546569824,9.41489505767822],[12.096999168396,19.1950550079346,9.41489505767822],[14.1669998168945,18.6640548706055,9.41489505767822],[14.1039991378784,20.2460556030273,9.41489505767822],[13.9849996566772,18.7360553741455,9.41489505767822],[14.7349996566772,19.1590557098389,9.41489505767822],[14.5849990844727,19.2050552368164,9.41489505767822],[14.5719995498657,18.4850559234619,9.41489505767822],[14.1939992904663,19.6760559082031,9.41489505767822],[14.1849994659424,19.9330558776855,9.41489505767822],[14.1759996414185,18.6640548706055,9.41489505767822],[14.261999130249,19.4890556335449,9.41489505767822],[14.3539991378784,19.3610553741455,9.41489505767822],[14.3559989929199,18.5830554962158,9.41489505767822],[11.6039991378784,20.1250553131104,9.41489505767822],[11.5209999084473,20.0520553588867,9.41489505767822],[11.4209995269775,19.2480545043945,9.41489505767822],[11.6989994049072,20.2690544128418,9.41389465332031],[11.7609996795654,20.4310550689697,9.41489505767822],[11.8359994888306,19.2130546569824,9.41489505767822],[14.1889991760254,20.1710548400879,9.41489505767822],[13.9689998626709,20.2840557098389,9.41489505767822],[13.8739995956421,20.315055847168,9.41489505767822],[13.7799997329712,18.8080558776855,9.41489505767822],[13.9869995117188,20.2750549316406,9.41489505767822],[12.3129997253418,20.5980548858643,9.41489505767822],[12.3399991989136,20.5090560913086,9.41489505767822],[12.3489990234375,20.3830547332764,9.41489505767822],[12.3599996566772,20.2680549621582,9.41489505767822],[12.3849992752075,20.1850547790527,9.41489505767822],[12.3849992752075,20.1670551300049,9.41489505767822],[12.4249992370605,20.065055847168,9.41489505767822],[12.4729995727539,19.1350555419922,9.41489505767822],[14.4399995803833,19.2900543212891,9.41489505767822],[14.3649997711182,18.5740547180176,9.41489505767822],[13.5729999542236,20.0310554504395,9.41489505767822],[13.4889993667603,19.9140548706055,9.41489505767822],[13.5639991760254,18.8710556030273,9.41489505767822],[13.6389999389648,20.1310558319092,9.41489505767822],[13.6719989776611,20.2130546569824,9.41489505767822],[13.75,20.3020553588867,9.41489505767822],[12.7399997711182,19.7810554504395,9.41489505767822],[12.6189994812012,19.8520545959473,9.41489505767822],[12.5799999237061,19.1200542449951,9.41489505767822],[12.8349990844727,19.069055557251,9.41489505767822],[11.2669992446899,19.9350547790527,9.41489505767822],[11.1029987335205,19.9230556488037,9.41489505767822],[11.0209999084473,19.2600555419922,9.41489505767822],[11.3819999694824,19.9710559844971,9.41489505767822],[13.418999671936,19.8530559539795,9.41489505767822],[13.4329996109009,18.9160556793213,9.41489505767822],[11.8399991989136,20.6430549621582,9.41489505767822],[13.3119993209839,19.7800559997559,9.41489505767822],[15.2189998626709,19.2600555419922,9.41489505767822],[15.1839990615845,18.1600551605225,9.41489505767822],[15.3639993667603,18.0520553588867,9.41489505767822],[13.0189990997314,19.7250556945801,9.41489505767822],[12.8949995040894,19.7350559234619,9.41489505767822],[15.9039993286133,19.1500549316406,9.41489505767822],[15.7699995040894,19.5140552520752,9.41489505767822],[15.8589992523193,18.9340553283691,9.41489505767822],[14.1939992904663,19.9510555267334,9.41489505767822],[14.2119998931885,20.0630550384521,9.41489505767822],[14.8589992523193,19.149055480957,9.41489505767822],[14.8159999847412,18.3670558929443,9.41489505767822],[14.8959999084473,18.3220558166504,9.41489505767822],[12.5189990997314,19.9360542297363,9.41489505767822],[11.0209999084473,19.9290542602539,9.41489505767822],[11.0209999084473,19.2530555725098,2.67489433288574],[11.0209999084473,19.9300556182861,2.67489433288574],[15.9799995422363,18.505931854248,5.58724021911621],[15.9799995422363,18.5044555664062,9.41489505767822],[15.9799995422363,18.5041732788086,2.67489433288574],[15.9799995422363,18.1684837341309,2.67489433288574],[15.9799995422363,18.1288299560547,9.41489505767822],[15.9799995422363,17.9876575469971,2.67489433288574],[15.9799995422363,17.6247596740723,3.91620373725891],[15.9799995422363,17.6247596740723,2.67489433288574],[15.9799995422363,17.6254329681396,4.32245063781738],[15.9799995422363,17.8920269012451,9.41489505767822],[15.9799995422363,17.8795108795166,2.67489433288574],[15.9799995422363,17.629810333252,4.58585262298584],[15.9799995422363,17.6336059570312,5.27938556671143],[15.9799995422363,17.8311748504639,2.67489433288574],[15.9799995422363,17.638355255127,9.41489505767822],[15.9799995422363,17.6346111297607,5.98653984069824],[15.9799995422363,17.8728256225586,2.67489433288574],[15.9799995422363,18.2221603393555,2.67489433288574] + ]; + $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],[207,202,104],[103,104,197],[103,197,194],[193,195,88],[88,103,194],[88,194,193],[200,199,8],[199,201,8],[204,205,6],[6,8,201],[6,201,204],[10,6,205],[10,205,208],[104,10,208],[104,208,207] + ]; } else { return undef; } diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index b59b44a30..b06ba281e 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -19,9 +19,4 @@ sub center { return $self->bounding_box->center; } -sub facets_count { - my $self = shift; - return $self->stats->{number_of_facets}; -} - 1; diff --git a/t/config.t b/t/config.t new file mode 100644 index 000000000..7950dbb18 --- /dev/null +++ b/t/config.t @@ -0,0 +1,26 @@ +use Test::More tests => 2; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('layer_height', 0.123); + $config->setenv; + is $ENV{SLIC3R_LAYER_HEIGHT}, '0.123', 'setenv'; +} + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('perimeter_extrusion_width', '250%'); + ok $config->validate, 'percent extrusion width is validated'; +} + +__END__ diff --git a/t/fill.t b/t/fill.t index 8e112484a..551160165 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,16 +2,17 @@ use Test::More; use strict; use warnings; -plan tests => 34; +plan tests => 42; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } +use List::Util qw(first); use Slic3r; -use Slic3r::Geometry qw(scale X Y); -use Slic3r::Geometry::Clipper qw(diff_ex); +use Slic3r::Geometry qw(scale X Y convex_hull); +use Slic3r::Geometry::Clipper qw(union diff_ex); use Slic3r::Surface qw(:types); use Slic3r::Test; @@ -166,11 +167,41 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } 'chained path'; } -for my $pattern (qw(hilbertcurve concentric)) { +for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) { my $config = Slic3r::Config->new_from_defaults; $config->set('fill_pattern', $pattern); + $config->set('perimeters', 1); + $config->set('skirts', 0); + $config->set('fill_density', 0.2); + $config->set('layer_height', 0.05); + $config->set('perimeter_extruder', 1); + $config->set('infill_extruder', 2); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2); + ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation"; + my $tool = undef; + my @perimeter_points = my @infill_points = (); + Slic3r::GCode::Reader->new->parse($gcode, sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { + if ($tool == $config->perimeter_extruder-1) { + push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); + } elsif ($tool == $config->infill_extruder-1) { + push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y}); + } + } + }); + my $convex_hull = convex_hull(\@perimeter_points); + ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)"; +} + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('infill_only_where_needed', 1); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), "successful $pattern infill generation"; + ok my $gcode = Slic3r::Test::gcode($print), "successful G-code generation when infill_only_where_needed is set"; } { @@ -194,4 +225,41 @@ for my $pattern (qw(hilbertcurve concentric)) { "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 0); + $config->set('perimeters', 3); + $config->set('fill_density', 0); + $config->set('layer_height', 0.2); + $config->set('first_layer_height', 0.2); + $config->set('nozzle_diameter', [0.35]); + $config->set('infill_extruder', 2); + $config->set('infill_extrusion_width', 0.52); + + my $print = Slic3r::Test::init_print('A', config => $config); + my %infill = (); # Z => [ Line, Line ... ] + my $tool = undef; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) { + if ($tool == $config->infill_extruder-1) { + my $z = 1 * $self->Z; + $infill{$z} ||= []; + push @{$infill{$z}}, Slic3r::Line->new_scale( + [ $self->X, $self->Y ], + [ $info->{new_X}, $info->{new_Y} ], + ); + } + } + }); + my $grow_d = scale($config->infill_extrusion_width)/2; + my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]); + my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]); + my $diff = [ grep $_->area >= 2*$grow_d**2, @{diff_ex($layer0_infill, $layer1_infill)} ]; + is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0'; +} + __END__ diff --git a/t/gcode.t b/t/gcode.t index bd5677fc7..9105082a9 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 6; +use Test::More tests => 8; use strict; use warnings; @@ -68,4 +68,34 @@ use Slic3r::Test; is_deeply [ @z_moves[0..($layer_count-1)] ], [ @z_moves[$layer_count..$#z_moves] ], 'complete_objects generates the correct Z moves'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('retract_length', [1000000]); + $config->set('use_relative_e_distances', 1); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + + }); + ok $print->total_used_filament > 0, 'final retraction is not considered in total used filament'; +} + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('gcode_flavor', 'sailfish'); + $config->set('raft_layers', 3); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my @percent = (); + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'M73') { + push @percent, $args->{P}; + } + }); + # the extruder heater is turned off when M73 P100 is reached + ok !(defined first { $_ > 100 } @percent), 'M73 is never given more than 100%'; +} + __END__ diff --git a/t/multi.t b/t/multi.t index 855461791..fb114f196 100644 --- a/t/multi.t +++ b/t/multi.t @@ -1,4 +1,4 @@ -use Test::More tests => 1; +use Test::More tests => 2; use strict; use warnings; @@ -58,4 +58,12 @@ use Slic3r::Test; ok !(first { $convex_hull->contains_point($_) } @toolchange_points), 'all toolchanges happen outside skirt'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('support_material_extruder', 3); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + ok Slic3r::Test::gcode($print), 'no errors when using non-consecutive extruders'; +} + __END__ diff --git a/t/perimeters.t b/t/perimeters.t index 7ce92fa39..0805d1b42 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -151,6 +151,7 @@ use Slic3r::Test; $config->set('perimeter_speed', 99); $config->set('external_perimeter_speed', 99); $config->set('small_perimeter_speed', 99); + $config->set('thin_walls', 0); my $print = Slic3r::Test::init_print('ipadstand', config => $config); my %perimeters = (); # z => number of loops diff --git a/t/shells.t b/t/shells.t index 541c7cb75..8fb2b9bb8 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 12; +use Test::More tests => 10; use strict; use warnings; @@ -7,8 +7,9 @@ BEGIN { use lib "$FindBin::Bin/../lib"; } -use List::Util qw(first); +use List::Util qw(first sum); use Slic3r; +use Slic3r::Geometry qw(epsilon); use Slic3r::Test; { @@ -17,6 +18,7 @@ use Slic3r::Test; $config->set('perimeters', 0); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); + $config->set('bridge_speed', 72); $config->set('first_layer_speed', '100%'); $config->set('cooling', 0); @@ -26,19 +28,25 @@ use Slic3r::Test; my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my %layers_with_shells = (); # Z => $count + my %z = (); # Z => 1 + my %layers_with_solid_infill = (); # Z => $count + my %layers_with_bridge_infill = (); # Z => $count Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; if ($self->Z > 0) { - $layers_with_shells{$self->Z} //= 0; - $layers_with_shells{$self->Z} = 1 - if $info->{extruding} - && $info->{dist_XY} > 0 - && ($args->{F} // $self->F) == $config->solid_infill_speed*60; + $z{ $self->Z } = 1; + if ($info->{extruding} && $info->{dist_XY} > 0) { + my $F = $args->{F} // $self->F; + $layers_with_solid_infill{$self->Z} = 1 + if $F == $config->solid_infill_speed*60; + $layers_with_bridge_infill{$self->Z} = 1 + if $F == $config->bridge_speed*60; + } } }); - my @shells = @layers_with_shells{sort { $a <=> $b } keys %layers_with_shells}; + my @z = sort { $a <=> $b } keys %z; + my @shells = map $layers_with_solid_infill{$_} || $layers_with_bridge_infill{$_}, @z; fail "insufficient number of bottom solid layers" unless !defined(first { !$_ } @shells[0..$config->bottom_solid_layers-1]); fail "excessive number of bottom solid layers" @@ -47,9 +55,17 @@ use Slic3r::Test; unless !defined(first { !$_ } @shells[-$config->top_solid_layers..-1]); fail "excessive number of top solid layers" unless scalar(grep $_, @shells[($#shells/2)..$#shells]) == $config->top_solid_layers; + if ($config->top_solid_layers > 0) { + fail "unexpected solid infill speed in first solid layer over sparse infill" + if $layers_with_solid_infill{ $z[-$config->top_solid_layers] }; + die "bridge speed not used in first solid layer over sparse infill" + if !$layers_with_bridge_infill{ $z[-$config->top_solid_layers] }; + } 1; }; + $config->set('top_solid_layers', 3); + $config->set('bottom_solid_layers', 3); ok $test->(), "proper number of shells is applied"; $config->set('top_solid_layers', 0); @@ -68,6 +84,7 @@ use Slic3r::Test; $config->set('bottom_solid_layers', 0); $config->set('top_solid_layers', 3); $config->set('cooling', 0); + $config->set('bridge_speed', 99); $config->set('solid_infill_speed', 99); $config->set('top_solid_infill_speed', 99); $config->set('first_layer_speed', '100%'); @@ -147,6 +164,7 @@ use Slic3r::Test; $config->set('bottom_solid_layers', 0); $config->set('skirts', 0); $config->set('first_layer_height', '100%'); + $config->set('start_gcode', ''); # TODO: this needs to be tested with a model with sloping edges, where starting # points of each layer are not aligned - in that case we would test that no @@ -161,21 +179,95 @@ use Slic3r::Test; Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { my ($self, $cmd, $args, $info) = @_; - $started_extruding = 1 if $info->{extruding}; - push @z_steps, ($args->{Z} - $self->Z) - if $started_extruding && exists $args->{Z}; - $travel_moves_after_first_extrusion++ - if $info->{travel} && $started_extruding && !exists $args->{Z}; + if ($cmd eq 'G1') { + $started_extruding = 1 if $info->{extruding}; + push @z_steps, $info->{dist_Z} + if $started_extruding && $info->{dist_Z} > 0; + $travel_moves_after_first_extrusion++ + if $info->{travel} && $started_extruding && !exists $args->{Z}; + } }); - is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase ($description)"; - ok !(grep { $_ > $config->layer_height } @z_steps), "no gaps in Z ($description)"; + + # we allow one travel move after first extrusion: i.e. when moving to the first + # spiral point after moving to second layer (bottom layer had loop clipping, so + # we're slightly distant from the starting point of the loop) + ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)"; + ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)"; }; $test->('20mm_cube', 'solid model'); - $test->('40x10', 'hollow model'); $config->set('z_offset', -10); $test->('20mm_cube', 'solid model with negative z-offset'); + + ### Disabled because the current unreliable medial axis code doesn't + ### always produce valid loops. + ###$test->('40x10', 'hollow model with negative z-offset'); +} + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('spiral_vase', 1); + $config->set('bottom_solid_layers', 0); + $config->set('skirts', 0); + $config->set('first_layer_height', '100%'); + $config->set('layer_height', 0.4); + $config->set('start_gcode', ''); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $z_moves = 0; + my @this_layer = (); # [ dist_Z, dist_XY ], ... + + my $bottom_layer_not_flat = 0; + my $null_z_moves_not_layer_changes = 0; + my $null_z_moves_not_multiples_of_layer_height = 0; + my $sum_of_partial_z_equals_to_layer_height = 0; + my $all_layer_segments_have_same_slope = 0; + my $horizontal_extrusions = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1') { + if ($z_moves < 2) { + # skip everything up to the second Z move + # (i.e. start of second layer) + if (exists $args->{Z}) { + $z_moves++; + $bottom_layer_not_flat = 1 + if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height; + } + } elsif ($info->{dist_Z} == 0 && $args->{Z}) { + $null_z_moves_not_layer_changes = 1 + if $info->{dist_XY} != 0; + + # % doesn't work easily with floats + $null_z_moves_not_multiples_of_layer_height = 1 + if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon; + + my $total_dist_XY = sum(map $_->[1], @this_layer); + $sum_of_partial_z_equals_to_layer_height = 1 + if abs(sum(map $_->[0], @this_layer) - $config->layer_height) > epsilon; + exit if $sum_of_partial_z_equals_to_layer_height; + foreach my $segment (@this_layer) { + # check that segment's dist_Z is proportioned to its dist_XY + $all_layer_segments_have_same_slope = 1 + if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > epsilon; + } + + @this_layer = (); + } elsif ($info->{extruding} && $info->{dist_XY} > 0) { + $horizontal_extrusions = 1 + if $info->{dist_Z} == 0; + push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ]; + } + } + }); + ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase'; + ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes'; + ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height'; + ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height'; + ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope'; + ok !$horizontal_extrusions, 'no horizontal extrusions'; } __END__ diff --git a/t/support.t b/t/support.t index a1e7173d9..13234619c 100644 --- a/t/support.t +++ b/t/support.t @@ -1,4 +1,4 @@ -use Test::More tests => 14; +use Test::More tests => 15; use strict; use warnings; @@ -112,7 +112,7 @@ use Slic3r::Test; if ($layer_id <= $config->raft_layers) { # this is a raft layer or the first object layer my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]); - my @path = $line->grow(scale($config->support_material_extrusion_width/2)); + my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))}; if ($layer_id < $config->raft_layers) { # this is a raft layer push @raft, @path; @@ -129,4 +129,31 @@ use Slic3r::Test; 'first object layer is completely supported by raft'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 0); + $config->set('raft_layers', 2); + $config->set('layer_height', 0.35); + $config->set('first_layer_height', 0.3); + $config->set('nozzle_diameter', [0.5]); + $config->set('support_material_extruder', 2); + $config->set('support_material_interface_extruder', 2); + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my %raft_z = (); # z => 1 + my $tool = undef; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($info->{extruding} && $info->{dist_XY} > 0) { + if ($tool == $config->support_material_extruder-1) { + $raft_z{$self->Z} = 1; + } + } + }); + + is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated'; +} + __END__ diff --git a/t/thin.t b/t/thin.t new file mode 100644 index 000000000..09b060d8b --- /dev/null +++ b/t/thin.t @@ -0,0 +1,48 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use List::Util qw(first); +use Slic3r::Geometry qw(epsilon); +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('layer_height', 0.2); + $config->set('first_layer_height', '100%'); + $config->set('extrusion_width', 0.5); + $config->set('first_layer_extrusion_width', '200%'); # check this one too + $config->set('skirts', 0); + $config->set('thin_walls', 1); + + my $print = Slic3r::Test::init_print('gt2_teeth', config => $config); + + my %extrusion_paths = (); # Z => count of continuous extrusions + my $extruding = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1') { + if ($info->{extruding} && $info->{dist_XY}) { + if (!$extruding) { + $extrusion_paths{$self->Z} //= 0; + $extrusion_paths{$self->Z}++; + } + $extruding = 1; + } else { + $extruding = 0; + } + } + }); + + ok !(first { $_ != 3 } values %extrusion_paths), + 'no superfluous thin walls are generated for toothed profile'; +} + +__END__ diff --git a/utils/dump-stl.pl b/utils/dump-stl.pl index 1810d9a3f..240f10b24 100644 --- a/utils/dump-stl.pl +++ b/utils/dump-stl.pl @@ -18,6 +18,7 @@ $ARGV[0] or usage(1); if (-e $ARGV[0]) { my $model = Slic3r::Format::STL->read_file($ARGV[0]); + $model->objects->[0]->add_instance(offset => [0,0]); my $mesh = $model->mesh; $mesh->repair; printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; diff --git a/xs/Build.PL b/xs/Build.PL index b35862313..9478c9e95 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -36,6 +36,7 @@ my $build = Module::Build::WithXSpp->new( cstring cstdlib ostream + sstream )] ); diff --git a/xs/src/Config.cpp b/xs/src/Config.cpp index 20be9a3c2..a4e2def8e 100644 --- a/xs/src/Config.cpp +++ b/xs/src/Config.cpp @@ -111,12 +111,12 @@ ConfigBase::get(t_config_option_key opt_key) { return newRV_noinc((SV*)av); } else if (ConfigOptionString* optv = dynamic_cast(opt)) { // we don't serialize() because that would escape newlines - return newSVpvn(optv->value.c_str(), optv->value.length()); + return newSVpvn_utf8(optv->value.c_str(), optv->value.length(), true); } else if (ConfigOptionStrings* optv = dynamic_cast(opt)) { AV* av = newAV(); av_fill(av, optv->values.size()-1); for (std::vector::iterator it = optv->values.begin(); it != optv->values.end(); ++it) - av_store(av, it - optv->values.begin(), newSVpvn(it->c_str(), it->length())); + av_store(av, it - optv->values.begin(), newSVpvn_utf8(it->c_str(), it->length(), true)); return newRV_noinc((SV*)av); } else if (ConfigOptionPoint* optv = dynamic_cast(opt)) { return optv->point.to_SV_pureperl(); @@ -136,7 +136,7 @@ ConfigBase::get(t_config_option_key opt_key) { return newRV_noinc((SV*)av); } else { std::string serialized = opt->serialize(); - return newSVpvn(serialized.c_str(), serialized.length()); + return newSVpvn_utf8(serialized.c_str(), serialized.length(), true); } } @@ -152,7 +152,7 @@ ConfigBase::get_at(t_config_option_key opt_key, size_t i) { } else if (ConfigOptionStrings* optv = dynamic_cast(opt)) { // we don't serialize() because that would escape newlines std::string val = optv->get_at(i); - return newSVpvn(val.c_str(), val.length()); + return newSVpvn_utf8(val.c_str(), val.length(), true); } else if (ConfigOptionPoints* optv = dynamic_cast(opt)) { return optv->get_at(i).to_SV_pureperl(); } else if (ConfigOptionBools* optv = dynamic_cast(opt)) { @@ -289,6 +289,11 @@ DynamicConfig::keys(t_config_option_keys *keys) { keys->push_back(it->first); } +void +DynamicConfig::erase(const t_config_option_key opt_key) { + this->options.erase(opt_key); +} + void StaticConfig::keys(t_config_option_keys *keys) { for (t_optiondef_map::const_iterator it = this->def->begin(); it != this->def->end(); ++it) { diff --git a/xs/src/Config.hpp b/xs/src/Config.hpp index 71f945349..9dcb4983d 100644 --- a/xs/src/Config.hpp +++ b/xs/src/Config.hpp @@ -1,9 +1,7 @@ #ifndef slic3r_Config_hpp_ #define slic3r_Config_hpp_ -#include #include -#include #include #include #include @@ -11,6 +9,7 @@ #include #include #include +#include #include "Point.hpp" namespace Slic3r { @@ -379,7 +378,6 @@ class ConfigOptionDef std::string tooltip; std::string sidetext; std::string cli; - std::string scope; t_config_option_key ratio_over; bool multiline; bool full_width; @@ -430,6 +428,7 @@ class DynamicConfig : public ConfigBase ~DynamicConfig(); ConfigOption* option(const t_config_option_key opt_key, bool create = false); void keys(t_config_option_keys *keys); + void erase(const t_config_option_key opt_key); private: DynamicConfig(const DynamicConfig& other); // we disable this by making it private and unimplemented diff --git a/xs/src/ExPolygon.cpp b/xs/src/ExPolygon.cpp index 50ea5b094..3cb54583f 100644 --- a/xs/src/ExPolygon.cpp +++ b/xs/src/ExPolygon.cpp @@ -20,6 +20,7 @@ ExPolygon::operator Points() const ExPolygon::operator Polygons() const { Polygons polygons; + polygons.reserve(this->holes.size() + 1); polygons.push_back(this->contour); for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { polygons.push_back(*it); @@ -77,7 +78,7 @@ ExPolygon::is_valid() const bool ExPolygon::contains_line(const Line* line) const { - Polylines pl(1); + Polylines pl; pl.push_back(*line); Polylines pl_out; @@ -98,7 +99,8 @@ ExPolygon::contains_point(const Point* point) const Polygons ExPolygon::simplify_p(double tolerance) const { - Polygons pp(this->holes.size() + 1); + Polygons pp; + pp.reserve(this->holes.size() + 1); // contour Polygon p = this->contour; @@ -211,6 +213,8 @@ void ExPolygon::from_SV_check(SV* expoly_sv) { if (sv_isobject(expoly_sv) && (SvTYPE(SvRV(expoly_sv)) == SVt_PVMG)) { + if (!sv_isa(expoly_sv, "Slic3r::ExPolygon") && !sv_isa(expoly_sv, "Slic3r::ExPolygon::Ref")) + CONFESS("Not a valid Slic3r::ExPolygon object"); // a XS ExPolygon was supplied *this = *(ExPolygon *)SvIV((SV*)SvRV( expoly_sv )); } else { diff --git a/xs/src/Line.cpp b/xs/src/Line.cpp index 66f3d860f..16a3241af 100644 --- a/xs/src/Line.cpp +++ b/xs/src/Line.cpp @@ -1,9 +1,19 @@ #include "Line.hpp" #include "Polyline.hpp" #include +#include namespace Slic3r { +std::string +Line::wkt() const +{ + std::ostringstream ss; + ss << "LINESTRING(" << this->a.x << " " << this->a.y << "," + << this->b.x << " " << this->b.y << ")"; + return ss.str(); +} + Line::operator Polyline() const { Polyline pl; @@ -88,6 +98,8 @@ void Line::from_SV_check(SV* line_sv) { if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) { + if (!sv_isa(line_sv, "Slic3r::Line") && !sv_isa(line_sv, "Slic3r::Line::Ref")) + CONFESS("Not a valid Slic3r::Line object"); *this = *(Line*)SvIV((SV*)SvRV( line_sv )); } else { this->from_SV(line_sv); diff --git a/xs/src/Line.hpp b/xs/src/Line.hpp index 4e31ecaa7..e0298216f 100644 --- a/xs/src/Line.hpp +++ b/xs/src/Line.hpp @@ -17,6 +17,7 @@ class Line Point b; Line() {}; explicit Line(Point _a, Point _b): a(_a), b(_b) {}; + std::string wkt() const; operator Polyline() const; void scale(double factor); void translate(double x, double y); diff --git a/xs/src/Point.cpp b/xs/src/Point.cpp index 0fcd3d4eb..57385a9d4 100644 --- a/xs/src/Point.cpp +++ b/xs/src/Point.cpp @@ -1,14 +1,24 @@ +#include +#include #include "Point.hpp" #include "Line.hpp" -#include namespace Slic3r { bool -Point::operator==(const Point& rhs) const { +Point::operator==(const Point& rhs) const +{ return this->coincides_with(rhs); } +std::string +Point::wkt() const +{ + std::ostringstream ss; + ss << "POINT(" << this->x << " " << this->y << ")"; + return ss.str(); +} + void Point::scale(double factor) { @@ -105,8 +115,8 @@ Point::distance_to(const Line &line) const { if (line.a.coincides_with(&line.b)) return this->distance_to(&line.a); - double n = (line.b.x - line.a.x) * (line.a.y - this->y) - - (line.a.x - this->x) * (line.b.y - line.a.y); + double n = (double)(line.b.x - line.a.x) * (double)(line.a.y - this->y) + - (double)(line.a.x - this->x) * (double)(line.b.y - line.a.y); return std::abs(n) / line.length(); } @@ -121,7 +131,7 @@ Point::distance_to(const Line &line) const double Point::ccw(const Point &p1, const Point &p2) const { - return (p2.x - p1.x)*(this->y - p1.y) - (p2.y - p1.y)*(this->x - p1.x); + return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x); } double @@ -174,6 +184,8 @@ void Point::from_SV_check(SV* point_sv) { if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) { + if (!sv_isa(point_sv, "Slic3r::Point") && !sv_isa(point_sv, "Slic3r::Point::Ref")) + CONFESS("Not a valid Slic3r::Point object"); *this = *(Point*)SvIV((SV*)SvRV( point_sv )); } else { this->from_SV(point_sv); diff --git a/xs/src/Point.hpp b/xs/src/Point.hpp index 20f1dcfb9..d2eb6b323 100644 --- a/xs/src/Point.hpp +++ b/xs/src/Point.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace Slic3r { @@ -22,6 +23,7 @@ class Point coord_t y; explicit Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {}; bool operator==(const Point& rhs) const; + std::string wkt() const; void scale(double factor); void translate(double x, double y); void rotate(double angle, Point* center); diff --git a/xs/src/Polygon.cpp b/xs/src/Polygon.cpp index 4c590c301..1d807bf54 100644 --- a/xs/src/Polygon.cpp +++ b/xs/src/Polygon.cpp @@ -172,6 +172,15 @@ Polygon::to_SV_clone_ref() const { sv_setref_pv( sv, "Slic3r::Polygon", new Polygon(*this) ); return sv; } + +void +Polygon::from_SV_check(SV* poly_sv) +{ + if (sv_isobject(poly_sv) && !sv_isa(poly_sv, "Slic3r::Polygon") && !sv_isa(poly_sv, "Slic3r::Polygon::Ref")) + CONFESS("Not a valid Slic3r::Polygon object"); + + MultiPoint::from_SV_check(poly_sv); +} #endif } diff --git a/xs/src/Polygon.hpp b/xs/src/Polygon.hpp index f03f52972..6ab62ab0d 100644 --- a/xs/src/Polygon.hpp +++ b/xs/src/Polygon.hpp @@ -33,6 +33,7 @@ class Polygon : public MultiPoint { void simplify(double tolerance, Polygons &polygons) const; #ifdef SLIC3RXS + void from_SV_check(SV* poly_sv); SV* to_SV_ref(); SV* to_SV_clone_ref() const; #endif diff --git a/xs/src/Polyline.cpp b/xs/src/Polyline.cpp index c2801d74c..5f62b5b47 100644 --- a/xs/src/Polyline.cpp +++ b/xs/src/Polyline.cpp @@ -5,7 +5,7 @@ namespace Slic3r { Polyline::operator Polylines() const { - Polylines polylines(1); + Polylines polylines; polylines.push_back(*this); return polylines; } @@ -110,6 +110,15 @@ Polyline::to_SV_clone_ref() const sv_setref_pv( sv, "Slic3r::Polyline", new Polyline(*this) ); return sv; } + +void +Polyline::from_SV_check(SV* poly_sv) +{ + if (!sv_isa(poly_sv, "Slic3r::Polyline") && !sv_isa(poly_sv, "Slic3r::Polyline::Ref")) + CONFESS("Not a valid Slic3r::Polyline object"); + + MultiPoint::from_SV_check(poly_sv); +} #endif } diff --git a/xs/src/Polyline.hpp b/xs/src/Polyline.hpp index 3b9b95694..394edf4f2 100644 --- a/xs/src/Polyline.hpp +++ b/xs/src/Polyline.hpp @@ -20,6 +20,7 @@ class Polyline : public MultiPoint { void simplify(double tolerance); #ifdef SLIC3RXS + void from_SV_check(SV* poly_sv); SV* to_SV_ref(); SV* to_SV_clone_ref() const; #endif diff --git a/xs/src/PrintConfig.hpp b/xs/src/PrintConfig.hpp index 3cd58085a..fe0c6ce84 100644 --- a/xs/src/PrintConfig.hpp +++ b/xs/src/PrintConfig.hpp @@ -82,7 +82,6 @@ class PrintConfigDef Options["bottom_solid_layers"].category = "Layers and Perimeters"; Options["bottom_solid_layers"].tooltip = "Number of solid layers to generate on bottom surfaces."; Options["bottom_solid_layers"].cli = "bottom-solid-layers=i"; - Options["bottom_solid_layers"].scope = "object"; Options["bottom_solid_layers"].full_label = "Bottom solid layers"; Options["bridge_acceleration"].type = coFloat; @@ -171,7 +170,6 @@ class PrintConfigDef Options["extra_perimeters"].category = "Layers and Perimeters"; Options["extra_perimeters"].tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls."; Options["extra_perimeters"].cli = "extra-perimeters!"; - Options["extra_perimeters"].scope = "object"; Options["extruder"].type = coInt; Options["extruder"].label = "Extruder"; @@ -211,6 +209,7 @@ class PrintConfigDef Options["extrusion_width"].type = coFloatOrPercent; Options["extrusion_width"].label = "Default extrusion width"; + Options["extrusion_width"].category = "Extrusion Width"; Options["extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width. If left to zero, Slic3r calculates a width automatically. If expressed as percentage (for example: 230%) it will be computed over layer height."; Options["extrusion_width"].sidetext = "mm or % (leave 0 for auto)"; Options["extrusion_width"].cli = "extrusion-width=s"; @@ -236,6 +235,7 @@ class PrintConfigDef Options["fill_angle"].type = coInt; Options["fill_angle"].label = "Fill angle"; + Options["fill_angle"].category = "Infill"; Options["fill_angle"].tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them."; Options["fill_angle"].sidetext = "°"; Options["fill_angle"].cli = "fill-angle=i"; @@ -246,14 +246,12 @@ class PrintConfigDef Options["fill_density"].category = "Infill"; Options["fill_density"].tooltip = "Density of internal infill, expressed in the range 0 - 1."; Options["fill_density"].cli = "fill-density=f"; - Options["fill_density"].scope = "object"; Options["fill_pattern"].type = coEnum; Options["fill_pattern"].label = "Fill pattern"; Options["fill_pattern"].category = "Infill"; Options["fill_pattern"].tooltip = "Fill pattern for general low-density infill."; Options["fill_pattern"].cli = "fill-pattern=s"; - Options["fill_pattern"].scope = "object"; Options["fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["fill_pattern"].enum_values.push_back("rectilinear"); Options["fill_pattern"].enum_values.push_back("line"); @@ -285,12 +283,14 @@ class PrintConfigDef Options["first_layer_extrusion_width"].type = coFloatOrPercent; Options["first_layer_extrusion_width"].label = "First layer"; - Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) if will be computed over layer height."; + Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) if will be computed over first layer height."; Options["first_layer_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["first_layer_extrusion_width"].cli = "first-layer-extrusion-width=s"; + Options["first_layer_extrusion_width"].ratio_over = "first_layer_height"; Options["first_layer_height"].type = coFloatOrPercent; Options["first_layer_height"].label = "First layer height"; + Options["first_layer_height"].category = "Layers and Perimeters"; Options["first_layer_height"].tooltip = "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height."; Options["first_layer_height"].sidetext = "mm or %"; Options["first_layer_height"].cli = "first-layer-height=s"; @@ -360,17 +360,18 @@ class PrintConfigDef Options["infill_every_layers"].tooltip = "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy."; Options["infill_every_layers"].sidetext = "layers"; Options["infill_every_layers"].cli = "infill-every-layers=i"; - Options["infill_every_layers"].scope = "object"; Options["infill_every_layers"].full_label = "Combine infill every n layers"; Options["infill_every_layers"].min = 1; Options["infill_extruder"].type = coInt; Options["infill_extruder"].label = "Infill extruder"; + Options["infill_extruder"].category = "Extruders"; Options["infill_extruder"].tooltip = "The extruder to use when printing infill."; Options["infill_extruder"].cli = "infill-extruder=i"; Options["infill_extrusion_width"].type = coFloatOrPercent; Options["infill_extrusion_width"].label = "Infill"; + Options["infill_extrusion_width"].category = "Extrusion Width"; Options["infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) if will be computed over layer height."; Options["infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["infill_extrusion_width"].cli = "infill-extrusion-width=s"; @@ -385,7 +386,6 @@ class PrintConfigDef Options["infill_only_where_needed"].category = "Infill"; Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material)."; Options["infill_only_where_needed"].cli = "infill-only-where-needed!"; - Options["infill_only_where_needed"].scope = "object"; Options["infill_speed"].type = coFloat; Options["infill_speed"].label = "Infill"; @@ -405,6 +405,7 @@ class PrintConfigDef Options["layer_height"].type = coFloat; Options["layer_height"].label = "Layer height"; + Options["layer_height"].category = "Layers and Perimeters"; Options["layer_height"].tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print."; Options["layer_height"].sidetext = "mm"; Options["layer_height"].cli = "layer-height=f"; @@ -472,7 +473,6 @@ class PrintConfigDef Options["overhangs"].category = "Layers and Perimeters"; Options["overhangs"].tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan."; Options["overhangs"].cli = "overhangs!"; - Options["overhangs"].scope = "object"; Options["perimeter_acceleration"].type = coFloat; Options["perimeter_acceleration"].label = "Perimeters"; @@ -482,12 +482,14 @@ class PrintConfigDef Options["perimeter_extruder"].type = coInt; Options["perimeter_extruder"].label = "Perimeter extruder"; + Options["perimeter_extruder"].category = "Extruders"; Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters."; Options["perimeter_extruder"].cli = "perimeter-extruder=i"; Options["perimeter_extruder"].aliases.push_back("perimeters_extruder"); Options["perimeter_extrusion_width"].type = coFloatOrPercent; Options["perimeter_extrusion_width"].label = "Perimeters"; + Options["perimeter_extrusion_width"].category = "Extrusion Width"; Options["perimeter_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If expressed as percentage (for example 90%) if will be computed over layer height."; Options["perimeter_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["perimeter_extrusion_width"].cli = "perimeter-extrusion-width=s"; @@ -505,7 +507,6 @@ class PrintConfigDef Options["perimeters"].category = "Layers and Perimeters"; Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled."; Options["perimeters"].cli = "perimeters=i"; - Options["perimeters"].scope = "object"; Options["perimeters"].aliases.push_back("perimeter_offsets"); Options["post_process"].type = coStrings; @@ -528,7 +529,6 @@ class PrintConfigDef Options["raft_layers"].tooltip = "The object will be raised by this number of layers, and support material will be generated under it."; Options["raft_layers"].sidetext = "layers"; Options["raft_layers"].cli = "raft-layers=i"; - Options["raft_layers"].scope = "object"; Options["randomize_start"].type = coBool; Options["randomize_start"].label = "Randomize starting points"; @@ -627,7 +627,6 @@ class PrintConfigDef Options["solid_fill_pattern"].category = "Infill"; Options["solid_fill_pattern"].tooltip = "Fill pattern for top/bottom infill."; Options["solid_fill_pattern"].cli = "solid-fill-pattern=s"; - Options["solid_fill_pattern"].scope = "object"; Options["solid_fill_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["solid_fill_pattern"].enum_values.push_back("rectilinear"); Options["solid_fill_pattern"].enum_values.push_back("concentric"); @@ -646,7 +645,6 @@ class PrintConfigDef Options["solid_infill_below_area"].tooltip = "Force solid infill for regions having a smaller area than the specified threshold."; Options["solid_infill_below_area"].sidetext = "mm²"; Options["solid_infill_below_area"].cli = "solid-infill-below-area=f"; - Options["solid_infill_below_area"].scope = "object"; Options["solid_infill_every_layers"].type = coInt; Options["solid_infill_every_layers"].label = "Solid infill every"; @@ -654,11 +652,11 @@ class PrintConfigDef Options["solid_infill_every_layers"].tooltip = "This feature allows to force a solid layer every given number of layers. Zero to disable."; Options["solid_infill_every_layers"].sidetext = "layers"; Options["solid_infill_every_layers"].cli = "solid-infill-every-layers=i"; - Options["solid_infill_every_layers"].scope = "object"; Options["solid_infill_every_layers"].min = 0; Options["solid_infill_extrusion_width"].type = coFloatOrPercent; Options["solid_infill_extrusion_width"].label = "Solid infill"; + Options["solid_infill_extrusion_width"].category = "Extrusion Width"; Options["solid_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If expressed as percentage (for example 90%) if will be computed over layer height."; Options["solid_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["solid_infill_extrusion_width"].cli = "solid-infill-extrusion-width=s"; @@ -714,7 +712,6 @@ class PrintConfigDef Options["support_material"].category = "Support material"; Options["support_material"].tooltip = "Enable support material generation."; Options["support_material"].cli = "support-material!"; - Options["support_material"].scope = "object"; Options["support_material_angle"].type = coInt; Options["support_material_angle"].label = "Pattern angle"; @@ -722,7 +719,6 @@ class PrintConfigDef Options["support_material_angle"].tooltip = "Use this setting to rotate the support material pattern on the horizontal plane."; Options["support_material_angle"].sidetext = "°"; Options["support_material_angle"].cli = "support-material-angle=i"; - Options["support_material_angle"].scope = "object"; Options["support_material_enforce_layers"].type = coInt; Options["support_material_enforce_layers"].label = "Enforce support for the first"; @@ -730,22 +726,24 @@ class PrintConfigDef Options["support_material_enforce_layers"].tooltip = "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate."; Options["support_material_enforce_layers"].sidetext = "layers"; Options["support_material_enforce_layers"].cli = "support-material-enforce-layers=f"; - Options["support_material_enforce_layers"].scope = "object"; Options["support_material_enforce_layers"].full_label = "Enforce support for the first n layers"; Options["support_material_extruder"].type = coInt; Options["support_material_extruder"].label = "Support material extruder"; + Options["support_material_extruder"].category = "Extruders"; Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too."; Options["support_material_extruder"].cli = "support-material-extruder=i"; Options["support_material_extrusion_width"].type = coFloatOrPercent; Options["support_material_extrusion_width"].label = "Support material"; + Options["support_material_extrusion_width"].category = "Extrusion Width"; Options["support_material_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) if will be computed over layer height."; Options["support_material_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["support_material_extrusion_width"].cli = "support-material-extrusion-width=s"; Options["support_material_interface_extruder"].type = coInt; Options["support_material_interface_extruder"].label = "Support material interface extruder"; + Options["support_material_interface_extruder"].category = "Extruders"; Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too."; Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i"; @@ -755,7 +753,6 @@ class PrintConfigDef Options["support_material_interface_layers"].tooltip = "Number of interface layers to insert between the object(s) and support material."; Options["support_material_interface_layers"].sidetext = "layers"; Options["support_material_interface_layers"].cli = "support-material-interface-layers=i"; - Options["support_material_interface_layers"].scope = "object"; Options["support_material_interface_spacing"].type = coFloat; Options["support_material_interface_spacing"].label = "Interface pattern spacing"; @@ -763,14 +760,12 @@ class PrintConfigDef Options["support_material_interface_spacing"].tooltip = "Spacing between interface lines. Set zero to get a solid interface."; Options["support_material_interface_spacing"].sidetext = "mm"; Options["support_material_interface_spacing"].cli = "support-material-interface-spacing=f"; - Options["support_material_interface_spacing"].scope = "object"; Options["support_material_pattern"].type = coEnum; Options["support_material_pattern"].label = "Pattern"; Options["support_material_pattern"].category = "Support material"; Options["support_material_pattern"].tooltip = "Pattern used to generate support material."; Options["support_material_pattern"].cli = "support-material-pattern=s"; - Options["support_material_pattern"].scope = "object"; Options["support_material_pattern"].enum_keys_map = ConfigOptionEnum::get_enum_values(); Options["support_material_pattern"].enum_values.push_back("rectilinear"); Options["support_material_pattern"].enum_values.push_back("rectilinear-grid"); @@ -787,10 +782,10 @@ class PrintConfigDef Options["support_material_spacing"].tooltip = "Spacing between support material lines."; Options["support_material_spacing"].sidetext = "mm"; Options["support_material_spacing"].cli = "support-material-spacing=f"; - Options["support_material_spacing"].scope = "object"; Options["support_material_speed"].type = coFloat; Options["support_material_speed"].label = "Support material"; + Options["support_material_speed"].category = "Support material"; Options["support_material_speed"].tooltip = "Speed for printing support material."; Options["support_material_speed"].sidetext = "mm/s"; Options["support_material_speed"].cli = "support-material-speed=f"; @@ -801,7 +796,6 @@ class PrintConfigDef Options["support_material_threshold"].tooltip = "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)."; Options["support_material_threshold"].sidetext = "°"; Options["support_material_threshold"].cli = "support-material-threshold=i"; - Options["support_material_threshold"].scope = "object"; Options["temperature"].type = coInts; Options["temperature"].label = "Other layers"; @@ -816,7 +810,6 @@ class PrintConfigDef Options["thin_walls"].category = "Layers and Perimeters"; Options["thin_walls"].tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)."; Options["thin_walls"].cli = "thin-walls!"; - Options["thin_walls"].scope = "object"; Options["threads"].type = coInt; Options["threads"].label = "Threads"; @@ -837,6 +830,7 @@ class PrintConfigDef Options["top_infill_extrusion_width"].type = coFloatOrPercent; Options["top_infill_extrusion_width"].label = "Top solid infill"; + Options["top_infill_extrusion_width"].category = "Extrusion Width"; Options["top_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) if will be computed over layer height."; Options["top_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)"; Options["top_infill_extrusion_width"].cli = "top-infill-extrusion-width=s"; @@ -853,7 +847,6 @@ class PrintConfigDef Options["top_solid_layers"].category = "Layers and Perimeters"; Options["top_solid_layers"].tooltip = "Number of solid layers to generate on top surfaces."; Options["top_solid_layers"].cli = "top-solid-layers=i"; - Options["top_solid_layers"].scope = "object"; Options["top_solid_layers"].full_label = "Top solid layers"; Options["travel_speed"].type = coFloat; @@ -981,7 +974,6 @@ class PrintRegionConfig : public virtual StaticConfig ConfigOptionFloat solid_infill_below_area; ConfigOptionFloatOrPercent solid_infill_extrusion_width; ConfigOptionInt solid_infill_every_layers; - ConfigOptionInt solid_layers; ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; @@ -1029,7 +1021,6 @@ class PrintRegionConfig : public virtual StaticConfig if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area; if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width; if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers; - if (opt_key == "solid_layers") return &this->solid_layers; if (opt_key == "thin_walls") return &this->thin_walls; if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width; if (opt_key == "top_solid_layers") return &this->top_solid_layers; diff --git a/xs/src/Surface.cpp b/xs/src/Surface.cpp index 085756b86..4f3b5b96a 100644 --- a/xs/src/Surface.cpp +++ b/xs/src/Surface.cpp @@ -16,6 +16,13 @@ Surface::is_solid() const || this->surface_type == stInternalSolid; } +bool +Surface::is_external() const +{ + return this->surface_type == stTop + || this->surface_type == stBottom; +} + bool Surface::is_bridge() const { @@ -24,6 +31,15 @@ Surface::is_bridge() const } #ifdef SLIC3RXS +void +Surface::from_SV_check(SV* surface_sv) +{ + if (!sv_isa(surface_sv, "Slic3r::Surface") && !sv_isa(surface_sv, "Slic3r::Surface::Ref")) + CONFESS("Not a valid Slic3r::Surface object"); + // a XS Surface was supplied + *this = *(Surface *)SvIV((SV*)SvRV( surface_sv )); +} + SV* Surface::to_SV_ref() { SV* sv = newSV(0); diff --git a/xs/src/Surface.hpp b/xs/src/Surface.hpp index 304cc4572..6d6fec1de 100644 --- a/xs/src/Surface.hpp +++ b/xs/src/Surface.hpp @@ -18,9 +18,11 @@ class Surface unsigned short extra_perimeters; double area() const; bool is_solid() const; + bool is_external() const; bool is_bridge() const; #ifdef SLIC3RXS + void from_SV_check(SV* surface_sv); SV* to_SV_ref(); SV* to_SV_clone_ref() const; #endif diff --git a/xs/src/SurfaceCollection.cpp b/xs/src/SurfaceCollection.cpp index fb63d3333..16e1ee583 100644 --- a/xs/src/SurfaceCollection.cpp +++ b/xs/src/SurfaceCollection.cpp @@ -21,14 +21,14 @@ SurfaceCollection::simplify(double tolerance) /* group surfaces by common properties */ void -SurfaceCollection::group(std::vector *retval, bool merge_solid) +SurfaceCollection::group(std::vector *retval) { for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) { // find a group with the same properties SurfacesPtr* group = NULL; for (std::vector::iterator git = retval->begin(); git != retval->end(); ++git) { Surface* gkey = git->front(); - if ((gkey->surface_type == it->surface_type || (merge_solid && gkey->is_solid() && it->is_solid())) + if ( gkey->surface_type == it->surface_type && gkey->thickness == it->thickness && gkey->thickness_layers == it->thickness_layers && gkey->bridge_angle == it->bridge_angle) { diff --git a/xs/src/SurfaceCollection.hpp b/xs/src/SurfaceCollection.hpp index dc5ebc115..cb8088c47 100644 --- a/xs/src/SurfaceCollection.hpp +++ b/xs/src/SurfaceCollection.hpp @@ -11,7 +11,7 @@ class SurfaceCollection public: Surfaces surfaces; void simplify(double tolerance); - void group(std::vector *retval, bool merge_solid = false); + void group(std::vector *retval); }; } diff --git a/xs/src/TriangleMesh.cpp b/xs/src/TriangleMesh.cpp index 90da0571b..ba0c1fdb6 100644 --- a/xs/src/TriangleMesh.cpp +++ b/xs/src/TriangleMesh.cpp @@ -68,11 +68,13 @@ TriangleMesh::write_binary(char* output_file) stl_write_binary(&this->stl, output_file, ""); } - void TriangleMesh::repair() { if (this->repaired) return; + // admesh fails when repairing empty meshes + if (this->stl.stats.number_of_facets == 0) return; + // checking exact stl_check_facets_exact(&stl); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); @@ -164,433 +166,6 @@ void TriangleMesh::rotate(double angle, Point* center) this->translate(+center->x, +center->y, 0); } -void -TriangleMesh::slice(const std::vector &z, std::vector* layers) -{ - /* - This method gets called with a list of unscaled Z coordinates and outputs - a vector pointer having the same number of items as the original list. - Each item is a vector of polygons created by slicing our mesh at the - given heights. - - This method should basically combine the behavior of the existing - Perl methods defined in lib/Slic3r/TriangleMesh.pm: - - - analyze(): this creates the 'facets_edges' and the 'edges_facets' - tables (we don't need the 'edges' table) - - - slice_facet(): this has to be done for each facet. It generates - intersection lines with each plane identified by the Z list. - The get_layer_range() binary search used to identify the Z range - of the facet is already ported to C++ (see Object.xsp) - - - make_loops(): this has to be done for each layer. It creates polygons - from the lines generated by the previous step. - - At the end, we free the tables generated by analyze() as we don't - need them anymore. - FUTURE: parallelize slice_facet() and make_loops() - */ - - // build a table to map a facet_idx to its three edge indices - this->require_shared_vertices(); - typedef std::pair t_edge; - typedef std::vector t_edges; // edge_idx => a_id,b_id - typedef std::map t_edges_map; // a_id,b_id => edge_idx - typedef std::vector< std::vector > t_facets_edges; - t_facets_edges facets_edges; - - facets_edges.resize(this->stl.stats.number_of_facets); - - { - t_edges edges; - // reserve() instad of resize() because otherwise we couldn't read .size() below to assign edge_idx - edges.reserve(this->stl.stats.number_of_facets * 3); // number of edges = number of facets * 3 - t_edges_map edges_map; - for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) { - facets_edges[facet_idx].resize(3); - for (int i = 0; i <= 2; i++) { - int a_id = this->stl.v_indices[facet_idx].vertex[i]; - int b_id = this->stl.v_indices[facet_idx].vertex[(i+1) % 3]; - - int edge_idx; - t_edges_map::const_iterator my_edge = edges_map.find(std::make_pair(b_id,a_id)); - if (my_edge != edges_map.end()) { - edge_idx = my_edge->second; - } else { - /* admesh can assign the same edge ID to more than two facets (which is - still topologically correct), so we have to search for a duplicate of - this edge too in case it was already seen in this orientation */ - my_edge = edges_map.find(std::make_pair(a_id,b_id)); - - if (my_edge != edges_map.end()) { - edge_idx = my_edge->second; - } else { - // edge isn't listed in table, so we insert it - edge_idx = edges.size(); - edges.push_back(std::make_pair(a_id,b_id)); - edges_map[ edges[edge_idx] ] = edge_idx; - } - } - facets_edges[facet_idx][i] = edge_idx; - - #ifdef SLIC3R_DEBUG - printf(" [facet %d, edge %d] a_id = %d, b_id = %d --> edge %d\n", facet_idx, i, a_id, b_id, edge_idx); - #endif - } - } - } - - std::vector lines(z.size()); - - // clone shared vertices coordinates and scale them - stl_vertex* v_scaled_shared = (stl_vertex*)calloc(this->stl.stats.shared_vertices, sizeof(stl_vertex)); - std::copy(this->stl.v_shared, this->stl.v_shared + this->stl.stats.shared_vertices, v_scaled_shared); - for (int i = 0; i < this->stl.stats.shared_vertices; i++) { - v_scaled_shared[i].x /= SCALING_FACTOR; - v_scaled_shared[i].y /= SCALING_FACTOR; - v_scaled_shared[i].z /= SCALING_FACTOR; - } - - for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; facet_idx++) { - stl_facet* facet = &this->stl.facet_start[facet_idx]; - - // find facet extents - double min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); - double max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); - - #ifdef SLIC3R_DEBUG - printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, - facet->vertex[0].x, facet->vertex[0].y, facet->vertex[0].z, - facet->vertex[1].x, facet->vertex[1].y, facet->vertex[1].z, - facet->vertex[2].x, facet->vertex[2].y, facet->vertex[2].z); - printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif - - if (min_z == max_z) { - #ifdef SLIC3R_DEBUG - printf("Facet is horizontal; ignoring\n"); - #endif - continue; - } - - // find layer extents - std::vector::const_iterator min_layer, max_layer; - min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z - #ifdef SLIC3R_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif - - for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { - std::vector::size_type layer_idx = it - z.begin(); - double slice_z_u = *it; // unscaled - double slice_z = slice_z_u / SCALING_FACTOR; - std::vector points; - std::vector< std::vector::size_type > points_on_layer; - bool found_horizontal_edge = false; - - /* reorder vertices so that the first one is the one with lowest Z - this is needed to get all intersection lines in a consistent order - (external on the right of the line) */ - int i = 0; - if (facet->vertex[1].z == min_z) { - // vertex 1 has lowest Z - i = 1; - } else if (facet->vertex[2].z == min_z) { - // vertex 2 has lowest Z - i = 2; - } - for (int j = i; (j-i) < 3; j++) { // loop through facet edges - int edge_id = facets_edges[facet_idx][j % 3]; - int a_id = this->stl.v_indices[facet_idx].vertex[j % 3]; - int b_id = this->stl.v_indices[facet_idx].vertex[(j+1) % 3]; - stl_vertex* a = &v_scaled_shared[a_id]; - stl_vertex* b = &v_scaled_shared[b_id]; - - if (a->z == b->z && a->z == slice_z) { - // edge is horizontal and belongs to the current layer - - /* We assume that this method is never being called for horizontal - facets, so no other edge is going to be on this layer. */ - IntersectionLine line; - if (facet->vertex[0].z < slice_z_u || facet->vertex[1].z < slice_z_u || facet->vertex[2].z < slice_z_u) { - line.edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); - } else { - line.edge_type = feBottom; - } - line.a.x = a->x; - line.a.y = a->y; - line.b.x = b->x; - line.b.y = b->y; - line.a_id = a_id; - line.b_id = b_id; - - lines[layer_idx].push_back(line); - found_horizontal_edge = true; - break; - } else if (a->z == slice_z) { - IntersectionPoint point; - point.x = a->x; - point.y = a->y; - point.point_id = a_id; - points.push_back(point); - points_on_layer.push_back(points.size()-1); - } else if (b->z == slice_z) { - IntersectionPoint point; - point.x = b->x; - point.y = b->y; - point.point_id = b_id; - points.push_back(point); - points_on_layer.push_back(points.size()-1); - } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { - // edge intersects the current layer; calculate intersection - - IntersectionPoint point; - point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); - point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); - point.edge_id = edge_id; - points.push_back(point); - } - } - if (found_horizontal_edge) continue; - - if (!points_on_layer.empty()) { - // we can't have only one point on layer because each vertex gets detected - // twice (once for each edge), and we can't have three points on layer because - // we assume this code is not getting called for horizontal facets - assert(points_on_layer.size() == 2); - assert( points[ points_on_layer[0] ].point_id == points[ points_on_layer[1] ].point_id ); - if (points.size() < 3) continue; // no intersection point, this is a V-shaped facet tangent to plane - points.erase( points.begin() + points_on_layer[1] ); - } - - if (!points.empty()) { - assert(points.size() == 2); // facets must intersect each plane 0 or 2 times - IntersectionLine line; - line.a.x = points[1].x; - line.a.y = points[1].y; - line.b.x = points[0].x; - line.b.y = points[0].y; - line.a_id = points[1].point_id; - line.b_id = points[0].point_id; - line.edge_a_id = points[1].edge_id; - line.edge_b_id = points[0].edge_id; - lines[layer_idx].push_back(line); - } - } - } - - free(v_scaled_shared); - - // build loops - layers->resize(z.size()); - for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { - int layer_idx = it - lines.begin(); - #ifdef SLIC3R_DEBUG - printf("Layer %d:\n", layer_idx); - #endif - - // remove tangent edges - for (IntersectionLines::iterator line = it->begin(); line != it->end(); ++line) { - if (line->skip || line->edge_type == feNone) continue; - - /* if the line is a facet edge, find another facet edge - having the same endpoints but in reverse order */ - for (IntersectionLines::iterator line2 = line + 1; line2 != it->end(); ++line2) { - if (line2->skip || line2->edge_type == feNone) continue; - - // are these facets adjacent? (sharing a common edge on this layer) - if (line->a_id == line2->a_id && line->b_id == line2->b_id) { - line2->skip = true; - - /* if they are both oriented upwards or downwards (like a 'V') - then we can remove both edges from this layer since it won't - affect the sliced shape */ - /* if one of them is oriented upwards and the other is oriented - downwards, let's only keep one of them (it doesn't matter which - one since all 'top' lines were reversed at slicing) */ - if (line->edge_type == line2->edge_type) { - line->skip = true; - break; - } - } - } - } - - // build a map of lines by edge_a_id and a_id - std::vector by_edge_a_id, by_a_id; - by_edge_a_id.resize(this->stl.stats.number_of_facets * 3); - by_a_id.resize(this->stl.stats.shared_vertices); - for (IntersectionLines::iterator line = it->begin(); line != it->end(); ++line) { - if (line->skip) continue; - if (line->edge_a_id != -1) by_edge_a_id[line->edge_a_id].push_back(&(*line)); - if (line->a_id != -1) by_a_id[line->a_id].push_back(&(*line)); - } - - CYCLE: while (1) { - // take first spare line and start a new loop - IntersectionLine* first_line = NULL; - for (IntersectionLines::iterator line = it->begin(); line != it->end(); ++line) { - if (line->skip) continue; - first_line = &(*line); - break; - } - if (first_line == NULL) break; - first_line->skip = true; - IntersectionLinePtrs loop; - loop.push_back(first_line); - - /* - printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, - first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); - */ - - while (1) { - // find a line starting where last one finishes - IntersectionLine* next_line = NULL; - if (loop.back()->edge_b_id != -1) { - IntersectionLinePtrs* candidates = &(by_edge_a_id[loop.back()->edge_b_id]); - for (IntersectionLinePtrs::iterator lineptr = candidates->begin(); lineptr != candidates->end(); ++lineptr) { - if ((*lineptr)->skip) continue; - next_line = *lineptr; - break; - } - } - if (next_line == NULL && loop.back()->b_id != -1) { - IntersectionLinePtrs* candidates = &(by_a_id[loop.back()->b_id]); - for (IntersectionLinePtrs::iterator lineptr = candidates->begin(); lineptr != candidates->end(); ++lineptr) { - if ((*lineptr)->skip) continue; - next_line = *lineptr; - break; - } - } - - if (next_line == NULL) { - // check whether we closed this loop - if ((loop.front()->edge_a_id != -1 && loop.front()->edge_a_id == loop.back()->edge_b_id) - || (loop.front()->a_id != -1 && loop.front()->a_id == loop.back()->b_id)) { - // loop is complete - Polygon p; - p.points.reserve(loop.size()); - for (IntersectionLinePtrs::iterator lineptr = loop.begin(); lineptr != loop.end(); ++lineptr) { - p.points.push_back((*lineptr)->a); - } - (*layers)[layer_idx].push_back(p); - - #ifdef SLIC3R_DEBUG - printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); - #endif - - goto CYCLE; - } - - // we can't close this loop! - //// push @failed_loops, [@loop]; - #ifdef SLIC3R_DEBUG - printf(" Unable to close this loop having %d points\n", (int)loop.size()); - #endif - goto CYCLE; - } - /* - printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, - next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); - */ - loop.push_back(next_line); - next_line->skip = true; - } - } - } -} - -class _area_comp { - public: - _area_comp(std::vector* _aa) : abs_area(_aa) {}; - bool operator() (const size_t &a, const size_t &b) { - return (*this->abs_area)[a] > (*this->abs_area)[b]; - } - - private: - std::vector* abs_area; -}; - -void -TriangleMesh::slice(const std::vector &z, std::vector* layers) -{ - std::vector layers_p; - this->slice(z, &layers_p); - - /* - Input loops are not suitable for evenodd nor nonzero fill types, as we might get - two consecutive concentric loops having the same winding order - and we have to - respect such order. In that case, evenodd would create wrong inversions, and nonzero - would ignore holes inside two concentric contours. - So we're ordering loops and collapse consecutive concentric loops having the same - winding order. - TODO: find a faster algorithm for this, maybe with some sort of binary search. - If we computed a "nesting tree" we could also just remove the consecutive loops - having the same winding order, and remove the extra one(s) so that we could just - supply everything to offset_ex() instead of performing several union/diff calls. - - we sort by area assuming that the outermost loops have larger area; - the previous sorting method, based on $b->contains_point($a->[0]), failed to nest - loops correctly in some edge cases when original model had overlapping facets - */ - - layers->resize(z.size()); - - for (std::vector::const_iterator loops = layers_p.begin(); loops != layers_p.end(); ++loops) { - size_t layer_id = loops - layers_p.begin(); - - std::vector area; - std::vector abs_area; - std::vector sorted_area; // vector of indices - for (Polygons::const_iterator loop = loops->begin(); loop != loops->end(); ++loop) { - double a = loop->area(); - area.push_back(a); - abs_area.push_back(std::fabs(a)); - sorted_area.push_back(loop - loops->begin()); - } - - std::sort(sorted_area.begin(), sorted_area.end(), _area_comp(&abs_area)); // outer first - - // we don't perform a safety offset now because it might reverse cw loops - Polygons slices; - for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++loop_idx) { - /* we rely on the already computed area to determine the winding order - of the loops, since the Orientation() function provided by Clipper - would do the same, thus repeating the calculation */ - Polygons::const_iterator loop = loops->begin() + *loop_idx; - if (area[*loop_idx] >= 0) { - slices.push_back(*loop); - } else { - diff(slices, *loop, slices); - } - } - - // perform a safety offset to merge very close facets (TODO: find test case for this) - double safety_offset = scale_(0.0499); - ExPolygons ex_slices; - offset2_ex(slices, ex_slices, +safety_offset, -safety_offset); - - #ifdef SLIC3R_DEBUG - size_t holes_count = 0; - for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++e) { - holes_count += e->holes.size(); - } - printf("Layer %zu (slice_z = %.2f): %zu surface(s) having %zu holes detected from %zu polylines\n", - layer_id, z[layer_id], ex_slices.size(), holes_count, loops->size()); - #endif - - ExPolygons* layer = &(*layers)[layer_id]; - layer->insert(layer->end(), ex_slices.begin(), ex_slices.end()); - } -} - TriangleMeshPtrs TriangleMesh::split() const { @@ -760,4 +335,574 @@ void TriangleMesh::ReadFromPerl(SV* vertices, SV* facets) } #endif +void +TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) +{ + /* + This method gets called with a list of unscaled Z coordinates and outputs + a vector pointer having the same number of items as the original list. + Each item is a vector of polygons created by slicing our mesh at the + given heights. + + This method should basically combine the behavior of the existing + Perl methods defined in lib/Slic3r/TriangleMesh.pm: + + - analyze(): this creates the 'facets_edges' and the 'edges_facets' + tables (we don't need the 'edges' table) + + - slice_facet(): this has to be done for each facet. It generates + intersection lines with each plane identified by the Z list. + The get_layer_range() binary search used to identify the Z range + of the facet is already ported to C++ (see Object.xsp) + + - make_loops(): this has to be done for each layer. It creates polygons + from the lines generated by the previous step. + + At the end, we free the tables generated by analyze() as we don't + need them anymore. + FUTURE: parallelize slice_facet() and make_loops() + + NOTE: this method accepts a vector of floats because the mesh coordinate + type is float. + */ + + std::vector lines(z.size()); + + for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { + stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; + + // find facet extents + float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); + float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); + + #ifdef SLIC3R_DEBUG + printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, + facet->vertex[0].x, facet->vertex[0].y, facet->vertex[0].z, + facet->vertex[1].x, facet->vertex[1].y, facet->vertex[1].z, + facet->vertex[2].x, facet->vertex[2].y, facet->vertex[2].z); + printf("z: min = %.2f, max = %.2f\n", min_z, max_z); + #endif + + // find layer extents + std::vector::const_iterator min_layer, max_layer; + min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z + max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z + #ifdef SLIC3R_DEBUG + printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); + #endif + + for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { + std::vector::size_type layer_idx = it - z.begin(); + this->slice_facet(*it / SCALING_FACTOR, *facet, facet_idx, min_z, max_z, &lines[layer_idx]); + } + } + + // v_scaled_shared could be freed here + + // build loops + layers->resize(z.size()); + for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { + int layer_idx = it - lines.begin(); + #ifdef SLIC3R_DEBUG + printf("Layer %d:\n", layer_idx); + #endif + + this->make_loops(*it, &(*layers)[layer_idx]); + } +} + +void +TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) +{ + std::vector layers_p; + this->slice(z, &layers_p); + + layers->resize(z.size()); + for (std::vector::const_iterator loops = layers_p.begin(); loops != layers_p.end(); ++loops) { + #ifdef SLIC3R_DEBUG + printf("Layer %zu (slice_z = %.2f): ", layer_id, z[layer_id]); + #endif + + this->make_expolygons(*loops, &(*layers)[ loops - layers_p.begin() ]); + } +} + +void +TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const +{ + std::vector points; + std::vector< std::vector::size_type > points_on_layer; + bool found_horizontal_edge = false; + + /* reorder vertices so that the first one is the one with lowest Z + this is needed to get all intersection lines in a consistent order + (external on the right of the line) */ + int i = 0; + if (facet.vertex[1].z == min_z) { + // vertex 1 has lowest Z + i = 1; + } else if (facet.vertex[2].z == min_z) { + // vertex 2 has lowest Z + i = 2; + } + for (int j = i; (j-i) < 3; j++) { // loop through facet edges + int edge_id = this->facets_edges[facet_idx][j % 3]; + int a_id = this->mesh->stl.v_indices[facet_idx].vertex[j % 3]; + int b_id = this->mesh->stl.v_indices[facet_idx].vertex[(j+1) % 3]; + stl_vertex* a = &this->v_scaled_shared[a_id]; + stl_vertex* b = &this->v_scaled_shared[b_id]; + + if (a->z == b->z && a->z == slice_z) { + // edge is horizontal and belongs to the current layer + + /* We assume that this method is never being called for horizontal + facets, so no other edge is going to be on this layer. */ + stl_vertex* v0 = &this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[0] ]; + stl_vertex* v1 = &this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[1] ]; + stl_vertex* v2 = &this->v_scaled_shared[ this->mesh->stl.v_indices[facet_idx].vertex[2] ]; + IntersectionLine line; + if (min_z == max_z) { + line.edge_type = feHorizontal; + } else if (v0->z < slice_z || v1->z < slice_z || v2->z < slice_z) { + line.edge_type = feTop; + std::swap(a, b); + std::swap(a_id, b_id); + } else { + line.edge_type = feBottom; + } + line.a.x = a->x; + line.a.y = a->y; + line.b.x = b->x; + line.b.y = b->y; + line.a_id = a_id; + line.b_id = b_id; + lines->push_back(line); + + found_horizontal_edge = true; + + // if this is a top or bottom edge, we can stop looping through edges + // because we won't find anything interesting + + if (line.edge_type != feHorizontal) return; + } else if (a->z == slice_z) { + IntersectionPoint point; + point.x = a->x; + point.y = a->y; + point.point_id = a_id; + points.push_back(point); + points_on_layer.push_back(points.size()-1); + } else if (b->z == slice_z) { + IntersectionPoint point; + point.x = b->x; + point.y = b->y; + point.point_id = b_id; + points.push_back(point); + points_on_layer.push_back(points.size()-1); + } else if ((a->z < slice_z && b->z > slice_z) || (b->z < slice_z && a->z > slice_z)) { + // edge intersects the current layer; calculate intersection + + IntersectionPoint point; + point.x = b->x + (a->x - b->x) * (slice_z - b->z) / (a->z - b->z); + point.y = b->y + (a->y - b->y) * (slice_z - b->z) / (a->z - b->z); + point.edge_id = edge_id; + points.push_back(point); + } + } + if (found_horizontal_edge) return; + + if (!points_on_layer.empty()) { + // we can't have only one point on layer because each vertex gets detected + // twice (once for each edge), and we can't have three points on layer because + // we assume this code is not getting called for horizontal facets + assert(points_on_layer.size() == 2); + assert( points[ points_on_layer[0] ].point_id == points[ points_on_layer[1] ].point_id ); + if (points.size() < 3) return; // no intersection point, this is a V-shaped facet tangent to plane + points.erase( points.begin() + points_on_layer[1] ); + } + + if (!points.empty()) { + assert(points.size() == 2); // facets must intersect each plane 0 or 2 times + IntersectionLine line; + line.a.x = points[1].x; + line.a.y = points[1].y; + line.b.x = points[0].x; + line.b.y = points[0].y; + line.a_id = points[1].point_id; + line.b_id = points[0].point_id; + line.edge_a_id = points[1].edge_id; + line.edge_b_id = points[0].edge_id; + lines->push_back(line); + return; + } +} + +void +TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) +{ + + /* + SVG svg("lines.svg"); + for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { + svg.AddLine(*line); + } + svg.Close(); + */ + + // remove tangent edges + for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { + if (line->skip || line->edge_type == feNone) continue; + + /* if the line is a facet edge, find another facet edge + having the same endpoints but in reverse order */ + for (IntersectionLines::iterator line2 = line + 1; line2 != lines.end(); ++line2) { + if (line2->skip || line2->edge_type == feNone) continue; + + // are these facets adjacent? (sharing a common edge on this layer) + if (line->a_id == line2->a_id && line->b_id == line2->b_id) { + line2->skip = true; + + /* if they are both oriented upwards or downwards (like a 'V') + then we can remove both edges from this layer since it won't + affect the sliced shape */ + /* if one of them is oriented upwards and the other is oriented + downwards, let's only keep one of them (it doesn't matter which + one since all 'top' lines were reversed at slicing) */ + if (line->edge_type == line2->edge_type) { + line->skip = true; + break; + } + } else if (line->a_id == line2->b_id && line->b_id == line2->a_id) { + /* if this edge joins two horizontal facets, remove both of them */ + if (line->edge_type == feHorizontal && line2->edge_type == feHorizontal) { + line->skip = true; + line2->skip = true; + break; + } + } + } + } + + // build a map of lines by edge_a_id and a_id + std::vector by_edge_a_id, by_a_id; + by_edge_a_id.resize(this->mesh->stl.stats.number_of_facets * 3); + by_a_id.resize(this->mesh->stl.stats.shared_vertices); + for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { + if (line->skip) continue; + if (line->edge_a_id != -1) by_edge_a_id[line->edge_a_id].push_back(&(*line)); + if (line->a_id != -1) by_a_id[line->a_id].push_back(&(*line)); + } + + CYCLE: while (1) { + // take first spare line and start a new loop + IntersectionLine* first_line = NULL; + for (IntersectionLines::iterator line = lines.begin(); line != lines.end(); ++line) { + if (line->skip) continue; + first_line = &(*line); + break; + } + if (first_line == NULL) break; + first_line->skip = true; + IntersectionLinePtrs loop; + loop.push_back(first_line); + + /* + printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, + first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); + */ + + while (1) { + // find a line starting where last one finishes + IntersectionLine* next_line = NULL; + if (loop.back()->edge_b_id != -1) { + IntersectionLinePtrs* candidates = &(by_edge_a_id[loop.back()->edge_b_id]); + for (IntersectionLinePtrs::iterator lineptr = candidates->begin(); lineptr != candidates->end(); ++lineptr) { + if ((*lineptr)->skip) continue; + next_line = *lineptr; + break; + } + } + if (next_line == NULL && loop.back()->b_id != -1) { + IntersectionLinePtrs* candidates = &(by_a_id[loop.back()->b_id]); + for (IntersectionLinePtrs::iterator lineptr = candidates->begin(); lineptr != candidates->end(); ++lineptr) { + if ((*lineptr)->skip) continue; + next_line = *lineptr; + break; + } + } + + if (next_line == NULL) { + // check whether we closed this loop + if ((loop.front()->edge_a_id != -1 && loop.front()->edge_a_id == loop.back()->edge_b_id) + || (loop.front()->a_id != -1 && loop.front()->a_id == loop.back()->b_id)) { + // loop is complete + Polygon p; + p.points.reserve(loop.size()); + for (IntersectionLinePtrs::iterator lineptr = loop.begin(); lineptr != loop.end(); ++lineptr) { + p.points.push_back((*lineptr)->a); + } + loops->push_back(p); + + #ifdef SLIC3R_DEBUG + printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); + #endif + + goto CYCLE; + } + + // we can't close this loop! + //// push @failed_loops, [@loop]; + //#ifdef SLIC3R_DEBUG + printf(" Unable to close this loop having %d points\n", (int)loop.size()); + //#endif + goto CYCLE; + } + /* + printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, + next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); + */ + loop.push_back(next_line); + next_line->skip = true; + } + } +} + +class _area_comp { + public: + _area_comp(std::vector* _aa) : abs_area(_aa) {}; + bool operator() (const size_t &a, const size_t &b) { + return (*this->abs_area)[a] > (*this->abs_area)[b]; + } + + private: + std::vector* abs_area; +}; + +void +TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) +{ + /* + Input loops are not suitable for evenodd nor nonzero fill types, as we might get + two consecutive concentric loops having the same winding order - and we have to + respect such order. In that case, evenodd would create wrong inversions, and nonzero + would ignore holes inside two concentric contours. + So we're ordering loops and collapse consecutive concentric loops having the same + winding order. + TODO: find a faster algorithm for this, maybe with some sort of binary search. + If we computed a "nesting tree" we could also just remove the consecutive loops + having the same winding order, and remove the extra one(s) so that we could just + supply everything to offset_ex() instead of performing several union/diff calls. + + we sort by area assuming that the outermost loops have larger area; + the previous sorting method, based on $b->contains_point($a->[0]), failed to nest + loops correctly in some edge cases when original model had overlapping facets + */ + + std::vector area; + std::vector abs_area; + std::vector sorted_area; // vector of indices + for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++loop) { + double a = loop->area(); + area.push_back(a); + abs_area.push_back(std::fabs(a)); + sorted_area.push_back(loop - loops.begin()); + } + + std::sort(sorted_area.begin(), sorted_area.end(), _area_comp(&abs_area)); // outer first + + // we don't perform a safety offset now because it might reverse cw loops + Polygons p_slices; + for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++loop_idx) { + /* we rely on the already computed area to determine the winding order + of the loops, since the Orientation() function provided by Clipper + would do the same, thus repeating the calculation */ + Polygons::const_iterator loop = loops.begin() + *loop_idx; + if (area[*loop_idx] >= 0) { + p_slices.push_back(*loop); + } else { + diff(p_slices, *loop, p_slices); + } + } + + // perform a safety offset to merge very close facets (TODO: find test case for this) + double safety_offset = scale_(0.0499); + ExPolygons ex_slices; + offset2_ex(p_slices, ex_slices, +safety_offset, -safety_offset); + + #ifdef SLIC3R_DEBUG + size_t holes_count = 0; + for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++e) { + holes_count += e->holes.size(); + } + printf("%zu surface(s) having %zu holes detected from %zu polylines\n", + ex_slices.size(), holes_count, loops.size()); + #endif + + // append to the supplied collection + slices->insert(slices->end(), ex_slices.begin(), ex_slices.end()); +} + +void +TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) +{ + Polygons pp; + this->make_loops(lines, &pp); + this->make_expolygons(pp, slices); +} + +void +TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) +{ + std::vector lines; + + float scaled_z = scale_(z); + for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { + stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; + + // find facet extents + float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); + float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); + + // intersect facet with cutting plane + this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &lines); + + if (min_z > z || (min_z == z && max_z > min_z)) { + // facet is above the cut plane but does not belong to it + if (upper != NULL) stl_add_facet(&upper->stl, facet); + } else if (max_z < z || (max_z == z && max_z > min_z)) { + // facet is below the cut plane but does not belong to it + if (lower != NULL) stl_add_facet(&lower->stl, facet); + } else if (min_z < z && max_z > z) { + // facet is cut by the slicing plane + + // look for the vertex on whose side of the slicing plane there are no other vertices + int isolated_vertex; + if ( (facet->vertex[0].z > z) == (facet->vertex[1].z > z) ) { + isolated_vertex = 2; + } else if ( (facet->vertex[1].z > z) == (facet->vertex[2].z > z) ) { + isolated_vertex = 0; + } else { + isolated_vertex = 1; + } + + // get vertices starting from the isolated one + stl_vertex* v0 = &facet->vertex[isolated_vertex]; + stl_vertex* v1 = &facet->vertex[(isolated_vertex+1) % 3]; + stl_vertex* v2 = &facet->vertex[(isolated_vertex+2) % 3]; + + // intersect v0-v1 and v2-v0 with cutting plane and make new vertices + stl_vertex v0v1, v2v0; + v0v1.x = v1->x + (v0->x - v1->x) * (z - v1->z) / (v0->z - v1->z); + v0v1.y = v1->y + (v0->y - v1->y) * (z - v1->z) / (v0->z - v1->z); + v0v1.z = z; + v2v0.x = v2->x + (v0->x - v2->x) * (z - v2->z) / (v0->z - v2->z); + v2v0.y = v2->y + (v0->y - v2->y) * (z - v2->z) / (v0->z - v2->z); + v2v0.z = z; + + // build the triangular facet + stl_facet triangle; + triangle.vertex[0] = *v0; + triangle.vertex[1] = v0v1; + triangle.vertex[2] = v2v0; + + // build the facets forming a quadrilateral on the other side + stl_facet quadrilateral[2]; + quadrilateral[0].vertex[0] = *v1; + quadrilateral[0].vertex[1] = *v2; + quadrilateral[0].vertex[2] = v0v1; + quadrilateral[1].vertex[0] = *v2; + quadrilateral[1].vertex[0] = v2v0; + quadrilateral[1].vertex[0] = v0v1; + + if (v0->z > z) { + if (upper != NULL) stl_add_facet(&upper->stl, &triangle); + if (lower != NULL) { + stl_add_facet(&lower->stl, &quadrilateral[0]); + stl_add_facet(&lower->stl, &quadrilateral[1]); + } + } else { + if (upper != NULL) { + stl_add_facet(&upper->stl, &quadrilateral[0]); + stl_add_facet(&upper->stl, &quadrilateral[1]); + } + if (lower != NULL) stl_add_facet(&lower->stl, &triangle); + } + } + } + + // compute shape of section + ExPolygons section; + this->make_expolygons(lines, §ion); + + /* + stl_get_size(&(upper->stl)); + stl_get_size(&(lower->stl)); + */ +} + +TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh), v_scaled_shared(NULL) +{ + // build a table to map a facet_idx to its three edge indices + this->mesh->require_shared_vertices(); + typedef std::pair t_edge; + typedef std::vector t_edges; // edge_idx => a_id,b_id + typedef std::map t_edges_map; // a_id,b_id => edge_idx + + this->facets_edges.resize(this->mesh->stl.stats.number_of_facets); + + { + t_edges edges; + // reserve() instad of resize() because otherwise we couldn't read .size() below to assign edge_idx + edges.reserve(this->mesh->stl.stats.number_of_facets * 3); // number of edges = number of facets * 3 + t_edges_map edges_map; + for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { + this->facets_edges[facet_idx].resize(3); + for (int i = 0; i <= 2; i++) { + int a_id = this->mesh->stl.v_indices[facet_idx].vertex[i]; + int b_id = this->mesh->stl.v_indices[facet_idx].vertex[(i+1) % 3]; + + int edge_idx; + t_edges_map::const_iterator my_edge = edges_map.find(std::make_pair(b_id,a_id)); + if (my_edge != edges_map.end()) { + edge_idx = my_edge->second; + } else { + /* admesh can assign the same edge ID to more than two facets (which is + still topologically correct), so we have to search for a duplicate of + this edge too in case it was already seen in this orientation */ + my_edge = edges_map.find(std::make_pair(a_id,b_id)); + + if (my_edge != edges_map.end()) { + edge_idx = my_edge->second; + } else { + // edge isn't listed in table, so we insert it + edge_idx = edges.size(); + edges.push_back(std::make_pair(a_id,b_id)); + edges_map[ edges[edge_idx] ] = edge_idx; + } + } + this->facets_edges[facet_idx][i] = edge_idx; + + #ifdef SLIC3R_DEBUG + printf(" [facet %d, edge %d] a_id = %d, b_id = %d --> edge %d\n", facet_idx, i, a_id, b_id, edge_idx); + #endif + } + } + } + + // clone shared vertices coordinates and scale them + this->v_scaled_shared = (stl_vertex*)calloc(this->mesh->stl.stats.shared_vertices, sizeof(stl_vertex)); + std::copy(this->mesh->stl.v_shared, this->mesh->stl.v_shared + this->mesh->stl.stats.shared_vertices, this->v_scaled_shared); + for (int i = 0; i < this->mesh->stl.stats.shared_vertices; i++) { + this->v_scaled_shared[i].x /= SCALING_FACTOR; + this->v_scaled_shared[i].y /= SCALING_FACTOR; + this->v_scaled_shared[i].z /= SCALING_FACTOR; + } +} + +TriangleMeshSlicer::~TriangleMeshSlicer() +{ + if (this->v_scaled_shared != NULL) free(this->v_scaled_shared); +} + } diff --git a/xs/src/TriangleMesh.hpp b/xs/src/TriangleMesh.hpp index 055817739..b1e9fea04 100644 --- a/xs/src/TriangleMesh.hpp +++ b/xs/src/TriangleMesh.hpp @@ -12,6 +12,7 @@ namespace Slic3r { class TriangleMesh; +class TriangleMeshSlicer; typedef std::vector TriangleMeshPtrs; class TriangleMesh @@ -30,8 +31,6 @@ class TriangleMesh void translate(float x, float y, float z); void align_to_origin(); void rotate(double angle, Point* center); - void slice(const std::vector &z, std::vector* layers); - void slice(const std::vector &z, std::vector* layers); TriangleMeshPtrs split() const; void merge(const TriangleMesh* mesh); void horizontal_projection(ExPolygons &retval) const; @@ -47,9 +46,10 @@ class TriangleMesh private: void require_shared_vertices(); + friend class TriangleMeshSlicer; }; -enum FacetEdgeType { feNone, feTop, feBottom }; +enum FacetEdgeType { feNone, feTop, feBottom, feHorizontal }; class IntersectionPoint : public Point { @@ -75,6 +75,26 @@ class IntersectionLine typedef std::vector IntersectionLines; typedef std::vector IntersectionLinePtrs; +class TriangleMeshSlicer +{ + public: + TriangleMesh* mesh; + TriangleMeshSlicer(TriangleMesh* _mesh); + ~TriangleMeshSlicer(); + void slice(const std::vector &z, std::vector* layers); + void slice(const std::vector &z, std::vector* layers); + void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const; + void cut(float z, TriangleMesh* upper, TriangleMesh* lower); + + private: + typedef std::vector< std::vector > t_facets_edges; + t_facets_edges facets_edges; + stl_vertex* v_scaled_shared; + void make_loops(std::vector &lines, Polygons* loops); + void make_expolygons(const Polygons &loops, ExPolygons* slices); + void make_expolygons(std::vector &lines, ExPolygons* slices); +}; + } #endif diff --git a/xs/src/admesh/connect.c b/xs/src/admesh/connect.c index 4582bf11f..d38a44a2b 100644 --- a/xs/src/admesh/connect.c +++ b/xs/src/admesh/connect.c @@ -52,7 +52,6 @@ static void stl_which_vertices_to_change(stl_file *stl, stl_hash_edge *edge_a, int *facet2, int *vertex2, stl_vertex *new_vertex1, stl_vertex *new_vertex2); static void stl_remove_degenerate(stl_file *stl, int facet); -static void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern int stl_check_normal_vector(stl_file *stl, int facet_num, int normal_fix_flag); static void stl_update_connects_remove_1(stl_file *stl, int facet_num); @@ -1100,7 +1099,7 @@ Try using a smaller tolerance or don't do a nearby check\n"); */ } } -static void +void stl_add_facet(stl_file *stl, stl_facet *new_facet) { stl->stats.facets_added += 1; diff --git a/xs/src/admesh/stl.h b/xs/src/admesh/stl.h index 507ec9d64..aa6340436 100644 --- a/xs/src/admesh/stl.h +++ b/xs/src/admesh/stl.h @@ -180,4 +180,5 @@ extern void stl_allocate(stl_file *stl); static void stl_read(stl_file *stl, int first_facet, int first); extern void stl_facet_stats(stl_file *stl, stl_facet facet, int first); extern void stl_reallocate(stl_file *stl); +extern void stl_add_facet(stl_file *stl, stl_facet *new_facet); extern void stl_get_size(stl_file *stl); diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index b8cb9a6f1..138163ec8 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -54,6 +54,7 @@ stl_initialize(stl_file *stl) stl->stats.number_of_parts = 0; stl->stats.original_num_facets = 0; stl->stats.number_of_facets = 0; + stl->stats.facets_malloced = 0; stl->stats.volume = -1.0; stl->neighbors_start = NULL; diff --git a/xs/src/myinit.h b/xs/src/myinit.h index 0156f64dc..43ed407bb 100644 --- a/xs/src/myinit.h +++ b/xs/src/myinit.h @@ -6,6 +6,7 @@ #undef seekdir #include #include +#include #ifdef SLIC3RXS extern "C" { @@ -23,6 +24,7 @@ extern "C" { #define PI 3.141592653589793238 #define scale_(val) (val / SCALING_FACTOR) #define unscale(val) (val * SCALING_FACTOR) +#define SCALED_EPSILON scale_(EPSILON) typedef long coord_t; typedef double coordf_t; diff --git a/xs/t/01_trianglemesh.t b/xs/t/01_trianglemesh.t index b5abc3658..9b1f2175b 100644 --- a/xs/t/01_trianglemesh.t +++ b/xs/t/01_trianglemesh.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 41; +use Test::More tests => 46; is Slic3r::TriangleMesh::hello_world(), 'Hello world!', 'hello world'; @@ -83,9 +83,42 @@ my $cube = { my $result = $m->slice(\@z); my $SCALING_FACTOR = 0.000001; for my $i (0..$#z) { - is scalar(@{$result->[$i]}), 1, 'number of returned polygons per layer'; + is scalar(@{$result->[$i]}), 1, "number of returned polygons per layer (z = " . $z[$i] . ")"; is $result->[$i][0]->area, 20*20/($SCALING_FACTOR**2), 'size of returned polygon'; } } +{ + my $m = Slic3r::TriangleMesh->new; + $m->ReadFromPerl( + [ [0,0,0],[0,0,20],[0,5,0],[0,5,20],[50,0,0],[50,0,20],[15,5,0],[35,5,0],[15,20,0],[50,5,0],[35,20,0],[15,5,10],[50,5,20],[35,5,10],[35,20,10],[15,20,10] ], + [ [0,1,2],[2,1,3],[1,0,4],[5,1,4],[0,2,4],[4,2,6],[7,6,8],[4,6,7],[9,4,7],[7,8,10],[2,3,6],[11,3,12],[7,12,9],[13,12,7],[6,3,11],[11,12,13],[3,1,5],[12,3,5],[5,4,9],[12,5,9],[13,7,10],[14,13,10],[8,15,10],[10,15,14],[6,11,8],[8,11,15],[15,11,13],[14,15,13] ], + ); + $m->repair; + my $slices = $m->slice([ 5, 10 ]); + is $slices->[0][0]->area, $slices->[1][0]->area, 'slicing a tangent plane includes its area'; +} + +{ + my $m = Slic3r::TriangleMesh->new; + $m->ReadFromPerl($cube->{vertices}, $cube->{facets}); + $m->repair; + { + my $upper = Slic3r::TriangleMesh->new; + my $lower = Slic3r::TriangleMesh->new; + $m->cut(0, $upper, $lower); + #$upper->repair; $lower->repair; + is $upper->facets_count, 10, 'upper mesh has all facets except those belonging to the slicing plane'; + is $lower->facets_count, 0, 'lower mesh has no facets'; + } + { + my $upper = Slic3r::TriangleMesh->new; + my $lower = Slic3r::TriangleMesh->new; + $m->cut(10, $upper, $lower); + #$upper->repair; $lower->repair; + is $upper->facets_count, 14, 'upper mesh has the right number of facets'; + is $lower->facets_count, 14, 'lower mesh has the right number of facets'; + } +} + __END__ diff --git a/xs/t/03_point.t b/xs/t/03_point.t index d8268602c..e1c88977f 100644 --- a/xs/t/03_point.t +++ b/xs/t/03_point.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 8; +use Test::More tests => 10; my $point = Slic3r::Point->new(10, 15); is_deeply [ @$point ], [10, 15], 'point roundtrip'; @@ -30,4 +30,20 @@ ok !$point->coincides_with($point2), 'coincides_with'; ok $nearest->coincides_with($point2), 'nearest_point'; } +{ + my $line = Slic3r::Line->new( + [18335846,18335845], + [18335846,1664160], + ); + $point = Slic3r::Point->new(1664161,18335848); + is $point->distance_to_line($line), 16671685, 'distance_to_line() does not overflow'; +} + +{ + my $p0 = Slic3r::Point->new(76975850,89989996); + my $p1 = Slic3r::Point->new(76989990,109989991); + my $p2 = Slic3r::Point->new(76989987,89989994); + ok $p0->ccw($p1, $p2) < 0, 'ccw() does not overflow'; +} + __END__ diff --git a/xs/t/05_surface.t b/xs/t/05_surface.t index 493a74b52..9c3e7dc95 100644 --- a/xs/t/05_surface.t +++ b/xs/t/05_surface.t @@ -4,7 +4,7 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 16; +use Test::More tests => 15; my $square = [ # ccw [100, 100], @@ -71,7 +71,6 @@ is $surface->extra_perimeters, 2, 'extra_perimeters'; ); my $collection = Slic3r::Surface::Collection->new(@surfaces); is scalar(@{$collection->group}), 2, 'group() returns correct number of groups'; - is scalar(@{$collection->group(1)}), 1, 'group() returns correct number of solid groups'; } __END__ diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 461f1cd89..1e59eb003 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -24,6 +24,7 @@ %code{% THIS->apply(*other, true); %}; std::vector get_keys() %code{% THIS->keys(&RETVAL); %}; + void erase(t_config_option_key opt_key); }; %name{Slic3r::Config::Print} class PrintConfig { @@ -146,14 +147,13 @@ print_config_def() throw "Unknown option type"; } (void)hv_stores( hv, "type", newSVpv(opt_type, 0) ); - (void)hv_stores( hv, "label", newSVpvn(optdef->label.c_str(), optdef->label.length()) ); + (void)hv_stores( hv, "label", newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) ); if (!optdef->full_label.empty()) - (void)hv_stores( hv, "full_label", newSVpvn(optdef->full_label.c_str(), optdef->full_label.length()) ); + (void)hv_stores( hv, "full_label", newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) ); (void)hv_stores( hv, "category", newSVpvn(optdef->category.c_str(), optdef->category.length()) ); - (void)hv_stores( hv, "tooltip", newSVpvn(optdef->tooltip.c_str(), optdef->tooltip.length()) ); - (void)hv_stores( hv, "sidetext", newSVpvn(optdef->sidetext.c_str(), optdef->sidetext.length()) ); + (void)hv_stores( hv, "tooltip", newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) ); + (void)hv_stores( hv, "sidetext", newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) ); (void)hv_stores( hv, "cli", newSVpvn(optdef->cli.c_str(), optdef->cli.length()) ); - (void)hv_stores( hv, "scope", newSVpvn(optdef->scope.c_str(), optdef->scope.length()) ); (void)hv_stores( hv, "ratio_over", newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) ); (void)hv_stores( hv, "multiline", newSViv(optdef->multiline ? 1 : 0) ); (void)hv_stores( hv, "full_width", newSViv(optdef->full_width ? 1 : 0) ); @@ -195,7 +195,7 @@ print_config_def() AV* av = newAV(); av_fill(av, optdef->enum_labels.size()-1); for (std::vector::iterator it = optdef->enum_labels.begin(); it != optdef->enum_labels.end(); ++it) - av_store(av, it - optdef->enum_labels.begin(), newSVpvn(it->c_str(), it->length())); + av_store(av, it - optdef->enum_labels.begin(), newSVpvn_utf8(it->c_str(), it->length(), true)); (void)hv_stores( hv, "labels", newRV_noinc((SV*)av) ); } diff --git a/xs/xsp/Point.xsp b/xs/xsp/Point.xsp index 453afd30a..79439d099 100644 --- a/xs/xsp/Point.xsp +++ b/xs/xsp/Point.xsp @@ -24,6 +24,9 @@ Point* nearest_point(Points points) %code{% const char* CLASS = "Slic3r::Point"; RETVAL = new Point(*(THIS->nearest_point(points))); %}; double distance_to(Point* point); + %name{distance_to_line} double distance_to(Line* line); + double ccw(Point* p1, Point* p2) + %code{% RETVAL = THIS->ccw(*p1, *p2); %}; %{ diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index bc2367169..466ada1b7 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -16,6 +16,7 @@ %code{% RETVAL = THIS->thickness_layers; %}; double area(); bool is_solid() const; + bool is_external() const; bool is_bridge() const; %{ diff --git a/xs/xsp/SurfaceCollection.xsp b/xs/xsp/SurfaceCollection.xsp index 4219bbe65..4bc2fc167 100644 --- a/xs/xsp/SurfaceCollection.xsp +++ b/xs/xsp/SurfaceCollection.xsp @@ -22,7 +22,7 @@ SurfaceCollection::new(...) RETVAL->surfaces.resize(items-1); for (unsigned int i = 1; i < items; i++) { // Note: a COPY of the input is stored - RETVAL->surfaces[i-1] = *(Surface *)SvIV((SV*)SvRV( ST(i) )); + RETVAL->surfaces[i-1].from_SV_check(ST(i)); } OUTPUT: RETVAL @@ -56,8 +56,9 @@ void SurfaceCollection::append(...) CODE: for (unsigned int i = 1; i < items; i++) { - // Note: a COPY of the input is stored - THIS->surfaces.push_back(*(Surface *)SvIV((SV*)SvRV( ST(i) ))); + Surface surface; + surface.from_SV_check( ST(i) ); + THIS->surfaces.push_back(surface); } void @@ -75,12 +76,11 @@ SurfaceCollection::set_surface_type(index, surface_type) THIS->surfaces[index].surface_type = surface_type; SV* -SurfaceCollection::group(merge_solid = false) - bool merge_solid +SurfaceCollection::group() CODE: // perform grouping std::vector groups; - THIS->group(&groups, merge_solid); + THIS->group(&groups); // build return arrayref AV* av = newAV(); diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index f026390c4..c3233fb5e 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -32,6 +32,8 @@ RETVAL = new BoundingBoxf3(); THIS->bounding_box(RETVAL); %}; + int facets_count() + %code{% RETVAL = THIS->stl.stats.number_of_facets; %}; %{ SV* @@ -137,8 +139,13 @@ SV* TriangleMesh::slice(z) std::vector* z CODE: + // convert doubles to floats + std::vector z_f(z->begin(), z->end()); + delete z; + std::vector layers; - THIS->slice(*z, &layers); + TriangleMeshSlicer mslicer(THIS); + mslicer.slice(z_f, &layers); AV* layers_av = newAV(); av_extend(layers_av, layers.size()-1); @@ -155,6 +162,15 @@ TriangleMesh::slice(z) OUTPUT: RETVAL +void +TriangleMesh::cut(z, upper, lower) + float z; + TriangleMesh* upper; + TriangleMesh* lower; + CODE: + TriangleMeshSlicer mslicer(THIS); + mslicer.cut(z, upper, lower); + std::vector TriangleMesh::bb3() CODE: