Merge branch 'master' into boost-medialaxis

Conflicts:
	lib/Slic3r/Layer/Region.pm
	xs/src/ExPolygon.cpp
	xs/src/Point.cpp
	xs/src/Point.hpp
	xs/src/TriangleMesh.cpp
	xs/t/01_trianglemesh.t
This commit is contained in:
Alessandro Ranellucci 2014-03-02 22:36:20 +01:00
commit eadffe4a9e
72 changed files with 1928 additions and 949 deletions

View File

@ -7,3 +7,5 @@ perl:
branches:
only:
- master
- stable

View File

@ -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';
}

View File

@ -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 {};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,10 +37,14 @@ sub write_file {
printf $fh qq{ <metadata type="cad">Slic3r %s</metadata>\n}, $Slic3r::VERSION;
for my $material_id (sort keys %{ $model->materials }) {
my $material = $model->materials->{$material_id};
printf $fh qq{ <material id="%d">\n}, $material_id;
printf $fh qq{ <material id="%s">\n}, $material_id;
for (keys %{$material->attributes}) {
printf $fh qq{ <metadata type=\"%s\">%s</metadata>\n}, $_, $material->attributes->{$_};
}
my $config = $material->config;
foreach my $opt_key (@{$config->get_keys}) {
printf $fh qq{ <metadata type=\"slic3r.%s\">%s</metadata>\n}, $opt_key, $config->serialize($opt_key);
}
printf $fh qq{ </material>\n};
}
my $instances = '';

View File

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

View File

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

View File

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

View File

@ -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) = @_;

View File

@ -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";
}

View File

@ -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) = @_;

View File

@ -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});

View File

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

View File

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

View File

@ -0,0 +1,99 @@
package Slic3r::GUI::Plater::OverrideSettingsPanel;
use strict;
use warnings;
use utf8;
use File::Basename qw(basename);
use Wx qw(:misc :sizer :button wxTAB_TRAVERSAL wxSUNKEN_BORDER wxBITMAP_TYPE_PNG);
use Wx::Event qw(EVT_BUTTON);
use base 'Wx::ScrolledWindow';
use constant ICON_MATERIAL => 0;
use constant ICON_SOLIDMESH => 1;
use constant ICON_MODIFIERMESH => 2;
sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
$self->{config} = $params{config}; # may be passed as undef
my @opt_keys = @{$params{opt_keys}};
$self->{sizer} = Wx::BoxSizer->new(wxVERTICAL);
# option selector
{
# get all options with object scope and sort them by category+label
my %settings = map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } @opt_keys;
$self->{options} = [ sort { $settings{$a} cmp $settings{$b} } keys %settings ];
my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [ map $settings{$_}, @{$self->{options}} ]);
# create the button
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG));
EVT_BUTTON($self, $btn, sub {
my $idx = $choice->GetSelection;
return if $idx == -1; # lack of selected item, can happen on Windows
my $opt_key = $self->{options}[$idx];
$self->{config}->apply(Slic3r::Config->new_from_defaults($opt_key));
$self->update_optgroup;
});
my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL);
$h_sizer->Add($choice, 1, wxEXPAND | wxALL, 0);
$h_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 10);
$self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxBOTTOM, 10);
}
$self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL);
$self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 0);
$self->SetSizer($self->{sizer});
$self->SetScrollbars(0, 1, 0, 1);
$self->update_optgroup;
return $self;
}
sub set_config {
my ($self, $config) = @_;
$self->{config} = $config;
$self->update_optgroup;
}
sub update_optgroup {
my $self = shift;
$self->{options_sizer}->Clear(1);
return if !defined $self->{config};
my %categories = ();
foreach my $opt_key (@{$self->{config}->get_keys}) {
my $category = $Slic3r::Config::Options->{$opt_key}{category};
$categories{$category} ||= [];
push @{$categories{$category}}, $opt_key;
}
foreach my $category (sort keys %categories) {
my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new(
parent => $self,
title => $category,
config => $self->{config},
options => [ sort @{$categories{$category}} ],
full_labels => 1,
extra_column => sub {
my ($line) = @_;
my ($opt_key) = @{$line->{options}}; # we assume that we have one option per line
my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG));
EVT_BUTTON($self, $btn, sub {
$self->{config}->erase($opt_key);
Slic3r::GUI->CallAfter(sub { $self->update_optgroup });
});
return $btn;
},
);
$self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 10);
}
$self->Layout;
}
1;

View File

@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -315,9 +315,11 @@ sub init_extruders {
}
}
# this value is not supposed to be compared with $layer->id
# since they have different semantics
sub layer_count {
my $self = shift;
return max(map { scalar @{$_->layers} } @{$self->objects});
return max(map $_->layer_count, @{$self->objects});
}
sub regions_count {
@ -444,15 +446,15 @@ sub process {
items => sub {
my @items = (); # [layer_id, region_id]
for my $region_id (0 .. ($self->regions_count-1)) {
push @items, map [$_, $region_id], 0..($object->layer_count-1);
push @items, map [$_, $region_id], 0..$#{$object->layers};
}
@items;
},
thread_cb => sub {
my $q = shift;
while (defined (my $obj_layer = $q->dequeue)) {
my ($layer_id, $region_id) = @$obj_layer;
my $layerm = $object->layers->[$layer_id]->regions->[$region_id];
my ($i, $region_id) = @$obj_layer;
my $layerm = $object->layers->[$i]->regions->[$region_id];
$layerm->fills->append( $object->fill_maker->make_fill($layerm) );
}
},
@ -475,12 +477,10 @@ sub process {
});
# make skirt
$status_cb->(88, "Generating skirt");
$status_cb->(88, "Generating skirt/brim");
$print_step->(STEP_SKIRT, sub {
$self->make_skirt;
});
$status_cb->(88, "Generating skirt");
$print_step->(STEP_BRIM, sub {
$self->make_brim; # must come after make_skirt
});
@ -559,29 +559,31 @@ EOF
($type eq 'contour' ? 'white' : 'black');
};
my @layers = sort { $a->print_z <=> $b->print_z }
map { @{$_->layers}, @{$_->support_layers} }
@{$self->objects};
my $layer_id = -1;
my @previous_layer_slices = ();
for my $layer_id (0..$self->layer_count-1) {
my @layers = map $_->layers->[$layer_id], @{$self->objects};
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, +(grep defined $_, @layers)[0]->slice_z;
for my $layer (@layers) {
$layer_id++;
# TODO: remove slic3r:z for raft layers
printf $fh qq{ <g id="layer%d" slic3r:z="%s">\n}, $layer_id, unscale($layer->slice_z);
my @current_layer_slices = ();
for my $obj_idx (0 .. $#{$self->objects}) {
my $layer = $self->objects->[$obj_idx]->layers->[$layer_id] or next;
# sort slices so that the outermost ones come first
my @slices = sort { $a->contour->contains_point($b->contour->first_point) ? 0 : 1 } @{$layer->slices};
foreach my $copy (@{$self->objects->[$obj_idx]->_shifted_copies}) {
foreach my $slice (@slices) {
my $expolygon = $slice->clone;
$expolygon->translate(@$copy);
$print_polygon->($expolygon->contour, 'contour');
$print_polygon->($_, 'hole') for @{$expolygon->holes};
push @current_layer_slices, $expolygon;
}
# sort slices so that the outermost ones come first
my @slices = sort { $a->contour->encloses_point($b->contour->[0]) ? 0 : 1 } @{$layer->slices};
foreach my $copy (@{$layer->object->copies}) {
foreach my $slice (@slices) {
my $expolygon = $slice->clone;
$expolygon->translate(@$copy);
$print_polygon->($expolygon->contour, 'contour');
$print_polygon->($_, 'hole') for @{$expolygon->holes};
push @current_layer_slices, $expolygon;
}
}
# generate support material
if ($self->has_support_material && $layer_id > 0) {
if ($self->has_support_material && $layer->id > 0) {
my (@supported_slices, @unsupported_slices) = ();
foreach my $expolygon (@current_layer_slices) {
my $intersection = intersection_ex(
@ -620,26 +622,50 @@ sub make_skirt {
$self->skirt->clear; # method must be idempotent
# First off we need to decide how tall the skirt must be.
# The skirt_height option from config is expressed in layers, but our
# object might have different layer heights, so we need to find the print_z
# of the highest layer involved.
# Note that unless skirt_height == -1 (which means it's printed on all layers)
# the actual skirt might not reach this $skirt_height_z value since the print
# order of objects on each layer is not guaranteed and will not generally
# include the thickest object first. It is just guaranteed that a skirt is
# prepended to the first 'n' layers (with 'n' = skirt_height).
# $skirt_height_z in this case is the highest possible skirt height for safety.
my $skirt_height_z = -1;
foreach my $object (@{$self->objects}) {
my $skirt_height = ($self->config->skirt_height == -1)
? scalar(@{$object->layers})
: min($self->config->skirt_height, scalar(@{$object->layers}));
my $highest_layer = $object->layers->[$skirt_height-1];
$skirt_height_z = max($skirt_height_z, $highest_layer->print_z);
}
# collect points from all layers contained in skirt height
my @points = ();
foreach my $obj_idx (0 .. $#{$self->objects}) {
my $object = $self->objects->[$obj_idx];
foreach my $object (@{$self->objects}) {
my @object_points = ();
# get skirt layers
my $skirt_height = ($self->config->skirt_height == -1)
? 1 + $#{$object->layers}
: 1 + min($self->config->skirt_height-1, $#{$object->layers}+1);
my @layer_points = (
map @$_, map @$_, map @{$object->layers->[$_]->slices}, 0..($skirt_height-1),
);
if (@{ $object->support_layers }) {
my @support_layers = map $object->support_layers->[$_], 0..min($self->config->skirt_height-1, $#{$object->support_layers});
push @layer_points,
(map @{$_->polyline}, map @{$_->support_fills}, grep $_->support_fills, @support_layers),
(map @{$_->polyline}, map @{$_->support_interface_fills}, grep $_->support_interface_fills, @support_layers);
# get object layers up to $skirt_height_z
foreach my $layer (@{$object->layers}) {
last if $layer->print_z > $skirt_height_z;
push @object_points, map @$_, map @$_, @{$layer->slices};
}
# get support layers up to $skirt_height_z
foreach my $layer (@{$object->support_layers}) {
last if $layer->print_z > $skirt_height_z;
push @object_points, map @{$_->polyline}, @{$layer->support_fills} if $layer->support_fills;
push @object_points, map @{$_->polyline}, @{$layer->support_interface_fills} if $layer->support_interface_fills;
}
# repeat points for each object copy
foreach my $copy (@{$object->_shifted_copies}) {
my @copy_points = map $_->clone, @object_points;
$_->translate(@$copy) for @copy_points;
push @points, @copy_points;
}
push @points, map move_points($_, @layer_points), @{$object->_shifted_copies};
}
return if @points < 3; # at least three points required for a convex hull
@ -977,11 +1003,15 @@ sub write_gcode {
$self->total_extruded_volume(0);
foreach my $extruder_id (@{$self->extruders}) {
my $extruder = $gcodegen->extruders->{$extruder_id};
$self->total_used_filament($self->total_used_filament + $extruder->absolute_E);
$self->total_extruded_volume($self->total_extruded_volume + $extruder->extruded_volume);
# the final retraction doesn't really count as "used filament"
my $used_filament = $extruder->absolute_E + $extruder->retract_length;
my $extruded_volume = $extruder->extruded_volume($used_filament);
printf $fh "; filament used = %.1fmm (%.1fcm3)\n",
$extruder->absolute_E, $extruder->extruded_volume/1000;
$used_filament, $extruded_volume/1000;
$self->total_used_filament($self->total_used_filament + $used_filament);
$self->total_extruded_volume($self->total_extruded_volume + $extruded_volume);
}
# append full config

View File

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

View File

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

View File

@ -50,7 +50,7 @@ sub output {
my $g = $svg->group(
style => {
'stroke-width' => 2,
'stroke-width' => 0,
'stroke' => $colour || 'black',
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
'fill-type' => $filltype,
@ -68,7 +68,7 @@ sub output {
my $g = $svg->group(
style => {
'stroke-width' => 2,
'stroke-width' => ($method eq 'polyline') ? 1 : 0,
'stroke' => $colour || 'black',
'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')),
},

File diff suppressed because one or more lines are too long

View File

@ -19,9 +19,4 @@ sub center {
return $self->bounding_box->center;
}
sub facets_count {
my $self = shift;
return $self->stats->{number_of_facets};
}
1;

26
t/config.t Normal file
View File

@ -0,0 +1,26 @@
use Test::More tests => 2;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('layer_height', 0.123);
$config->setenv;
is $ENV{SLIC3R_LAYER_HEIGHT}, '0.123', 'setenv';
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('perimeter_extrusion_width', '250%');
ok $config->validate, 'percent extrusion width is validated';
}
__END__

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use Test::More tests => 14;
use Test::More tests => 15;
use strict;
use warnings;
@ -112,7 +112,7 @@ use Slic3r::Test;
if ($layer_id <= $config->raft_layers) {
# this is a raft layer or the first object layer
my $line = Slic3r::Line->new_scale([ $self->X, $self->Y ], [ $info->{new_X}, $info->{new_Y} ]);
my @path = $line->grow(scale($config->support_material_extrusion_width/2));
my @path = @{$line->grow(scale($config->support_material_extrusion_width/2))};
if ($layer_id < $config->raft_layers) {
# this is a raft layer
push @raft, @path;
@ -129,4 +129,31 @@ use Slic3r::Test;
'first object layer is completely supported by raft';
}
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('skirts', 0);
$config->set('raft_layers', 2);
$config->set('layer_height', 0.35);
$config->set('first_layer_height', 0.3);
$config->set('nozzle_diameter', [0.5]);
$config->set('support_material_extruder', 2);
$config->set('support_material_interface_extruder', 2);
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my %raft_z = (); # z => 1
my $tool = undef;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd =~ /^T(\d+)/) {
$tool = $1;
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
if ($tool == $config->support_material_extruder-1) {
$raft_z{$self->Z} = 1;
}
}
});
is scalar(keys %raft_z), $config->raft_layers, 'correct number of raft layers is generated';
}
__END__

48
t/thin.t Normal file
View File

@ -0,0 +1,48 @@
use Test::More tests => 1;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
}
use Slic3r;
use List::Util qw(first);
use Slic3r::Geometry qw(epsilon);
use Slic3r::Test;
{
my $config = Slic3r::Config->new_from_defaults;
$config->set('layer_height', 0.2);
$config->set('first_layer_height', '100%');
$config->set('extrusion_width', 0.5);
$config->set('first_layer_extrusion_width', '200%'); # check this one too
$config->set('skirts', 0);
$config->set('thin_walls', 1);
my $print = Slic3r::Test::init_print('gt2_teeth', config => $config);
my %extrusion_paths = (); # Z => count of continuous extrusions
my $extruding = 0;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1') {
if ($info->{extruding} && $info->{dist_XY}) {
if (!$extruding) {
$extrusion_paths{$self->Z} //= 0;
$extrusion_paths{$self->Z}++;
}
$extruding = 1;
} else {
$extruding = 0;
}
}
});
ok !(first { $_ != 3 } values %extrusion_paths),
'no superfluous thin walls are generated for toothed profile';
}
__END__

View File

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

View File

@ -36,6 +36,7 @@ my $build = Module::Build::WithXSpp->new(
cstring
cstdlib
ostream
sstream
)]
);

View File

@ -111,12 +111,12 @@ ConfigBase::get(t_config_option_key opt_key) {
return newRV_noinc((SV*)av);
} else if (ConfigOptionString* optv = dynamic_cast<ConfigOptionString*>(opt)) {
// we don't serialize() because that would escape newlines
return newSVpvn(optv->value.c_str(), optv->value.length());
return newSVpvn_utf8(optv->value.c_str(), optv->value.length(), true);
} else if (ConfigOptionStrings* optv = dynamic_cast<ConfigOptionStrings*>(opt)) {
AV* av = newAV();
av_fill(av, optv->values.size()-1);
for (std::vector<std::string>::iterator it = optv->values.begin(); it != optv->values.end(); ++it)
av_store(av, it - optv->values.begin(), newSVpvn(it->c_str(), it->length()));
av_store(av, it - optv->values.begin(), newSVpvn_utf8(it->c_str(), it->length(), true));
return newRV_noinc((SV*)av);
} else if (ConfigOptionPoint* optv = dynamic_cast<ConfigOptionPoint*>(opt)) {
return optv->point.to_SV_pureperl();
@ -136,7 +136,7 @@ ConfigBase::get(t_config_option_key opt_key) {
return newRV_noinc((SV*)av);
} else {
std::string serialized = opt->serialize();
return newSVpvn(serialized.c_str(), serialized.length());
return newSVpvn_utf8(serialized.c_str(), serialized.length(), true);
}
}
@ -152,7 +152,7 @@ ConfigBase::get_at(t_config_option_key opt_key, size_t i) {
} else if (ConfigOptionStrings* optv = dynamic_cast<ConfigOptionStrings*>(opt)) {
// we don't serialize() because that would escape newlines
std::string val = optv->get_at(i);
return newSVpvn(val.c_str(), val.length());
return newSVpvn_utf8(val.c_str(), val.length(), true);
} else if (ConfigOptionPoints* optv = dynamic_cast<ConfigOptionPoints*>(opt)) {
return optv->get_at(i).to_SV_pureperl();
} else if (ConfigOptionBools* optv = dynamic_cast<ConfigOptionBools*>(opt)) {
@ -289,6 +289,11 @@ DynamicConfig::keys(t_config_option_keys *keys) {
keys->push_back(it->first);
}
void
DynamicConfig::erase(const t_config_option_key opt_key) {
this->options.erase(opt_key);
}
void
StaticConfig::keys(t_config_option_keys *keys) {
for (t_optiondef_map::const_iterator it = this->def->begin(); it != this->def->end(); ++it) {

View File

@ -1,9 +1,7 @@
#ifndef slic3r_Config_hpp_
#define slic3r_Config_hpp_
#include <myinit.h>
#include <map>
#include <sstream>
#include <climits>
#include <cstdio>
#include <cstdlib>
@ -11,6 +9,7 @@
#include <stdexcept>
#include <string>
#include <vector>
#include <myinit.h>
#include "Point.hpp"
namespace Slic3r {
@ -379,7 +378,6 @@ class ConfigOptionDef
std::string tooltip;
std::string sidetext;
std::string cli;
std::string scope;
t_config_option_key ratio_over;
bool multiline;
bool full_width;
@ -430,6 +428,7 @@ class DynamicConfig : public ConfigBase
~DynamicConfig();
ConfigOption* option(const t_config_option_key opt_key, bool create = false);
void keys(t_config_option_keys *keys);
void erase(const t_config_option_key opt_key);
private:
DynamicConfig(const DynamicConfig& other); // we disable this by making it private and unimplemented

View File

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

View File

@ -1,9 +1,19 @@
#include "Line.hpp"
#include "Polyline.hpp"
#include <algorithm>
#include <sstream>
namespace Slic3r {
std::string
Line::wkt() const
{
std::ostringstream ss;
ss << "LINESTRING(" << this->a.x << " " << this->a.y << ","
<< this->b.x << " " << this->b.y << ")";
return ss.str();
}
Line::operator Polyline() const
{
Polyline pl;
@ -88,6 +98,8 @@ void
Line::from_SV_check(SV* line_sv)
{
if (sv_isobject(line_sv) && (SvTYPE(SvRV(line_sv)) == SVt_PVMG)) {
if (!sv_isa(line_sv, "Slic3r::Line") && !sv_isa(line_sv, "Slic3r::Line::Ref"))
CONFESS("Not a valid Slic3r::Line object");
*this = *(Line*)SvIV((SV*)SvRV( line_sv ));
} else {
this->from_SV(line_sv);

View File

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

View File

@ -1,14 +1,24 @@
#include <cmath>
#include <sstream>
#include "Point.hpp"
#include "Line.hpp"
#include <cmath>
namespace Slic3r {
bool
Point::operator==(const Point& rhs) const {
Point::operator==(const Point& rhs) const
{
return this->coincides_with(rhs);
}
std::string
Point::wkt() const
{
std::ostringstream ss;
ss << "POINT(" << this->x << " " << this->y << ")";
return ss.str();
}
void
Point::scale(double factor)
{
@ -105,8 +115,8 @@ Point::distance_to(const Line &line) const
{
if (line.a.coincides_with(&line.b)) return this->distance_to(&line.a);
double n = (line.b.x - line.a.x) * (line.a.y - this->y)
- (line.a.x - this->x) * (line.b.y - line.a.y);
double n = (double)(line.b.x - line.a.x) * (double)(line.a.y - this->y)
- (double)(line.a.x - this->x) * (double)(line.b.y - line.a.y);
return std::abs(n) / line.length();
}
@ -121,7 +131,7 @@ Point::distance_to(const Line &line) const
double
Point::ccw(const Point &p1, const Point &p2) const
{
return (p2.x - p1.x)*(this->y - p1.y) - (p2.y - p1.y)*(this->x - p1.x);
return (double)(p2.x - p1.x)*(double)(this->y - p1.y) - (double)(p2.y - p1.y)*(double)(this->x - p1.x);
}
double
@ -174,6 +184,8 @@ void
Point::from_SV_check(SV* point_sv)
{
if (sv_isobject(point_sv) && (SvTYPE(SvRV(point_sv)) == SVt_PVMG)) {
if (!sv_isa(point_sv, "Slic3r::Point") && !sv_isa(point_sv, "Slic3r::Point::Ref"))
CONFESS("Not a valid Slic3r::Point object");
*this = *(Point*)SvIV((SV*)SvRV( point_sv ));
} else {
this->from_SV(point_sv);

View File

@ -5,6 +5,7 @@
#include <vector>
#include <math.h>
#include <boost/polygon/polygon.hpp>
#include <string>
namespace Slic3r {
@ -22,6 +23,7 @@ class Point
coord_t y;
explicit Point(coord_t _x = 0, coord_t _y = 0): x(_x), y(_y) {};
bool operator==(const Point& rhs) const;
std::string wkt() const;
void scale(double factor);
void translate(double x, double y);
void rotate(double angle, Point* center);

View File

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

View File

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

View File

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

View File

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

View File

@ -82,7 +82,6 @@ class PrintConfigDef
Options["bottom_solid_layers"].category = "Layers and Perimeters";
Options["bottom_solid_layers"].tooltip = "Number of solid layers to generate on bottom surfaces.";
Options["bottom_solid_layers"].cli = "bottom-solid-layers=i";
Options["bottom_solid_layers"].scope = "object";
Options["bottom_solid_layers"].full_label = "Bottom solid layers";
Options["bridge_acceleration"].type = coFloat;
@ -171,7 +170,6 @@ class PrintConfigDef
Options["extra_perimeters"].category = "Layers and Perimeters";
Options["extra_perimeters"].tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls.";
Options["extra_perimeters"].cli = "extra-perimeters!";
Options["extra_perimeters"].scope = "object";
Options["extruder"].type = coInt;
Options["extruder"].label = "Extruder";
@ -211,6 +209,7 @@ class PrintConfigDef
Options["extrusion_width"].type = coFloatOrPercent;
Options["extrusion_width"].label = "Default extrusion width";
Options["extrusion_width"].category = "Extrusion Width";
Options["extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width. If left to zero, Slic3r calculates a width automatically. If expressed as percentage (for example: 230%) it will be computed over layer height.";
Options["extrusion_width"].sidetext = "mm or % (leave 0 for auto)";
Options["extrusion_width"].cli = "extrusion-width=s";
@ -236,6 +235,7 @@ class PrintConfigDef
Options["fill_angle"].type = coInt;
Options["fill_angle"].label = "Fill angle";
Options["fill_angle"].category = "Infill";
Options["fill_angle"].tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them.";
Options["fill_angle"].sidetext = "°";
Options["fill_angle"].cli = "fill-angle=i";
@ -246,14 +246,12 @@ class PrintConfigDef
Options["fill_density"].category = "Infill";
Options["fill_density"].tooltip = "Density of internal infill, expressed in the range 0 - 1.";
Options["fill_density"].cli = "fill-density=f";
Options["fill_density"].scope = "object";
Options["fill_pattern"].type = coEnum;
Options["fill_pattern"].label = "Fill pattern";
Options["fill_pattern"].category = "Infill";
Options["fill_pattern"].tooltip = "Fill pattern for general low-density infill.";
Options["fill_pattern"].cli = "fill-pattern=s";
Options["fill_pattern"].scope = "object";
Options["fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
Options["fill_pattern"].enum_values.push_back("rectilinear");
Options["fill_pattern"].enum_values.push_back("line");
@ -285,12 +283,14 @@ class PrintConfigDef
Options["first_layer_extrusion_width"].type = coFloatOrPercent;
Options["first_layer_extrusion_width"].label = "First layer";
Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) if will be computed over layer height.";
Options["first_layer_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) if will be computed over first layer height.";
Options["first_layer_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
Options["first_layer_extrusion_width"].cli = "first-layer-extrusion-width=s";
Options["first_layer_extrusion_width"].ratio_over = "first_layer_height";
Options["first_layer_height"].type = coFloatOrPercent;
Options["first_layer_height"].label = "First layer height";
Options["first_layer_height"].category = "Layers and Perimeters";
Options["first_layer_height"].tooltip = "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height.";
Options["first_layer_height"].sidetext = "mm or %";
Options["first_layer_height"].cli = "first-layer-height=s";
@ -360,17 +360,18 @@ class PrintConfigDef
Options["infill_every_layers"].tooltip = "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy.";
Options["infill_every_layers"].sidetext = "layers";
Options["infill_every_layers"].cli = "infill-every-layers=i";
Options["infill_every_layers"].scope = "object";
Options["infill_every_layers"].full_label = "Combine infill every n layers";
Options["infill_every_layers"].min = 1;
Options["infill_extruder"].type = coInt;
Options["infill_extruder"].label = "Infill extruder";
Options["infill_extruder"].category = "Extruders";
Options["infill_extruder"].tooltip = "The extruder to use when printing infill.";
Options["infill_extruder"].cli = "infill-extruder=i";
Options["infill_extrusion_width"].type = coFloatOrPercent;
Options["infill_extrusion_width"].label = "Infill";
Options["infill_extrusion_width"].category = "Extrusion Width";
Options["infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) if will be computed over layer height.";
Options["infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
Options["infill_extrusion_width"].cli = "infill-extrusion-width=s";
@ -385,7 +386,6 @@ class PrintConfigDef
Options["infill_only_where_needed"].category = "Infill";
Options["infill_only_where_needed"].tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material).";
Options["infill_only_where_needed"].cli = "infill-only-where-needed!";
Options["infill_only_where_needed"].scope = "object";
Options["infill_speed"].type = coFloat;
Options["infill_speed"].label = "Infill";
@ -405,6 +405,7 @@ class PrintConfigDef
Options["layer_height"].type = coFloat;
Options["layer_height"].label = "Layer height";
Options["layer_height"].category = "Layers and Perimeters";
Options["layer_height"].tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print.";
Options["layer_height"].sidetext = "mm";
Options["layer_height"].cli = "layer-height=f";
@ -472,7 +473,6 @@ class PrintConfigDef
Options["overhangs"].category = "Layers and Perimeters";
Options["overhangs"].tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan.";
Options["overhangs"].cli = "overhangs!";
Options["overhangs"].scope = "object";
Options["perimeter_acceleration"].type = coFloat;
Options["perimeter_acceleration"].label = "Perimeters";
@ -482,12 +482,14 @@ class PrintConfigDef
Options["perimeter_extruder"].type = coInt;
Options["perimeter_extruder"].label = "Perimeter extruder";
Options["perimeter_extruder"].category = "Extruders";
Options["perimeter_extruder"].tooltip = "The extruder to use when printing perimeters.";
Options["perimeter_extruder"].cli = "perimeter-extruder=i";
Options["perimeter_extruder"].aliases.push_back("perimeters_extruder");
Options["perimeter_extrusion_width"].type = coFloatOrPercent;
Options["perimeter_extrusion_width"].label = "Perimeters";
Options["perimeter_extrusion_width"].category = "Extrusion Width";
Options["perimeter_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If expressed as percentage (for example 90%) if will be computed over layer height.";
Options["perimeter_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
Options["perimeter_extrusion_width"].cli = "perimeter-extrusion-width=s";
@ -505,7 +507,6 @@ class PrintConfigDef
Options["perimeters"].category = "Layers and Perimeters";
Options["perimeters"].tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled.";
Options["perimeters"].cli = "perimeters=i";
Options["perimeters"].scope = "object";
Options["perimeters"].aliases.push_back("perimeter_offsets");
Options["post_process"].type = coStrings;
@ -528,7 +529,6 @@ class PrintConfigDef
Options["raft_layers"].tooltip = "The object will be raised by this number of layers, and support material will be generated under it.";
Options["raft_layers"].sidetext = "layers";
Options["raft_layers"].cli = "raft-layers=i";
Options["raft_layers"].scope = "object";
Options["randomize_start"].type = coBool;
Options["randomize_start"].label = "Randomize starting points";
@ -627,7 +627,6 @@ class PrintConfigDef
Options["solid_fill_pattern"].category = "Infill";
Options["solid_fill_pattern"].tooltip = "Fill pattern for top/bottom infill.";
Options["solid_fill_pattern"].cli = "solid-fill-pattern=s";
Options["solid_fill_pattern"].scope = "object";
Options["solid_fill_pattern"].enum_keys_map = ConfigOptionEnum<InfillPattern>::get_enum_values();
Options["solid_fill_pattern"].enum_values.push_back("rectilinear");
Options["solid_fill_pattern"].enum_values.push_back("concentric");
@ -646,7 +645,6 @@ class PrintConfigDef
Options["solid_infill_below_area"].tooltip = "Force solid infill for regions having a smaller area than the specified threshold.";
Options["solid_infill_below_area"].sidetext = "mm²";
Options["solid_infill_below_area"].cli = "solid-infill-below-area=f";
Options["solid_infill_below_area"].scope = "object";
Options["solid_infill_every_layers"].type = coInt;
Options["solid_infill_every_layers"].label = "Solid infill every";
@ -654,11 +652,11 @@ class PrintConfigDef
Options["solid_infill_every_layers"].tooltip = "This feature allows to force a solid layer every given number of layers. Zero to disable.";
Options["solid_infill_every_layers"].sidetext = "layers";
Options["solid_infill_every_layers"].cli = "solid-infill-every-layers=i";
Options["solid_infill_every_layers"].scope = "object";
Options["solid_infill_every_layers"].min = 0;
Options["solid_infill_extrusion_width"].type = coFloatOrPercent;
Options["solid_infill_extrusion_width"].label = "Solid infill";
Options["solid_infill_extrusion_width"].category = "Extrusion Width";
Options["solid_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If expressed as percentage (for example 90%) if will be computed over layer height.";
Options["solid_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
Options["solid_infill_extrusion_width"].cli = "solid-infill-extrusion-width=s";
@ -714,7 +712,6 @@ class PrintConfigDef
Options["support_material"].category = "Support material";
Options["support_material"].tooltip = "Enable support material generation.";
Options["support_material"].cli = "support-material!";
Options["support_material"].scope = "object";
Options["support_material_angle"].type = coInt;
Options["support_material_angle"].label = "Pattern angle";
@ -722,7 +719,6 @@ class PrintConfigDef
Options["support_material_angle"].tooltip = "Use this setting to rotate the support material pattern on the horizontal plane.";
Options["support_material_angle"].sidetext = "°";
Options["support_material_angle"].cli = "support-material-angle=i";
Options["support_material_angle"].scope = "object";
Options["support_material_enforce_layers"].type = coInt;
Options["support_material_enforce_layers"].label = "Enforce support for the first";
@ -730,22 +726,24 @@ class PrintConfigDef
Options["support_material_enforce_layers"].tooltip = "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate.";
Options["support_material_enforce_layers"].sidetext = "layers";
Options["support_material_enforce_layers"].cli = "support-material-enforce-layers=f";
Options["support_material_enforce_layers"].scope = "object";
Options["support_material_enforce_layers"].full_label = "Enforce support for the first n layers";
Options["support_material_extruder"].type = coInt;
Options["support_material_extruder"].label = "Support material extruder";
Options["support_material_extruder"].category = "Extruders";
Options["support_material_extruder"].tooltip = "The extruder to use when printing support material. This affects brim and raft too.";
Options["support_material_extruder"].cli = "support-material-extruder=i";
Options["support_material_extrusion_width"].type = coFloatOrPercent;
Options["support_material_extrusion_width"].label = "Support material";
Options["support_material_extrusion_width"].category = "Extrusion Width";
Options["support_material_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) if will be computed over layer height.";
Options["support_material_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
Options["support_material_extrusion_width"].cli = "support-material-extrusion-width=s";
Options["support_material_interface_extruder"].type = coInt;
Options["support_material_interface_extruder"].label = "Support material interface extruder";
Options["support_material_interface_extruder"].category = "Extruders";
Options["support_material_interface_extruder"].tooltip = "The extruder to use when printing support material interface. This affects raft too.";
Options["support_material_interface_extruder"].cli = "support-material-interface-extruder=i";
@ -755,7 +753,6 @@ class PrintConfigDef
Options["support_material_interface_layers"].tooltip = "Number of interface layers to insert between the object(s) and support material.";
Options["support_material_interface_layers"].sidetext = "layers";
Options["support_material_interface_layers"].cli = "support-material-interface-layers=i";
Options["support_material_interface_layers"].scope = "object";
Options["support_material_interface_spacing"].type = coFloat;
Options["support_material_interface_spacing"].label = "Interface pattern spacing";
@ -763,14 +760,12 @@ class PrintConfigDef
Options["support_material_interface_spacing"].tooltip = "Spacing between interface lines. Set zero to get a solid interface.";
Options["support_material_interface_spacing"].sidetext = "mm";
Options["support_material_interface_spacing"].cli = "support-material-interface-spacing=f";
Options["support_material_interface_spacing"].scope = "object";
Options["support_material_pattern"].type = coEnum;
Options["support_material_pattern"].label = "Pattern";
Options["support_material_pattern"].category = "Support material";
Options["support_material_pattern"].tooltip = "Pattern used to generate support material.";
Options["support_material_pattern"].cli = "support-material-pattern=s";
Options["support_material_pattern"].scope = "object";
Options["support_material_pattern"].enum_keys_map = ConfigOptionEnum<SupportMaterialPattern>::get_enum_values();
Options["support_material_pattern"].enum_values.push_back("rectilinear");
Options["support_material_pattern"].enum_values.push_back("rectilinear-grid");
@ -787,10 +782,10 @@ class PrintConfigDef
Options["support_material_spacing"].tooltip = "Spacing between support material lines.";
Options["support_material_spacing"].sidetext = "mm";
Options["support_material_spacing"].cli = "support-material-spacing=f";
Options["support_material_spacing"].scope = "object";
Options["support_material_speed"].type = coFloat;
Options["support_material_speed"].label = "Support material";
Options["support_material_speed"].category = "Support material";
Options["support_material_speed"].tooltip = "Speed for printing support material.";
Options["support_material_speed"].sidetext = "mm/s";
Options["support_material_speed"].cli = "support-material-speed=f";
@ -801,7 +796,6 @@ class PrintConfigDef
Options["support_material_threshold"].tooltip = "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended).";
Options["support_material_threshold"].sidetext = "°";
Options["support_material_threshold"].cli = "support-material-threshold=i";
Options["support_material_threshold"].scope = "object";
Options["temperature"].type = coInts;
Options["temperature"].label = "Other layers";
@ -816,7 +810,6 @@ class PrintConfigDef
Options["thin_walls"].category = "Layers and Perimeters";
Options["thin_walls"].tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace).";
Options["thin_walls"].cli = "thin-walls!";
Options["thin_walls"].scope = "object";
Options["threads"].type = coInt;
Options["threads"].label = "Threads";
@ -837,6 +830,7 @@ class PrintConfigDef
Options["top_infill_extrusion_width"].type = coFloatOrPercent;
Options["top_infill_extrusion_width"].label = "Top solid infill";
Options["top_infill_extrusion_width"].category = "Extrusion Width";
Options["top_infill_extrusion_width"].tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) if will be computed over layer height.";
Options["top_infill_extrusion_width"].sidetext = "mm or % (leave 0 for default)";
Options["top_infill_extrusion_width"].cli = "top-infill-extrusion-width=s";
@ -853,7 +847,6 @@ class PrintConfigDef
Options["top_solid_layers"].category = "Layers and Perimeters";
Options["top_solid_layers"].tooltip = "Number of solid layers to generate on top surfaces.";
Options["top_solid_layers"].cli = "top-solid-layers=i";
Options["top_solid_layers"].scope = "object";
Options["top_solid_layers"].full_label = "Top solid layers";
Options["travel_speed"].type = coFloat;
@ -981,7 +974,6 @@ class PrintRegionConfig : public virtual StaticConfig
ConfigOptionFloat solid_infill_below_area;
ConfigOptionFloatOrPercent solid_infill_extrusion_width;
ConfigOptionInt solid_infill_every_layers;
ConfigOptionInt solid_layers;
ConfigOptionBool thin_walls;
ConfigOptionFloatOrPercent top_infill_extrusion_width;
ConfigOptionInt top_solid_layers;
@ -1029,7 +1021,6 @@ class PrintRegionConfig : public virtual StaticConfig
if (opt_key == "solid_infill_below_area") return &this->solid_infill_below_area;
if (opt_key == "solid_infill_extrusion_width") return &this->solid_infill_extrusion_width;
if (opt_key == "solid_infill_every_layers") return &this->solid_infill_every_layers;
if (opt_key == "solid_layers") return &this->solid_layers;
if (opt_key == "thin_walls") return &this->thin_walls;
if (opt_key == "top_infill_extrusion_width") return &this->top_infill_extrusion_width;
if (opt_key == "top_solid_layers") return &this->top_solid_layers;

View File

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

View File

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

View File

@ -21,14 +21,14 @@ SurfaceCollection::simplify(double tolerance)
/* group surfaces by common properties */
void
SurfaceCollection::group(std::vector<SurfacesPtr> *retval, bool merge_solid)
SurfaceCollection::group(std::vector<SurfacesPtr> *retval)
{
for (Surfaces::iterator it = this->surfaces.begin(); it != this->surfaces.end(); ++it) {
// find a group with the same properties
SurfacesPtr* group = NULL;
for (std::vector<SurfacesPtr>::iterator git = retval->begin(); git != retval->end(); ++git) {
Surface* gkey = git->front();
if ((gkey->surface_type == it->surface_type || (merge_solid && gkey->is_solid() && it->is_solid()))
if ( gkey->surface_type == it->surface_type
&& gkey->thickness == it->thickness
&& gkey->thickness_layers == it->thickness_layers
&& gkey->bridge_angle == it->bridge_angle) {

View File

@ -11,7 +11,7 @@ class SurfaceCollection
public:
Surfaces surfaces;
void simplify(double tolerance);
void group(std::vector<SurfacesPtr> *retval, bool merge_solid = false);
void group(std::vector<SurfacesPtr> *retval);
};
}

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
namespace Slic3r {
class TriangleMesh;
class TriangleMeshSlicer;
typedef std::vector<TriangleMesh*> TriangleMeshPtrs;
class TriangleMesh
@ -30,8 +31,6 @@ class TriangleMesh
void translate(float x, float y, float z);
void align_to_origin();
void rotate(double angle, Point* center);
void slice(const std::vector<double> &z, std::vector<Polygons>* layers);
void slice(const std::vector<double> &z, std::vector<ExPolygons>* layers);
TriangleMeshPtrs split() const;
void merge(const TriangleMesh* mesh);
void horizontal_projection(ExPolygons &retval) const;
@ -47,9 +46,10 @@ class TriangleMesh
private:
void require_shared_vertices();
friend class TriangleMeshSlicer;
};
enum FacetEdgeType { feNone, feTop, feBottom };
enum FacetEdgeType { feNone, feTop, feBottom, feHorizontal };
class IntersectionPoint : public Point
{
@ -75,6 +75,26 @@ class IntersectionLine
typedef std::vector<IntersectionLine> IntersectionLines;
typedef std::vector<IntersectionLine*> IntersectionLinePtrs;
class TriangleMeshSlicer
{
public:
TriangleMesh* mesh;
TriangleMeshSlicer(TriangleMesh* _mesh);
~TriangleMeshSlicer();
void slice(const std::vector<float> &z, std::vector<Polygons>* layers);
void slice(const std::vector<float> &z, std::vector<ExPolygons>* layers);
void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector<IntersectionLine>* lines) const;
void cut(float z, TriangleMesh* upper, TriangleMesh* lower);
private:
typedef std::vector< std::vector<int> > t_facets_edges;
t_facets_edges facets_edges;
stl_vertex* v_scaled_shared;
void make_loops(std::vector<IntersectionLine> &lines, Polygons* loops);
void make_expolygons(const Polygons &loops, ExPolygons* slices);
void make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices);
};
}
#endif

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@
#undef seekdir
#include <ostream>
#include <iostream>
#include <sstream>
#ifdef SLIC3RXS
extern "C" {
@ -23,6 +24,7 @@ extern "C" {
#define PI 3.141592653589793238
#define scale_(val) (val / SCALING_FACTOR)
#define unscale(val) (val * SCALING_FACTOR)
#define SCALED_EPSILON scale_(EPSILON)
typedef long coord_t;
typedef double coordf_t;

View File

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

View File

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

View File

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

View File

@ -24,6 +24,7 @@
%code{% THIS->apply(*other, true); %};
std::vector<std::string> get_keys()
%code{% THIS->keys(&RETVAL); %};
void erase(t_config_option_key opt_key);
};
%name{Slic3r::Config::Print} class PrintConfig {
@ -146,14 +147,13 @@ print_config_def()
throw "Unknown option type";
}
(void)hv_stores( hv, "type", newSVpv(opt_type, 0) );
(void)hv_stores( hv, "label", newSVpvn(optdef->label.c_str(), optdef->label.length()) );
(void)hv_stores( hv, "label", newSVpvn_utf8(optdef->label.c_str(), optdef->label.length(), true) );
if (!optdef->full_label.empty())
(void)hv_stores( hv, "full_label", newSVpvn(optdef->full_label.c_str(), optdef->full_label.length()) );
(void)hv_stores( hv, "full_label", newSVpvn_utf8(optdef->full_label.c_str(), optdef->full_label.length(), true) );
(void)hv_stores( hv, "category", newSVpvn(optdef->category.c_str(), optdef->category.length()) );
(void)hv_stores( hv, "tooltip", newSVpvn(optdef->tooltip.c_str(), optdef->tooltip.length()) );
(void)hv_stores( hv, "sidetext", newSVpvn(optdef->sidetext.c_str(), optdef->sidetext.length()) );
(void)hv_stores( hv, "tooltip", newSVpvn_utf8(optdef->tooltip.c_str(), optdef->tooltip.length(), true) );
(void)hv_stores( hv, "sidetext", newSVpvn_utf8(optdef->sidetext.c_str(), optdef->sidetext.length(), true) );
(void)hv_stores( hv, "cli", newSVpvn(optdef->cli.c_str(), optdef->cli.length()) );
(void)hv_stores( hv, "scope", newSVpvn(optdef->scope.c_str(), optdef->scope.length()) );
(void)hv_stores( hv, "ratio_over", newSVpvn(optdef->ratio_over.c_str(), optdef->ratio_over.length()) );
(void)hv_stores( hv, "multiline", newSViv(optdef->multiline ? 1 : 0) );
(void)hv_stores( hv, "full_width", newSViv(optdef->full_width ? 1 : 0) );
@ -195,7 +195,7 @@ print_config_def()
AV* av = newAV();
av_fill(av, optdef->enum_labels.size()-1);
for (std::vector<std::string>::iterator it = optdef->enum_labels.begin(); it != optdef->enum_labels.end(); ++it)
av_store(av, it - optdef->enum_labels.begin(), newSVpvn(it->c_str(), it->length()));
av_store(av, it - optdef->enum_labels.begin(), newSVpvn_utf8(it->c_str(), it->length(), true));
(void)hv_stores( hv, "labels", newRV_noinc((SV*)av) );
}

View File

@ -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); %};
%{

View File

@ -16,6 +16,7 @@
%code{% RETVAL = THIS->thickness_layers; %};
double area();
bool is_solid() const;
bool is_external() const;
bool is_bridge() const;
%{

View File

@ -22,7 +22,7 @@ SurfaceCollection::new(...)
RETVAL->surfaces.resize(items-1);
for (unsigned int i = 1; i < items; i++) {
// Note: a COPY of the input is stored
RETVAL->surfaces[i-1] = *(Surface *)SvIV((SV*)SvRV( ST(i) ));
RETVAL->surfaces[i-1].from_SV_check(ST(i));
}
OUTPUT:
RETVAL
@ -56,8 +56,9 @@ void
SurfaceCollection::append(...)
CODE:
for (unsigned int i = 1; i < items; i++) {
// Note: a COPY of the input is stored
THIS->surfaces.push_back(*(Surface *)SvIV((SV*)SvRV( ST(i) )));
Surface surface;
surface.from_SV_check( ST(i) );
THIS->surfaces.push_back(surface);
}
void
@ -75,12 +76,11 @@ SurfaceCollection::set_surface_type(index, surface_type)
THIS->surfaces[index].surface_type = surface_type;
SV*
SurfaceCollection::group(merge_solid = false)
bool merge_solid
SurfaceCollection::group()
CODE:
// perform grouping
std::vector<SurfacesPtr> groups;
THIS->group(&groups, merge_solid);
THIS->group(&groups);
// build return arrayref
AV* av = newAV();

View File

@ -32,6 +32,8 @@
RETVAL = new BoundingBoxf3();
THIS->bounding_box(RETVAL);
%};
int facets_count()
%code{% RETVAL = THIS->stl.stats.number_of_facets; %};
%{
SV*
@ -137,8 +139,13 @@ SV*
TriangleMesh::slice(z)
std::vector<double>* z
CODE:
// convert doubles to floats
std::vector<float> z_f(z->begin(), z->end());
delete z;
std::vector<ExPolygons> layers;
THIS->slice(*z, &layers);
TriangleMeshSlicer mslicer(THIS);
mslicer.slice(z_f, &layers);
AV* layers_av = newAV();
av_extend(layers_av, layers.size()-1);
@ -155,6 +162,15 @@ TriangleMesh::slice(z)
OUTPUT:
RETVAL
void
TriangleMesh::cut(z, upper, lower)
float z;
TriangleMesh* upper;
TriangleMesh* lower;
CODE:
TriangleMeshSlicer mslicer(THIS);
mslicer.cut(z, upper, lower);
std::vector<double>
TriangleMesh::bb3()
CODE: