Merge branch 'xs-model'

Conflicts:
	lib/Slic3r/Model.pm
This commit is contained in:
Alessandro Ranellucci 2013-12-13 12:22:17 +01:00
commit 21ca1901c1
24 changed files with 690 additions and 693 deletions

View File

@ -159,4 +159,8 @@ sub open {
return CORE::open $$fh, $mode, encode_path($filename);
}
# this package declaration prevents an ugly fatal warning to be emitted when
# spawning a new thread
package GLUquadricObjPtr;
1;

View File

@ -120,7 +120,7 @@ sub end_document {
foreach my $instance (@{ $self->{_instances}{$object_id} }) {
$self->{_model}->objects->[$new_object_id]->add_instance(
rotation => $instance->{rz} || 0,
offset => Slic3r::Point->new($instance->{deltax} || 0, $instance->{deltay} || 0),
offset => [ $instance->{deltax} || 0, $instance->{deltay} || 0 ],
);
}
}

View File

@ -12,7 +12,7 @@ use Wx qw(:bitmap :brush :button :cursor :dialog :filedialog :font :keycode :ico
use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE);
use base 'Wx::Panel';
use constant TB_LOAD => &Wx::NewId;
use constant TB_ADD => &Wx::NewId;
use constant TB_REMOVE => &Wx::NewId;
use constant TB_RESET => &Wx::NewId;
use constant TB_ARRANGE => &Wx::NewId;
@ -48,8 +48,8 @@ sub new {
$self->{config} = Slic3r::Config->new_from_defaults(qw(
bed_size print_center complete_objects extruder_clearance_radius skirts skirt_distance
));
$self->{model} = Slic3r::Model->new;
$self->{objects} = [];
$self->{selected_objects} = [];
$self->{canvas} = Wx::Panel->new($self, -1, wxDefaultPosition, CANVAS_SIZE, wxTAB_TRAVERSAL);
$self->{canvas}->SetBackgroundColour(Wx::wxWHITE);
@ -69,7 +69,7 @@ sub new {
if (!&Wx::wxMSW) {
Wx::ToolTip::Enable(1);
$self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL);
$self->{htoolbar}->AddTool(TB_LOAD, "Add…", Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ADD, "Add…", Wx::Bitmap->new("$Slic3r::var/brick_add.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_REMOVE, "Delete", Wx::Bitmap->new("$Slic3r::var/brick_delete.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_RESET, "Delete All", Wx::Bitmap->new("$Slic3r::var/cross.png", wxBITMAP_TYPE_PNG), '');
$self->{htoolbar}->AddTool(TB_ARRANGE, "Arrange", Wx::Bitmap->new("$Slic3r::var/bricks.png", wxBITMAP_TYPE_PNG), '');
@ -159,7 +159,7 @@ sub new {
EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl);
if ($self->{htoolbar}) {
EVT_TOOL($self, TB_LOAD, \&load);
EVT_TOOL($self, TB_ADD, \&add);
EVT_TOOL($self, TB_REMOVE, sub { $self->remove() }); # explicitly pass no argument to remove
EVT_TOOL($self, TB_RESET, \&reset);
EVT_TOOL($self, TB_ARRANGE, \&arrange);
@ -173,7 +173,7 @@ sub new {
EVT_TOOL($self, TB_VIEW, sub { $_[0]->object_preview_dialog });
EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog });
} else {
EVT_BUTTON($self, $self->{btn_load}, \&load);
EVT_BUTTON($self, $self->{btn_add}, \&add);
EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove
EVT_BUTTON($self, $self->{btn_reset}, \&reset);
EVT_BUTTON($self, $self->{btn_arrange}, \&arrange);
@ -357,7 +357,7 @@ sub filament_presets {
return map $_->GetSelection, @{ $self->{preset_choosers}{filament} };
}
sub load {
sub add {
my $self = shift;
my $dir = $Slic3r::GUI::Settings->{recent}{skein_directory} || $Slic3r::GUI::Settings->{recent}{config_directory} || '';
@ -375,7 +375,6 @@ sub load_file {
my $self = shift;
my ($input_file) = @_;
my $basename = basename($input_file);
$Slic3r::GUI::Settings->{recent}{skein_directory} = dirname($input_file);
Slic3r::GUI->save_settings;
@ -384,31 +383,32 @@ sub load_file {
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
my $model = Slic3r::Model->read_from_file($input_file);
for my $i (0 .. $#{$model->objects}) {
my $object = Slic3r::GUI::Plater::Object->new(
name => $basename,
input_file => $input_file,
input_file_object_id => $i,
model => $model,
model_object_idx => $i,
mesh_stats => $model->objects->[$i]->mesh_stats, # so that we can free model
instances => [
$model->objects->[$i]->instances
? (map $_->offset, @{$model->objects->[$i]->instances})
: Slic3r::Point->new(0,0),
],
);
# we only consider the rotation of the first instance for now
$object->rotate($model->objects->[$i]->instances->[0]->rotation)
if $model->objects->[$i]->instances;
push @{ $self->{objects} }, $object;
$self->object_loaded($#{ $self->{objects} }, no_arrange => (@{$object->instances} > 1));
}
$self->load_model_object($_) for @{$model->objects};
$process_dialog->Destroy;
$self->statusbar->SetStatusText("Loaded $basename");
$self->statusbar->SetStatusText("Loaded " . basename($input_file));
}
sub load_model_object {
my ($self, $model_object) = @_;
my $o = $self->{model}->add_object($model_object);
push @{ $self->{objects} }, Slic3r::GUI::Plater::Object->new(
name => basename($model_object->input_file),
);
my $need_arrange = 0;
if (!defined $model_object->instances) {
# if object has no defined position(s) we need to rearrange everything after loading
$need_arrange = 1;
# add a default instance and center object around origin
$o->center_around_origin;
$o->add_instance(offset => [30,30]);
}
$self->object_loaded($#{ $self->{objects} }, no_arrange => !$need_arrange);
}
sub object_loaded {
@ -416,15 +416,17 @@ sub object_loaded {
my ($obj_idx, %params) = @_;
my $object = $self->{objects}[$obj_idx];
my $model_object = $self->{model}->objects->[$obj_idx];
$self->{list}->InsertStringItem($obj_idx, $object->name);
$self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL))
if $self->{list}->can('SetItemFont'); # legacy code for wxPerl < 0.9918 not supporting SetItemFont()
$self->{list}->SetItem($obj_idx, 1, $object->instances_count);
$self->{list}->SetItem($obj_idx, 2, ($object->scale * 100) . "%");
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
$self->{list}->SetItem($obj_idx, 2, ($model_object->instances->[0]->scaling_factor * 100) . "%");
$self->make_thumbnail($obj_idx);
$self->arrange unless $params{no_arrange};
$self->recenter;
$self->{list}->Update;
$self->{list}->Select($obj_idx, 1);
$self->object_list_changed;
@ -440,11 +442,11 @@ sub remove {
}
splice @{$self->{objects}}, $obj_idx, 1;
$self->{model}->delete_object($obj_idx);
$self->{list}->DeleteItem($obj_idx);
$self->{selected_objects} = [];
$self->selection_changed(0);
$self->object_list_changed;
$self->select_object(undef);
$self->recenter;
$self->{canvas}->Refresh;
}
@ -453,11 +455,11 @@ sub reset {
my $self = shift;
@{$self->{objects}} = ();
$self->{model}->delete_all_objects;
$self->{list}->DeleteAllItems;
$self->{selected_objects} = [];
$self->selection_changed(0);
$self->object_list_changed;
$self->select_object(undef);
$self->{canvas}->Refresh;
}
@ -465,10 +467,14 @@ sub increase {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
my $instances = $object->instances;
push @$instances, my $new_instance = $instances->[-1]->clone;
$new_instance->translate(scale(10), scale(10));
$self->{list}->SetItem($obj_idx, 1, $object->instances_count);
my $model_object = $self->{model}->objects->[$obj_idx];
my $last_instance = $model_object->instances->[-1];
$model_object->add_instance(
offset => [ map 10+$_, @{$last_instance->offset} ],
scaling_factor => $last_instance->scaling_factor,
rotation => $last_instance->rotation,
);
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
$self->arrange;
}
@ -476,9 +482,10 @@ sub decrease {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
if ($object->instances_count >= 2) {
pop @{$object->instances};
$self->{list}->SetItem($obj_idx, 1, $object->instances_count);
my $model_object = $self->{model}->objects->[$obj_idx];
if ($model_object->instances_count >= 2) {
$model_object->delete_last_instance;
$self->{list}->SetItem($obj_idx, 1, $model_object->instances_count);
} else {
$self->remove;
}
@ -496,18 +503,25 @@ sub rotate {
my ($angle) = @_;
my ($obj_idx, $object) = $self->selected_object;
my $model_object = $self->{model}->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# we need thumbnail to be computed before allowing rotation
return if !$object->thumbnail;
if (!defined $angle) {
$angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $object->rotate, -364, 364, $self);
$angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $model_instance->rotation, -364, 364, $self);
return if !$angle || $angle == -1;
$angle = 0 - $angle; # rotate clockwise (be consistent with button icon)
}
$object->rotate($object->rotate + $angle);
$self->selection_changed(1); # refresh info (size etc.)
{
my $new_angle = $model_instance->rotation + $angle;
$_->rotation($new_angle) for @{ $model_object->instances };
$model_object->update_bounding_box;
$object->transform_thumbnail($self->{model}, $obj_idx);
}
$self->selection_changed; # refresh info (size etc.)
$self->recenter;
$self->{canvas}->Refresh;
}
@ -516,16 +530,28 @@ sub changescale {
my $self = shift;
my ($obj_idx, $object) = $self->selected_object;
my $model_object = $self->{model}->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# we need thumbnail to be computed before allowing scaling
return if !$object->thumbnail;
# max scale factor should be above 2540 to allow importing files exported in inches
my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 100000, $self);
my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $model_instance->scaling_factor*100, 0, 100000, $self);
return if !$scale || $scale == -1;
$self->{list}->SetItem($obj_idx, 2, "$scale%");
$object->changescale($scale / 100);
$scale /= 100; # turn percent into factor
{
my $variation = $scale / $model_instance->scaling_factor;
foreach my $range (@{ $model_object->layer_height_ranges }) {
$range->[0] *= $variation;
$range->[1] *= $variation;
}
$_->scaling_factor($scale) for @{ $model_object->instances };
$model_object->update_bounding_box;
$object->transform_thumbnail($self->{model}, $obj_idx);
}
$self->selection_changed(1); # refresh info (size, volume etc.)
$self->arrange;
}
@ -533,19 +559,8 @@ sub changescale {
sub arrange {
my $self = shift;
my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return;
my @size = ();
for my $a (X,Y) {
$size[$a] = max(map $_->transformed_size->[$a], @{$self->{objects}});
}
eval {
my $config = $self->skeinpanel->config;
my @positions = Slic3r::Geometry::arrange
($total_parts, @size, @{$config->bed_size}, $config->min_object_distance, $self->skeinpanel->config);
@$_ = @{shift @positions}
for map @{$_->instances}, @{$self->{objects}};
$self->{model}->arrange_objects($self->skeinpanel->config);
};
# ignore arrange warnings on purpose
@ -556,54 +571,56 @@ sub arrange {
sub split_object {
my $self = shift;
my ($obj_idx, $current_object) = $self->selected_object;
my $current_copies_num = $current_object->instances_count;
my $model_object = $current_object->get_model_object;
my ($obj_idx, $current_object) = $self->selected_object;
my $current_model_object = $self->{model}->objects->[$obj_idx];
if (@{$model_object->volumes} > 1) {
if (@{$current_model_object->volumes} > 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains more than one volume/material.");
return;
}
my $mesh = $model_object->mesh;
$mesh->align_to_origin;
$mesh->repair;
my @new_meshes = @{$mesh->split};
my @new_meshes = @{$current_model_object->volumes->[0]->mesh->split};
if (@new_meshes == 1) {
Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part.");
return;
}
# remove the original object before spawning the object_loaded event, otherwise
# we'll pass the wrong $obj_idx to it (which won't be recognized after the
# thumbnail thread returns)
$self->remove($obj_idx);
$current_object = $obj_idx = undef;
# create a bogus Model object, we only need to instantiate the new Model::Object objects
my $new_model = Slic3r::Model->new;
foreach my $mesh (@new_meshes) {
$mesh->repair;
my $bb = $mesh->bounding_box;
my $model_object = $new_model->add_object;
$model_object->add_volume(mesh => $mesh);
my $object = Slic3r::GUI::Plater::Object->new(
name => basename($current_object->input_file),
input_file => $current_object->input_file,
input_file_object_id => undef,
model => $new_model,
model_object_idx => $#{$new_model->objects},
mesh_stats => $mesh->stats, # so that we can free model
instances => [ map $bb->min_point->pp, 1..$current_copies_num ],
my $model_object = $new_model->add_object(
input_file => $current_model_object->input_file,
config => $current_model_object->config->clone,
layer_height_ranges => $current_model_object->layer_height_ranges, # TODO: clone this
material_mapping => $current_model_object->material_mapping, # TODO: clone this
);
$model_object->add_volume(
mesh => $mesh,
material_id => $current_model_object->volumes->[0]->material_id,
);
push @{ $self->{objects} }, $object;
$self->object_loaded($#{ $self->{objects} }, no_arrange => 1);
for my $instance_idx (0..$#{ $current_model_object->instances }) {
my $current_instance = $current_model_object->instances->[$instance_idx];
$model_object->add_instance(
offset => [
$current_instance->offset->[X] + ($instance_idx * 10),
$current_instance->offset->[Y] + ($instance_idx * 10),
],
rotation => $current_instance->rotation,
scaling_factor => $current_instance->scaling_factor,
);
}
# we need to center this single object around origin
$model_object->center_around_origin;
$self->load_model_object($model_object);
}
$self->recenter;
$self->{canvas}->Refresh;
}
sub export_gcode {
@ -621,7 +638,7 @@ sub export_gcode {
# select output file
$self->{output_file} = $main::opt{output};
{
$self->{output_file} = $self->skeinpanel->init_print->expanded_output_filepath($self->{output_file}, $self->{objects}[0]->input_file);
$self->{output_file} = $self->skeinpanel->init_print->expanded_output_filepath($self->{output_file}, $self->{model}->objects->[0]->input_file);
my $dlg = Wx::FileDialog->new($self, 'Save G-code file as:', Slic3r::GUI->output_path(dirname($self->{output_file})),
basename($self->{output_file}), &Slic3r::GUI::SkeinPanel::FILE_WILDCARDS->{gcode}, wxFD_SAVE);
if ($dlg->ShowModal != wxID_OK) {
@ -636,8 +653,6 @@ sub export_gcode {
$self->statusbar->StartBusy;
$_->free_model_object for @{$self->{objects}};
# It looks like declaring a local $SIG{__WARN__} prevents the ugly
# "Attempt to free unreferenced scalar" warning...
local $SIG{__WARN__} = Slic3r::GUI::warning_catcher($self);
@ -704,12 +719,13 @@ sub export_gcode2 {
eval {
$print->config->validate;
$print->add_model($self->make_model);
$print->add_model_object($_) for @{ $self->{model}->objects };
$print->validate;
{
my @warnings = ();
local $SIG{__WARN__} = sub { push @warnings, $_[0] };
my %params = (
output_file => $output_file,
status_cb => sub { $params{progressbar}->(@_) },
@ -764,7 +780,7 @@ sub export_stl {
my $self = shift;
my $output_file = $self->_get_export_file('STL') or return;
Slic3r::Format::STL->write_file($output_file, $self->make_model, binary => 1);
Slic3r::Format::STL->write_file($output_file, $self->{model}, binary => 1);
$self->statusbar->SetStatusText("STL file exported to $output_file");
}
@ -772,7 +788,7 @@ sub export_amf {
my $self = shift;
my $output_file = $self->_get_export_file('AMF') or return;
Slic3r::Format::AMF->write_file($output_file, $self->make_model);
Slic3r::Format::AMF->write_file($output_file, $self->{model});
$self->statusbar->SetStatusText("AMF file exported to $output_file");
}
@ -784,7 +800,7 @@ sub _get_export_file {
my $output_file = $main::opt{output};
{
$output_file = $self->skeinpanel->init_print->expanded_output_filepath($output_file, $self->{objects}[0]->input_file);
$output_file = $self->skeinpanel->init_print->expanded_output_filepath($output_file, $self->{model}->objects->[0]->input_file);
$output_file =~ s/\.gcode$/$suffix/i;
my $dlg = Wx::FileDialog->new($self, "Save $format file as:", dirname($output_file),
basename($output_file), &Slic3r::GUI::SkeinPanel::MODEL_WILDCARD, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
@ -798,47 +814,14 @@ sub _get_export_file {
return $output_file;
}
sub make_model {
my $self = shift;
my $model = Slic3r::Model->new;
foreach my $plater_object (@{$self->{objects}}) {
my $model_object = $plater_object->get_model_object;
my $new_model_object = $model->add_object(
input_file => $plater_object->input_file,
config => $plater_object->config,
layer_height_ranges => $plater_object->layer_height_ranges,
material_mapping => $plater_object->material_mapping,
);
foreach my $volume (@{$model_object->volumes}) {
$new_model_object->add_volume(
material_id => $volume->material_id,
mesh => $volume->mesh,
);
$model->set_material($volume->material_id || 0, {});
}
$new_model_object->align_to_origin;
$new_model_object->add_instance(
rotation => $plater_object->rotate, # around center point
scaling_factor => $plater_object->scale,
offset => Slic3r::Point->new(@$_),
) for @{$plater_object->instances};
}
$model->align_to_origin;
return $model;
}
sub make_thumbnail {
my $self = shift;
my ($obj_idx) = @_;
my $object = $self->{objects}[$obj_idx];
$object->thumbnail_scaling_factor($self->{scaling_factor});
$object->thumbnail(Slic3r::ExPolygon::Collection->new);
my $plater_object = $self->{objects}[$obj_idx];
$plater_object->thumbnail(Slic3r::ExPolygon::Collection->new);
my $cb = sub {
$object->make_thumbnail;
$plater_object->make_thumbnail($self->{model}, $obj_idx);
if ($Slic3r::have_threads) {
Wx::PostEvent($self, Wx::PlThreadEvent->new(-1, $THUMBNAIL_DONE_EVENT, shared_clone([ $obj_idx ])));
@ -857,33 +840,38 @@ sub on_thumbnail_made {
my $self = shift;
my ($obj_idx) = @_;
$self->{objects}[$obj_idx]->_transform_thumbnail;
$self->{objects}[$obj_idx]->transform_thumbnail($self->{model}, $obj_idx);
$self->recenter;
$self->{canvas}->Refresh;
}
sub clean_instance_thumbnails {
my ($self) = @_;
foreach my $object (@{ $self->{objects} }) {
@{ $object->instance_thumbnails } = ();
}
}
# this method gets called whenever bed is resized or the objects' bounding box changes
# (i.e. when an object is added/removed/moved/rotated/scaled)
sub recenter {
my $self = shift;
return unless @{$self->{objects}};
# calculate displacement needed to center the print
my @bbs = ();
foreach my $plobj (@{$self->{objects}}) {
my $bb = $plobj->transformed_bounding_box; # in scaled coordinates
foreach my $inst (@{$plobj->instances}) {
push @bbs, my $inst_bb = $bb->clone;
$inst_bb->translate(@$inst, 0);
}
}
my $print_bb = Slic3r::Geometry::BoundingBox->merge(@bbs);
# get model bounding box in pixels
my $print_bb = $self->{model}->bounding_box;
$print_bb->scale($self->{scaling_factor});
# get model size in pixels
my $print_size = $print_bb->size;
# $self->{shift} contains the offset in pixels to add to object instances in order to center them
# it is expressed in upwards Y
# $self->{shift} contains the offset in pixels to add to object thumbnails
# in order to center them
$self->{shift} = [
$self->to_pixel(-$print_bb->x_min) + ($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_size->[X])) / 2,
$self->to_pixel(-$print_bb->y_min) + ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_size->[Y])) / 2,
-$print_bb->x_min + ($self->{canvas}->GetSize->GetWidth - $print_size->[X]) / 2,
-$print_bb->y_min + ($self->{canvas}->GetSize->GetHeight - $print_size->[Y]) / 2,
];
}
@ -921,7 +909,6 @@ sub _update_bed_size {
# when the canvas is not rendered yet, its GetSize() method returns 0,0
# scaling_factor is expressed in pixel / mm
$self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size });
$_->thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} };
$self->recenter;
}
@ -971,55 +958,61 @@ sub repaint {
# draw thumbnails
$dc->SetPen(wxBLACK_PEN);
@{$parent->{object_previews}} = ();
$parent->clean_instance_thumbnails;
for my $obj_idx (0 .. $#{$parent->{objects}}) {
my $object = $parent->{objects}[$obj_idx];
my $model_object = $parent->{model}->objects->[$obj_idx];
next unless defined $object->thumbnail;
for my $instance_idx (0 .. $#{$object->instances}) {
my $instance = $object->instances->[$instance_idx];
for my $instance_idx (0 .. $#{$model_object->instances}) {
my $instance = $model_object->instances->[$instance_idx];
next if !defined $object->transformed_thumbnail;
my $thumbnail = $object->transformed_thumbnail->clone;
$thumbnail->translate(map $_ * $parent->{scaling_factor}, @$instance);
$thumbnail->translate(map scale($parent->{shift}[$_]), (X,Y));
push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $thumbnail ]; # $thumbnail has scaled coordinates
my $thumbnail = $object->transformed_thumbnail->clone; # in scaled coordinates
$thumbnail->scale(&Slic3r::SCALING_FACTOR * $parent->{scaling_factor}); # in unscaled pixels
$thumbnail->translate(map $_ * $parent->{scaling_factor}, @{$instance->offset});
$thumbnail->translate(@{$parent->{shift}});
my $drag_object = $self->{drag_object};
if (defined $drag_object && $obj_idx == $drag_object->[0] && $instance_idx == $drag_object->[1]) {
$object->instance_thumbnails->[$instance_idx] = $thumbnail;
if (defined $self->{drag_object} && $self->{drag_object}[0] == $obj_idx && $self->{drag_object}[1] == $instance_idx) {
$dc->SetBrush($parent->{dragged_brush});
} elsif (grep { $_->[0] == $obj_idx } @{$parent->{selected_objects}}) {
} elsif ($object->selected) {
$dc->SetBrush($parent->{selected_brush});
} else {
$dc->SetBrush($parent->{objects_brush});
}
foreach my $expolygon (@$thumbnail) {
my $points = $expolygon->contour->pp;
foreach my $point (@$points) {
$point->[X] *= &Slic3r::SCALING_FACTOR;
$point->[Y] *= &Slic3r::SCALING_FACTOR;
}
$dc->DrawPolygon($parent->_y($points), 0, 0);
}
# if sequential printing is enabled and we have more than one object
if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) {
my @points = map @{$_->contour}, @{$parent->{object_previews}->[-1][2]};
if (@points >= 3) {
my ($clearance) = @{offset([convex_hull(\@points)], scale($parent->{config}->extruder_clearance_radius / 2) * $parent->{scaling_factor}, 100, JT_ROUND)};
$clearance->scale(&Slic3r::SCALING_FACTOR);
$dc->SetPen($parent->{clearance_pen});
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawPolygon($parent->_y($clearance), 0, 0);
}
if (0) {
# draw bounding box for debugging purposes
my $bb = $model_object->instance_bounding_box($instance_idx);
$bb->scale($parent->{scaling_factor});
# no need to translate by instance offset because instance_bounding_box() does that
$bb->translate(@{$parent->{shift}}, 0);
my $points = $bb->polygon->pp;
$dc->SetPen($parent->{clearance_pen});
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawPolygon($parent->_y($points), 0, 0);
}
# if sequential printing is enabled and we have more than one object, draw clearance area
if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{model}->objects}) > 1) {
my ($clearance) = @{offset([$thumbnail->convex_hull], ($parent->{config}->extruder_clearance_radius / 2) * $parent->{scaling_factor}, 100, JT_ROUND)};
$dc->SetPen($parent->{clearance_pen});
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawPolygon($parent->_y($clearance), 0, 0);
}
}
}
# draw skirt
if (@{$parent->{object_previews}} && $parent->{config}->skirts) {
my @points = map @{$_->contour}, map @{$_->[2]}, @{$parent->{object_previews}};
if (@{$parent->{objects}} && $parent->{config}->skirts) {
my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$parent->{objects}};
if (@points >= 3) {
my ($convex_hull) = @{offset([convex_hull(\@points)], scale($parent->{config}->skirt_distance) * $parent->{scaling_factor}, 1, JT_ROUND)};
$convex_hull->scale(&Slic3r::SCALING_FACTOR);
my ($convex_hull) = @{offset([convex_hull(\@points)], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 100, JT_ROUND)};
$dc->SetPen($parent->{skirt_pen});
$dc->SetBrush($parent->{transparent_brush});
$dc->DrawPolygon($parent->_y($convex_hull), 0, 0);
@ -1034,20 +1027,22 @@ sub mouse_event {
my $parent = $self->GetParent;
my $point = $event->GetPosition;
my $pos = Slic3r::Point->new_scale(@{$parent->_y([[$point->x, $point->y]])->[0]}); #]] in scaled pixels
my $pos = Slic3r::Point->new(@{$parent->_y([[$point->x, $point->y]])->[0]}); # in pixels
if ($event->ButtonDown(&Wx::wxMOUSE_BTN_LEFT)) {
$parent->{selected_objects} = [];
$parent->{list}->Select($parent->{list}->GetFirstSelected, 0);
$parent->selection_changed(0);
for my $preview (@{$parent->{object_previews}}) {
my ($obj_idx, $instance_idx, $thumbnail) = @$preview;
if (defined first { $_->contour->contains_point($pos) } @$thumbnail) {
$parent->{selected_objects} = [ [$obj_idx, $instance_idx] ];
$parent->{list}->Select($obj_idx, 1);
$parent->selection_changed(1);
my $instance = $parent->{objects}[$obj_idx]->instances->[$instance_idx];
$self->{drag_start_pos} = [ map $pos->[$_] - scale($parent->{shift}[$_]) - scale($parent->to_pixel($instance->[$_])), X,Y ]; # displacement between the click and the instance origin
$self->{drag_object} = $preview;
$parent->select_object(undef);
for my $obj_idx (0 .. $#{$parent->{objects}}) {
my $object = $parent->{objects}->[$obj_idx];
for my $instance_idx (0 .. $#{ $object->instance_thumbnails }) {
my $thumbnail = $object->instance_thumbnails->[$instance_idx];
if ($thumbnail->contains_point($pos)) {
$parent->select_object($obj_idx);
my $instance = $parent->{model}->objects->[$obj_idx]->instances->[$instance_idx];
$self->{drag_start_pos} = [ # displacement between the click and the instance origin
$pos->x - $parent->{shift}[X] - ($instance->offset->[X] * $parent->{scaling_factor}),
$pos->y - $parent->{shift}[Y] - ($instance->offset->[Y] * $parent->{scaling_factor}),
];
$self->{drag_object} = [ $obj_idx, $instance_idx ];
}
}
}
$parent->Refresh;
@ -1058,23 +1053,21 @@ sub mouse_event {
$self->{drag_object} = undef;
$self->SetCursor(wxSTANDARD_CURSOR);
} elsif ($event->ButtonDClick) {
$parent->object_preview_dialog if @{$parent->{selected_objects}};
$parent->object_preview_dialog if $parent->selected_object;
} elsif ($event->Dragging) {
return if !$self->{drag_start_pos}; # concurrency problems
for my $preview ($self->{drag_object}) {
my ($obj_idx, $instance_idx, $thumbnail) = @$preview;
$parent->{objects}[$obj_idx]->instances->[$instance_idx] = Slic3r::Point->new(
map { $parent->to_units(unscale($pos->[$_] - $self->{drag_start_pos}[$_]) - $parent->{shift}[$_]) } (X,Y),
);
$parent->Refresh;
}
my ($obj_idx, $instance_idx) = @{ $self->{drag_object} };
my $model_object = $parent->{model}->objects->[$obj_idx];
$model_object->instances->[$instance_idx]->offset([
($pos->[X] - $self->{drag_start_pos}[X] - $parent->{shift}[X]) / $parent->{scaling_factor},
($pos->[Y] - $self->{drag_start_pos}[Y] - $parent->{shift}[Y]) / $parent->{scaling_factor},
]);
$model_object->update_bounding_box;
$parent->Refresh;
} elsif ($event->Moving) {
my $cursor = wxSTANDARD_CURSOR;
for my $preview (@{$parent->{object_previews}}) {
if (defined first { $_->contour->contains_point($pos) } @{ $preview->[2] }) {
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
last;
}
if (defined first { $_->contains_point($pos) } map @{$_->instance_thumbnails}, @{ $parent->{objects} }) {
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
}
$self->SetCursor($cursor);
}
@ -1084,9 +1077,8 @@ sub list_item_deselected {
my ($self, $event) = @_;
if ($self->{list}->GetFirstSelected == -1) {
$self->{selected_objects} = [];
$self->select_object(undef);
$self->{canvas}->Refresh;
$self->selection_changed(0);
}
}
@ -1094,9 +1086,8 @@ sub list_item_selected {
my ($self, $event) = @_;
my $obj_idx = $event->GetIndex;
$self->{selected_objects} = [ grep $_->[0] == $obj_idx, @{$self->{object_previews}} ];
$self->select_object($obj_idx);
$self->{canvas}->Refresh;
$self->selection_changed(1);
}
sub list_item_activated {
@ -1120,7 +1111,8 @@ sub object_preview_dialog {
}
my $dlg = Slic3r::GUI::Plater::ObjectPreviewDialog->new($self,
object => $self->{objects}[$obj_idx],
object => $self->{objects}[$obj_idx],
model_object => $self->{model}->objects->[$obj_idx],
);
$dlg->ShowModal;
}
@ -1139,7 +1131,8 @@ sub object_settings_dialog {
return unless $self->validate_config;
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self,
object => $self->{objects}[$obj_idx],
object => $self->{objects}[$obj_idx],
model_object => $self->{model}->objects->[$obj_idx],
);
$dlg->ShowModal;
}
@ -1160,7 +1153,9 @@ sub object_list_changed {
sub selection_changed {
my $self = shift;
my ($have_sel) = @_;
my ($obj_idx, $object) = $self->selected_object;
my $have_sel = defined $obj_idx;
my $method = $have_sel ? 'Enable' : 'Disable';
$self->{"btn_$_"}->$method
@ -1173,13 +1168,14 @@ sub selection_changed {
if ($self->{object_info_size}) { # have we already loaded the info pane?
if ($have_sel) {
my ($obj_idx, $object) = $self->selected_object;
$self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", map unscale($_), @{$object->transformed_size}));
$self->{object_info_materials}->SetLabel($object->materials);
my $model_object = $self->{model}->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
$self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size}));
$self->{object_info_materials}->SetLabel($model_object->materials_count);
if (my $stats = $object->mesh_stats) {
$self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($object->scale**3)));
$self->{object_info_facets}->SetLabel(sprintf('%d (%d shells)', $object->facets, $stats->{number_of_parts}));
if (my $stats = $model_object->mesh_stats) {
$self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($model_instance->scaling_factor**3)));
$self->{object_info_facets}->SetLabel(sprintf('%d (%d shells)', $model_object->facets_count, $stats->{number_of_parts}));
if (my $errors = sum(@$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)})) {
$self->{object_info_manifold}->SetLabel(sprintf("Auto-repaired (%d errors)", $errors));
$self->{object_info_manifold_warning_icon}->Show;
@ -1205,9 +1201,24 @@ sub selection_changed {
}
}
sub select_object {
my ($self, $obj_idx) = @_;
$_->selected(0) for @{ $self->{objects} };
if (defined $obj_idx) {
$self->{objects}->[$obj_idx]->selected(1);
$self->{list}->Select($obj_idx, 1);
} else {
# TODO: deselect all in list
}
$self->selection_changed(1);
}
sub selected_object {
my $self = shift;
my $obj_idx = $self->{selected_objects}[0] ? $self->{selected_objects}[0][0] : $self->{list}->GetFirstSelected;
my $obj_idx = first { $self->{objects}[$_]->selected } 0..$#{ $self->{objects} };
return undef if !defined $obj_idx;
return ($obj_idx, $self->{objects}[$obj_idx]),
}
@ -1276,155 +1287,47 @@ use List::Util qw(first);
use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad);
has 'name' => (is => 'rw', required => 1);
has 'input_file' => (is => 'rw', required => 1);
has 'input_file_object_id' => (is => 'rw'); # undef means keep model object
has 'model' => (is => 'rw', required => 1, trigger => \&_trigger_model_object);
has 'model_object_idx' => (is => 'rw', required => 1, trigger => \&_trigger_model_object);
has 'bounding_box' => (is => 'rw'); # scaled 3D bb of original object (aligned to origin) with no rotation or scaling
has 'convex_hull' => (is => 'rw'); # scaled 2D convex hull of original object (aligned to origin) with no rotation or scaling
has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail);
has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_transform_thumbnail); # around object center point
has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis
has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail);
has 'thumbnail' => (is => 'rw'); # ExPolygon::Collection in scaled model units with no transforms
has 'transformed_thumbnail' => (is => 'rw');
has 'thumbnail_scaling_factor' => (is => 'rw', trigger => \&_transform_thumbnail);
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new });
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => extruder_idx }
has 'mesh_stats' => (is => 'ro', required => 1);
# statistics
has 'facets' => (is => 'rw');
has 'vertices' => (is => 'rw');
has 'materials' => (is => 'rw');
has 'is_manifold' => (is => 'rw');
sub _trigger_model_object {
my $self = shift;
if ($self->model && defined $self->model_object_idx) {
my $model_object = $self->model->objects->[$self->model_object_idx];
$model_object->align_to_origin;
$self->bounding_box($model_object->bounding_box);
$self->bounding_box->scale(1 / &Slic3r::SCALING_FACTOR);
my $mesh = $model_object->mesh;
$mesh->repair;
$self->convex_hull($mesh->convex_hull);
$self->facets($mesh->facets_count);
$self->vertices(scalar @{$mesh->vertices});
$self->materials($model_object->materials_count);
}
}
sub changescale {
my $self = shift;
my ($scale) = @_;
my $variation = $scale / $self->scale;
foreach my $range (@{ $self->layer_height_ranges }) {
$range->[0] *= $variation;
$range->[1] *= $variation;
}
$self->scale($scale);
}
sub needed_repair {
my $self = shift;
if ($self->get_model_object->needed_repair) {
warn "Warning: the input file contains manifoldness errors. "
. "Slic3r repaired it successfully by guessing what the correct shape should be, "
. "but you might still want to inspect the G-code before printing.\n";
$self->is_manifold(0);
} else {
$self->is_manifold(1);
}
return $self->is_manifold;
}
sub free_model_object {
my $self = shift;
# only delete mesh from memory if we can retrieve it from the original file
return unless $self->input_file && defined $self->input_file_object_id;
$self->model(undef);
$self->model_object_idx(undef);
}
sub get_model_object {
my $self = shift;
if ($self->model) {
return $self->model->objects->[$self->model_object_idx];
}
return Slic3r::Model
->read_from_file($self->input_file)
->objects
->[$self->input_file_object_id];
}
sub instances_count {
my $self = shift;
return scalar @{$self->instances};
}
has 'instance_thumbnails' => (is => 'ro', default => sub { [] }); # array of ExPolygon::Collection objects, each one representing the actual placed thumbnail of each instance in pixel units
has 'selected' => (is => 'rw', default => sub { 0 });
sub make_thumbnail {
my $self = shift;
my ($self, $model, $obj_idx) = @_;
my $mesh = $self->get_model_object->mesh; # $self->model_object is already aligned to origin
$mesh->repair;
if (@{$mesh->facets} <= 5000) {
my $mesh = $model->objects->[$obj_idx]->raw_mesh;
if ($mesh->facets_count <= 5000) {
# remove polygons with area <= 1mm
my $area_threshold = Slic3r::Geometry::scale 1;
$self->thumbnail->append(
grep $_->area >= $area_threshold,
@{ $mesh->horizontal_projection },
@{ $mesh->horizontal_projection }, # horizontal_projection returns scaled expolygons
);
$self->thumbnail->simplify(0.5);
} else {
my $convex_hull = Slic3r::ExPolygon->new($self->convex_hull);
my $convex_hull = Slic3r::ExPolygon->new($mesh->convex_hull);
$self->thumbnail->append($convex_hull);
}
return $self->thumbnail;
}
sub _transform_thumbnail {
my $self = shift;
sub transform_thumbnail {
my ($self, $model, $obj_idx) = @_;
return unless defined $self->thumbnail;
my $t = $self->_apply_transform($self->thumbnail);
$t->scale($self->thumbnail_scaling_factor);
my $model_object = $model->objects->[$obj_idx];
my $model_instance = $model_object->instances->[0];
# the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model_object()
my $t = $self->thumbnail->clone;
$t->rotate(deg2rad($model_instance->rotation), Slic3r::Point->new(0,0));
$t->scale($model_instance->scaling_factor);
$self->transformed_thumbnail($t);
}
# bounding box with applied rotation and scaling
sub transformed_bounding_box {
my $self = shift;
my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull));
$bb->extents->[Z] = $self->bounding_box->clone->extents->[Z];
$bb->extents->[Z][MAX] *= $self->scale;
return $bb;
}
sub _apply_transform {
my $self = shift;
my ($entity) = @_; # can be anything that implements ->clone(), ->rotate() and ->scale()
# the order of these transformations MUST be the same everywhere, including
# in Slic3r::Print->add_model()
my $result = $entity->clone;
$result->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D);
$result->scale($self->scale);
return $result;
}
sub transformed_size {
my $self = shift;
return $self->transformed_bounding_box->size;
}
1;

View File

@ -11,10 +11,10 @@ sub new {
my $class = shift;
my ($parent, %params) = @_;
my $self = $class->SUPER::new($parent, -1, $params{object}->name, wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
$self->{object} = $params{object};
$self->{model_object} = $params{model_object};
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
$sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object), 1, wxEXPAND, 0);
$sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{model_object}), 1, wxEXPAND, 0);
$self->SetSizer($sizer);
$self->SetMinSize($self->GetSize);

View File

@ -11,12 +11,12 @@ 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);
$self->{object} = $params{object};
$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}, object => $self->{object}), "Settings");
$self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers");
$self->{tabpanel}->AddPage($self->{materials} = Slic3r::GUI::Plater::ObjectDialog::MaterialsTab->new($self->{tabpanel}, object => $self->{object}), "Materials");
$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->{materials} = Slic3r::GUI::Plater::ObjectDialog::MaterialsTab->new($self->{tabpanel}), "Materials");
my $buttons = $self->CreateStdDialogButtonSizer(wxOK);
EVT_BUTTON($self, wxID_OK, sub {
@ -42,17 +42,24 @@ sub new {
return $self;
}
package Slic3r::GUI::Plater::ObjectDialog::BaseTab;
use base 'Wx::Panel';
sub model_object {
my ($self) = @_;
return $self->GetParent->GetParent->{model_object};
}
package Slic3r::GUI::Plater::ObjectDialog::SettingsTab;
use Wx qw(:dialog :id :misc :sizer :systemsettings :button :icon);
use Wx::Grid;
use Wx::Event qw(EVT_BUTTON);
use base 'Wx::Panel';
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);
@ -79,7 +86,7 @@ sub new {
my $idx = $choice->GetSelection;
return if $idx == -1; # lack of selected item, can happen on Windows
my $opt_key = $self->{options}[$idx];
$self->{object}->config->apply(Slic3r::Config->new_from_defaults($opt_key));
$self->model_object->config->apply(Slic3r::Config->new_from_defaults($opt_key));
$self->update_optgroup;
});
@ -105,7 +112,7 @@ sub update_optgroup {
$self->{options_sizer}->Clear(1);
my $config = $self->{object}->config;
my $config = $self->model_object->config;
my %categories = ();
foreach my $opt_key (keys %$config) {
my $category = $Slic3r::Config::Options->{$opt_key}{category};
@ -124,7 +131,7 @@ sub update_optgroup {
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->{object}->config->{$opt_key};
delete $self->model_object->config->{$opt_key};
Slic3r::GUI->CallAfter(sub { $self->update_optgroup });
});
return $btn;
@ -141,7 +148,7 @@ sub CanClose {
# validate options before allowing user to dismiss the dialog
# the validate method only works on full configs so we have
# to merge our settings with the default ones
my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->{object}->config);
my $config = Slic3r::Config->merge($self->GetParent->GetParent->GetParent->GetParent->GetParent->config, $self->model_object->config);
eval {
$config->validate;
};
@ -153,13 +160,12 @@ package Slic3r::GUI::Plater::ObjectDialog::LayersTab;
use Wx qw(:dialog :id :misc :sizer :systemsettings);
use Wx::Grid;
use Wx::Event qw(EVT_GRID_CELL_CHANGED);
use base 'Wx::Panel';
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};
my $sizer = Wx::BoxSizer->new(wxVERTICAL);
@ -182,7 +188,7 @@ sub new {
$grid->SetDefaultCellAlignment(wxALIGN_CENTRE, wxALIGN_CENTRE);
# load data
foreach my $range (@{ $self->{object}->layer_height_ranges }) {
foreach my $range (@{ $self->model_object->layer_height_ranges }) {
$grid->AppendRows(1);
my $i = $grid->GetNumberRows-1;
$grid->SetCellValue($i, $_, $range->[$_]) for 0..2;
@ -242,7 +248,7 @@ sub Closing {
my $self = shift;
# save ranges into the plater object
$self->{object}->layer_height_ranges([ $self->_get_ranges ]);
$self->model_object->layer_height_ranges([ $self->_get_ranges ]);
}
sub _get_ranges {
@ -261,7 +267,7 @@ 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 'Wx::Panel';
use base 'Slic3r::GUI::Plater::ObjectDialog::BaseTab';
sub new {
my $class = shift;
@ -280,12 +286,12 @@ sub new {
}
# get unique materials used in this object
$self->{materials} = [ $self->{object}->get_model_object->unique_materials ];
$self->{materials} = [ $self->model_object->unique_materials ];
# build an OptionsGroup
$self->{mapping} = {
(map { $self->{materials}[$_] => $_+1 } 0..$#{ $self->{materials} }), # defaults
%{$self->{object}->material_mapping},
%{$self->model_object->material_mapping},
};
my $optgroup = Slic3r::GUI::OptionsGroup->new(
parent => $self,
@ -298,7 +304,7 @@ sub new {
{
opt_key => "material_extruder_$_",
type => 'i',
label => $self->{object}->get_model_object->model->get_material_name($material_id),
label => $self->model_object->model->get_material_name($material_id),
min => 1,
default => $self->{mapping}{$material_id},
on_change => sub { $self->{mapping}{$material_id} = $_[0] },
@ -318,7 +324,7 @@ sub Closing {
my $self = shift;
# save mappings into the plater object
$self->{object}->material_mapping($self->{mapping});
$self->model_object->material_mapping($self->{mapping});
}
1;

View File

@ -28,16 +28,15 @@ sub new {
$self->sphi(45);
$self->stheta(-45);
$object->align_to_origin;
$self->object_center($object->center);
$self->object_size($object->size);
my $bounding_box = $object->raw_mesh->bounding_box;
$self->object_center($bounding_box->center);
$self->object_size($bounding_box->size);
# group mesh(es) by material
my @materials = ();
$self->volumes([]);
foreach my $volume (@{$object->volumes}) {
my $mesh = $volume->mesh;
$mesh->repair;
my $material_id = $volume->material_id // '_';
my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials;

View File

@ -134,7 +134,17 @@ sub quick_slice {
Slic3r::GUI->save_settings;
my $print = $self->init_print;
$print->add_model(Slic3r::Model->read_from_file($input_file));
my $model = Slic3r::Model->read_from_file($input_file);
if ($model->has_objects_with_no_instances) {
# apply a default position to all objects not having one
foreach my $object (@{$model->objects}) {
$object->add_instance(offset => [0,0]) if !defined $object->instances;
}
$model->arrange_objects($config);
}
$print->add_model_object($_) for @{ $model->objects };
$print->validate;
# select output file

View File

@ -130,6 +130,16 @@ sub min_point {
return Slic3r::Point->new($self->extents->[X][MIN], $self->extents->[Y][MIN]);
}
sub min_point3 {
my $self = shift;
return [ map $self->extents->[$_][MIN], (X,Y,Z) ];
}
sub vector_to_origin {
my $self = shift;
return [ map -$_, @{$self->min_point3} ];
}
sub max_point {
my $self = shift;
return Slic3r::Point->new($self->extents->[X][MAX], $self->extents->[Y][MAX]);

View File

@ -6,7 +6,6 @@ use Slic3r::Geometry qw(X Y Z MIN move_points);
has 'materials' => (is => 'ro', default => sub { {} });
has 'objects' => (is => 'ro', default => sub { [] });
has '_bounding_box' => (is => 'rw');
sub read_from_file {
my $class = shift;
@ -28,40 +27,61 @@ sub merge {
my $new_model = ref($class)
? $class
: $class->new;
foreach my $model (@models) {
# merge material attributes (should we rename them in case of duplicates?)
$new_model->set_material($_, { %{$model->materials->{$_}}, %{$model->materials->{$_} || {}} })
for keys %{$model->materials};
foreach my $object (@{$model->objects}) {
my $new_object = $new_model->add_object(
input_file => $object->input_file,
config => $object->config,
layer_height_ranges => $object->layer_height_ranges,
);
$new_object->add_volume(
material_id => $_->material_id,
mesh => $_->mesh->clone,
) for @{$object->volumes};
$new_object->add_instance(
offset => $_->offset,
rotation => $_->rotation,
) for @{ $object->instances // [] };
}
}
$new_model->add_object($_) for map @{$_->objects}, @models;
return $new_model;
}
sub add_object {
my $self = shift;
my $object = Slic3r::Model::Object->new(model => $self, @_);
push @{$self->objects}, $object;
$self->_bounding_box(undef);
return $object;
my $new_object;
if (@_ == 1) {
# we have a Model::Object
my ($object) = @_;
$new_object = $self->add_object(
input_file => $object->input_file,
config => $object->config,
layer_height_ranges => $object->layer_height_ranges, # TODO: clone!
material_mapping => $object->material_mapping, # TODO: clone!
);
foreach my $volume (@{$object->volumes}) {
$new_object->add_volume(
material_id => $volume->material_id,
mesh => $volume->mesh->clone,
);
if (defined $volume->material_id) {
# merge material attributes (should we rename materials in case of duplicates?)
$self->set_material($volume->material_id, {
%{ $object->model->materials->{$volume->material_id} },
%{ $self->materials->{$volume->material_id} || {} },
});
}
}
$new_object->add_instance(
offset => $_->offset,
rotation => $_->rotation,
scaling_factor => $_->scaling_factor,
) for @{ $object->instances // [] };
} else {
push @{$self->objects}, $new_object = Slic3r::Model::Object->new(model => $self, @_);
}
return $new_object;
}
sub delete_object {
my ($self, $obj_idx) = @_;
splice @{$self->objects}, $obj_idx, 1;
}
sub delete_all_objects {
my ($self) = @_;
@{$self->objects} = ();
}
sub set_material {
@ -74,104 +94,111 @@ sub set_material {
);
}
sub arrange_objects {
my $self = shift;
my ($config) = @_;
sub duplicate_objects_grid {
my ($self, $grid, $distance) = @_;
# do we have objects with no position?
if (first { !defined $_->instances } @{$self->objects}) {
# we shall redefine positions for all objects
my ($copies, @positions) = $self->_arrange(
config => $config,
items => $self->objects,
);
# apply positions to objects
foreach my $object (@{$self->objects}) {
$object->align_to_origin;
$object->instances([]);
die "Grid duplication is not supported with multiple objects\n"
if @{$self->objects} > 1;
my $object = $self->objects->[0];
@{$object->instances} = ();
my $size = $object->bounding_box->size;
for my $x_copy (1..$grid->[X]) {
for my $y_copy (1..$grid->[Y]) {
$object->add_instance(
offset => $_,
rotation => 0,
) for splice @positions, 0, $copies;
offset => [
($size->[X] + $distance) * ($x_copy-1),
($size->[Y] + $distance) * ($y_copy-1),
],
);
}
} else {
# we only have objects with defined position
# align the whole model to origin as it is
$self->align_to_origin;
# arrange this model as a whole
my ($copies, @positions) = $self->_arrange(
config => $config,
items => [$self],
);
# apply positions to objects by translating the current positions
foreach my $object (@{$self->objects}) {
my @old_instances = @{$object->instances};
$object->instances([]);
foreach my $instance (@old_instances) {
}
}
# this will append more instances to each object
# and then automatically rearrange everything
sub duplicate_objects {
my ($self, $config, $copies_num) = @_;
foreach my $object (@{$self->objects}) {
my @instances = @{$object->instances};
foreach my $instance (@instances) {
### $object->add_instance($instance->clone); if we had clone()
$object->add_instance(
offset => [ @{$instance->offset} ],
rotation => $instance->rotation,
scaling_factor => $instance->scaling_factor,
) for 2..$copies_num;
}
}
$self->arrange_objects($config);
}
# arrange objects preserving their instance count
# but altering their instance positions
sub arrange_objects {
my ($self, $config) = @_;
# get the (transformed) size of each instance so that we take
# into account their different transformations when packing
my @instance_sizes = ();
foreach my $object (@{$self->objects}) {
push @instance_sizes, map $object->instance_bounding_box($_)->size, 0..$#{$object->instances};
}
my @positions = $self->_arrange($config, \@instance_sizes);
foreach my $object (@{$self->objects}) {
$_->offset(shift @positions) for @{$object->instances};
$object->update_bounding_box;
}
}
# duplicate the entire model preserving instance relative positions
sub duplicate {
my ($self, $config, $copies_num) = @_;
my $model_size = $self->bounding_box->size;
my @positions = $self->_arrange($config, [ map $model_size, 2..$copies_num ]);
# note that this will leave the object count unaltered
foreach my $object (@{$self->objects}) {
foreach my $instance (@{$object->instances}) {
foreach my $pos (@positions) {
### $object->add_instance($instance->clone); if we had clone()
$object->add_instance(
offset => $_,
rotation => $instance->rotation,
scaling_factor => $instance->scaling_factor,
) for move_points($instance->offset, @positions);
offset => [ $instance->offset->[X] + $pos->[X], $instance->offset->[Y] + $pos->[Y] ],
rotation => $instance->rotation,
scaling_factor => $instance->scaling_factor,
);
}
}
$object->update_bounding_box;
}
}
sub _arrange {
my $self = shift;
my %params = @_;
my ($self, $config, $sizes) = @_;
my $config = $params{config};
my @items = @{$params{items}}; # can be Model or Object objects, they have to implement size()
if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) {
if (@items > 1) {
die "Grid duplication is not supported with multiple objects\n";
}
my @positions = ();
my $size = $items[0]->size;
my $dist = $config->duplicate_distance;
for my $x_copy (1..$config->duplicate_grid->[X]) {
for my $y_copy (1..$config->duplicate_grid->[Y]) {
push @positions, [
($size->[X] + $dist) * ($x_copy-1),
($size->[Y] + $dist) * ($y_copy-1),
];
}
}
return ($config->duplicate_grid->[X] * $config->duplicate_grid->[Y]), @positions;
} else {
my $total_parts = $config->duplicate * @items;
my @sizes = map $_->size, @items;
my $partx = max(map $_->[X], @sizes);
my $party = max(map $_->[Y], @sizes);
return $config->duplicate,
Slic3r::Geometry::arrange
($total_parts, $partx, $party, (map $_, @{$config->bed_size}),
$config->min_object_distance, $config);
}
my $partx = max(map $_->[X], @$sizes);
my $party = max(map $_->[Y], @$sizes);
return Slic3r::Geometry::arrange
(scalar(@$sizes), $partx, $party, (map $_, @{$config->bed_size}),
$config->min_object_distance, $config);
}
sub size {
my $self = shift;
return $self->bounding_box->size;
sub has_objects_with_no_instances {
my ($self) = @_;
return (first { !defined $_->instances } @{$self->objects}) ? 1 : 0;
}
# this returns the bounding box of the *transformed* instances
sub bounding_box {
my $self = shift;
if (!defined $self->_bounding_box) {
$self->_bounding_box(Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, @{$self->objects}));
}
return $self->_bounding_box->clone;
return Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, @{ $self->objects });
}
sub align_to_origin {
@ -181,52 +208,33 @@ sub align_to_origin {
# have lowest value for each axis at coordinate 0
{
my $bb = $self->bounding_box;
$self->move(map -$bb->extents->[$_][MIN], X,Y,Z);
$self->translate(map -$bb->extents->[$_][MIN], X,Y,Z);
}
# align all instances to 0,0 as well
{
my @instances = map @{$_->instances}, @{$self->objects};
my @extents = Slic3r::Geometry::bounding_box_3D([ map $_->offset, @instances ]);
$_->offset->translate(-$extents[X][MIN], -$extents[Y][MIN]) for @instances;
foreach my $instance (@instances) {
$instance->offset->[X] -= $extents[X][MIN];
$instance->offset->[Y] -= $extents[Y][MIN];
}
}
}
sub scale {
my $self = shift;
$_->scale(@_) for @{$self->objects};
$self->_bounding_box->scale(@_) if defined $self->_bounding_box;
}
sub move {
sub translate {
my $self = shift;
my @shift = @_;
$_->move(@shift) for @{$self->objects};
$self->_bounding_box->translate(@shift) if defined $self->_bounding_box;
$_->translate(@shift) for @{$self->objects};
}
# flattens everything to a single mesh
sub mesh {
my $self = shift;
my @meshes = ();
foreach my $object (@{$self->objects}) {
my @instances = $object->instances ? @{$object->instances} : (undef);
foreach my $instance (@instances) {
my $mesh = $object->mesh->clone;
if ($instance) {
$mesh->align_to_origin;
$mesh->rotate($instance->rotation, $object->center_2D);
$mesh->scale($instance->scaling_factor);
$mesh->translate(@{$instance->offset}, 0);
}
push @meshes, $mesh;
}
}
my $mesh = Slic3r::TriangleMesh->new;
$mesh->merge($_) for @meshes;
$mesh->merge($_->mesh) for @{$self->objects};
return $mesh;
}
@ -249,24 +257,19 @@ sub split_meshes {
foreach my $mesh (@{$volume->mesh->split}) {
my $new_object = $self->add_object(
input_file => $object->input_file,
config => $object->config,
layer_height_ranges => $object->layer_height_ranges,
config => $object->config->clone,
layer_height_ranges => $object->layer_height_ranges, # TODO: this needs to be cloned
);
$new_object->add_volume(
mesh => $mesh,
material_id => $volume->material_id,
);
# let's now align the new object to the origin and put its displacement
# (extents) in the instances info
my $bb = $mesh->bounding_box;
$new_object->align_to_origin;
# add one instance per original instance applying the displacement
# add one instance per original instance
$new_object->add_instance(
offset => [ $_->offset->[X] + $bb->x_min, $_->offset->[Y] + $bb->y_min ],
rotation => $_->rotation,
scaling_factor => $_->scaling_factor,
offset => [ @{$_->offset} ],
rotation => $_->rotation,
scaling_factor => $_->scaling_factor,
) for @{ $object->instances // [] };
}
}
@ -302,17 +305,16 @@ use Moo;
use File::Basename qw(basename);
use List::Util qw(first sum);
use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D);
use Storable qw(dclone);
use Slic3r::Geometry qw(X Y Z MIN MAX);
has 'input_file' => (is => 'rw');
has 'model' => (is => 'ro', weak_ref => 1, required => 1);
has 'volumes' => (is => 'ro', default => sub { [] });
has 'instances' => (is => 'rw'); # in unscaled coordinates
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new });
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => extruder_idx }
has '_bounding_box' => (is => 'rw');
has 'input_file' => (is => 'rw');
has 'model' => (is => 'ro', weak_ref => 1, required => 1);
has 'volumes' => (is => 'ro', default => sub { [] });
has 'instances' => (is => 'rw');
has 'config' => (is => 'rw', default => sub { Slic3r::Config->new });
has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ]
has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => region_idx }
has '_bounding_box' => (is => 'rw');
sub add_volume {
my $self = shift;
@ -323,54 +325,78 @@ sub add_volume {
%args,
);
$self->_bounding_box(undef);
$self->model->_bounding_box(undef);
return $volume;
}
sub add_instance {
my $self = shift;
my %params = @_;
$self->instances([]) if !defined $self->instances;
push @{$self->instances}, Slic3r::Model::Instance->new(object => $self, @_);
$self->model->_bounding_box(undef);
return $self->instances->[-1];
push @{$self->instances}, my $i = Slic3r::Model::Instance->new(object => $self, %params);
$self->_bounding_box(undef);
return $i;
}
sub mesh {
sub delete_last_instance {
my ($self) = @_;
pop @{$self->instances};
$self->_bounding_box(undef);
}
sub instances_count {
my $self = shift;
return scalar(@{ $self->instances // [] });
}
sub raw_mesh {
my $self = shift;
my $mesh = Slic3r::TriangleMesh->new;
$mesh->merge($_->mesh) for @{$self->volumes};
$mesh->merge($_->mesh) for @{ $self->volumes };
return $mesh;
}
sub size {
# flattens all volumes and instances into a single mesh
sub mesh {
my $self = shift;
return $self->bounding_box->size;
my $mesh = $self->raw_mesh;
my @instance_meshes = ();
foreach my $instance (@{ $self->instances }) {
my $m = $mesh->clone;
$instance->transform_mesh($m);
push @instance_meshes, $m;
}
my $full_mesh = Slic3r::TriangleMesh->new;
$full_mesh->merge($_) for @instance_meshes;
return $full_mesh;
}
sub center {
my $self = shift;
return $self->bounding_box->center;
}
sub center_2D {
my $self = shift;
return $self->bounding_box->center_2D;
sub update_bounding_box {
my ($self) = @_;
$self->_bounding_box($self->mesh->bounding_box);
}
# this returns the bounding box of the *transformed* instances
sub bounding_box {
my $self = shift;
if (!defined $self->_bounding_box) {
my @meshes = map $_->mesh, @{$self->volumes};
my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_bb((shift @meshes)->bb3);
$bounding_box->merge(Slic3r::Geometry::BoundingBox->new_from_bb($_->bb3)) for @meshes;
$self->_bounding_box($bounding_box);
}
$self->update_bounding_box if !defined $self->_bounding_box;
return $self->_bounding_box->clone;
}
# this returns the bounding box of the *transformed* given instance
sub instance_bounding_box {
my ($self, $instance_idx) = @_;
my $mesh = $self->raw_mesh;
$self->instances->[$instance_idx]->transform_mesh($mesh);
return $mesh->bounding_box;
}
sub align_to_origin {
my $self = shift;
@ -378,11 +404,38 @@ sub align_to_origin {
# have lowest value for each axis at coordinate 0
my $bb = $self->bounding_box;
my @shift = map -$bb->extents->[$_][MIN], X,Y,Z;
$self->move(@shift);
$self->translate(@shift);
return @shift;
}
sub move {
sub center_around_origin {
my $self = shift;
# calculate the displacements needed to
# center this object around the origin
my $bb = $self->raw_mesh->bounding_box;
# first align to origin on XYZ
my @shift = @{ $bb->vector_to_origin };
# then center it on XY
my $size = $bb->size;
$shift[$_] -= $size->[$_]/2 for X,Y;
$self->translate(@shift);
if (defined $self->instances) {
foreach my $instance (@{ $self->instances }) {
$instance->offset->[X] -= $shift[X];
$instance->offset->[Y] -= $shift[Y];
}
$self->update_bounding_box;
}
return @shift;
}
sub translate {
my $self = shift;
my @shift = @_;
@ -390,24 +443,6 @@ sub move {
$self->_bounding_box->translate(@shift) if defined $self->_bounding_box;
}
sub scale {
my $self = shift;
my ($factor) = @_;
return if $factor == 1;
$_->mesh->scale($factor) for @{$self->volumes};
$self->_bounding_box->scale($factor) if defined $self->_bounding_box;
}
sub rotate {
my $self = shift;
my ($deg) = @_;
return if $deg == 0;
$_->mesh->rotate($deg, Slic3r::Point->(0,0)) for @{$self->volumes};
$self->_bounding_box(undef);
}
sub materials_count {
my $self = shift;
@ -425,7 +460,7 @@ sub unique_materials {
sub facets_count {
my $self = shift;
return sum(map $_->facets_count, @{$self->volumes});
return sum(map $_->mesh->facets_count, @{$self->volumes});
}
sub needed_repair {
@ -465,21 +500,27 @@ sub print_info {
}
}
sub clone { dclone($_[0]) }
package Slic3r::Model::Volume;
use Moo;
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
has 'material_id' => (is => 'rw');
has 'mesh' => (is => 'rw', required => 1);
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
has 'material_id' => (is => 'rw');
has 'mesh' => (is => 'rw', required => 1);
package Slic3r::Model::Instance;
use Moo;
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point
has 'scaling_factor' => (is => 'rw', default => sub { 1 });
has 'offset' => (is => 'rw'); # must be Slic3r::Point object in scaled coordinates
has 'object' => (is => 'ro', weak_ref => 1, required => 1);
has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point
has 'scaling_factor' => (is => 'rw', default => sub { 1 });
has 'offset' => (is => 'rw'); # must be arrayref in *unscaled* coordinates
sub transform_mesh {
my ($self, $mesh) = @_;
$mesh->rotate($self->rotation, Slic3r::Point->new(0,0)); # rotate around mesh origin
$mesh->scale($self->scaling_factor); # scale around mesh origin
$mesh->translate(@{$self->offset}, 0);
}
1;

View File

@ -77,89 +77,76 @@ sub _build_has_support_material {
# caller is responsible for supplying models whose objects don't collide
# and have explicit instance positions
sub add_model {
sub add_model_object {
my $self = shift;
my ($model) = @_;
my ($object) = @_;
# optimization: if avoid_crossing_perimeters is enabled, split
# this mesh into distinct objects so that we reduce the complexity
# of the graphs
# -- Disabling this one because there are too many legit objects having nested shells
# -- It also caused a bug where plater rotation was applied to each single object by the
# -- code below (thus around its own center), instead of being applied to the whole
# -- thing before the split.
###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects;
# read the material mapping provided by the model object, if any
my %matmap = %{ $object->material_mapping || {} };
$_-- for values %matmap; # extruders in the mapping are 1-indexed but we want 0-indexed
my %unmapped_materials = ();
foreach my $object (@{ $model->objects }) {
# we align object to origin before applying transformations
my @align = $object->align_to_origin;
# extract meshes by material
my @meshes = (); # by region_id
foreach my $volume (@{$object->volumes}) {
my $region_id;
if (defined $volume->material_id) {
if ($object->material_mapping) {
$region_id = $object->material_mapping->{$volume->material_id} - 1
if defined $object->material_mapping->{$volume->material_id};
}
$region_id //= $unmapped_materials{$volume->material_id};
if (!defined $region_id) {
$region_id = $unmapped_materials{$volume->material_id} = scalar(keys %unmapped_materials);
}
my %meshes = (); # region_id => TriangleMesh
foreach my $volume (@{$object->volumes}) {
my $region_id;
if (defined $volume->material_id) {
if (!exists $matmap{ $volume->material_id }) {
# there's no mapping between this material and a region
$matmap{ $volume->material_id } = scalar(@{ $self->regions });
}
$region_id //= 0;
my $mesh = $volume->mesh->clone;
# should the object contain multiple volumes of the same material, merge them
$meshes[$region_id] = $meshes[$region_id]
? Slic3r::TriangleMesh->merge($meshes[$region_id], $mesh)
: $mesh;
}
$self->regions->[$_] //= Slic3r::Print::Region->new for 0..$#meshes;
foreach my $mesh (grep $_, @meshes) {
# the order of these transformations must be the same as the one used in plater
# to make the object positioning consistent with the visual preview
# we ignore the per-instance transformations currently and only
# consider the first one
if ($object->instances && @{$object->instances}) {
$mesh->rotate($object->instances->[0]->rotation, $object->center_2D);
$mesh->scale($object->instances->[0]->scaling_factor);
}
$mesh->repair;
$region_id = $matmap{ $volume->material_id };
} else {
$region_id = 0;
}
# we also align object after transformations so that we only work with positive coordinates
# and the assumption that bounding_box === size works
my $bb = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, grep $_, @meshes);
my @align2 = map -$bb->extents->[$_][MIN], (X,Y,Z);
$_->translate(@align2) for grep $_, @meshes;
# instantiate region if it does not exist
$self->regions->[$region_id] //= Slic3r::Print::Region->new;
my $scaled_bb = $bb->clone;
$scaled_bb->scale(1 / &Slic3r::SCALING_FACTOR);
# initialize print object
push @{$self->objects}, Slic3r::Print::Object->new(
print => $self,
meshes => [ @meshes ],
copies => [
map Slic3r::Point->new(@$_),
$object->instances
? (map [ scale($_->offset->[X] - $align[X]) - $align2[X], scale($_->offset->[Y] - $align[Y]) - $align2[Y] ], @{$object->instances})
: [0,0],
],
size => $scaled_bb->size, # transformed size
input_file => $object->input_file,
config_overrides => $object->config,
layer_height_ranges => $object->layer_height_ranges,
# if a mesh is already associated to this region, append this one to it
$meshes{$region_id} //= Slic3r::TriangleMesh->new;
$meshes{$region_id}->merge($volume->mesh);
}
# bounding box of the original meshes in original position in unscaled coordinates
my $bb1 = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, values %meshes);
foreach my $mesh (values %meshes) {
# we ignore the per-instance transformations currently and only
# consider the first one
$object->instances->[0]->transform_mesh($mesh);
}
# we align object also after transformations so that we only work with positive coordinates
# and the assumption that bounding_box === size works
my $bb2 = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, values %meshes);
$_->translate(@{$bb2->vector_to_origin}) for values %meshes;
# prepare scaled object size
my $scaled_bb = $bb2->clone;
$scaled_bb->translate(@{$bb2->vector_to_origin}); # not needed for getting size, but who knows
$scaled_bb->scale(1 / &Slic3r::SCALING_FACTOR);
# prepare copies
my @copies = ();
foreach my $instance (@{ $object->instances }) {
push @copies, Slic3r::Point->new(
scale($instance->offset->[X] - $bb1->extents->[X][MIN]),
scale($instance->offset->[Y] - $bb1->extents->[Y][MIN]),
);
}
# initialize print object
push @{$self->objects}, Slic3r::Print::Object->new(
print => $self,
meshes => [ map $meshes{$_}, 0..$#{$self->regions} ],
copies => [ @copies ],
size => $scaled_bb->size, # transformed size
input_file => $object->input_file,
config_overrides => $object->config,
layer_height_ranges => $object->layer_height_ranges,
);
if (!defined $self->extra_variables->{input_filename}) {
if (defined (my $input_file = $self->objects->[0]->input_file)) {
if (defined (my $input_file = $object->input_file)) {
@{$self->extra_variables}{qw(input_filename input_filename_base)} = parse_filename($input_file);
}
}

View File

@ -118,7 +118,6 @@ sub slice {
# process facets
for my $region_id (0 .. $#{$self->meshes}) {
my $mesh = $self->meshes->[$region_id] // next; # ignore undef meshes
$mesh->repair;
{
my $loops = $mesh->slice([ map $_->slice_z, @{$self->layers} ]);

View File

@ -108,7 +108,7 @@ sub init_print {
$model_name = [$model_name] if ref($model_name) ne 'ARRAY';
for my $model (map model($_, %params), @$model_name) {
$model->arrange_objects($config);
$print->add_model($model);
$print->add_model_object($_) for @{$model->objects};
}
$print->validate;

View File

@ -12,6 +12,7 @@ use Getopt::Long qw(:config no_auto_abbrev);
use List::Util qw(first);
use POSIX qw(setlocale LC_NUMERIC);
use Slic3r;
use Slic3r::Geometry qw(X Y);
$|++;
our %opt = ();
@ -119,9 +120,30 @@ if (@ARGV) { # slicing from command line
} else {
$model = Slic3r::Model->read_from_file($input_file);
}
$_->scale($config->scale) for @{$model->objects};
$_->rotate($config->rotate) for @{$model->objects};
$model->arrange_objects($config);
my $need_arrange = $model->has_objects_with_no_instances;
if ($need_arrange) {
# apply a default position to all objects not having one
foreach my $object (@{$model->objects}) {
$object->add_instance(offset => [0,0]) if !defined $object->instances;
}
}
# apply scaling and rotation supplied from command line if any
foreach my $instance (map @{$_->instances}, @{$model->objects}) {
$instance->scaling_factor($instance->scaling_factor * $config->scale);
$instance->rotation($instance->rotation + $config->rotate);
}
# TODO: --scale --rotate, --duplicate* shouldn't be stored in config
if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) {
$model->duplicate_objects_grid($config->duplicate_grid, $config->duplicate_distance);
} elsif ($need_arrange) {
$model->duplicate_objects($config, $config->duplicate);
} elsif ($config->duplicate > 1) {
# if all input objects have defined position(s) apply duplication to the whole model
$model->duplicate($config, $config->duplicate);
}
if ($opt{info}) {
$model->print_info;
@ -129,7 +151,7 @@ if (@ARGV) { # slicing from command line
}
my $print = Slic3r::Print->new(config => $config);
$print->add_model($model);
$print->add_model_object($_) for @{$model->objects};
$print->validate;
my %params = (
output_file => $opt{output},

View File

@ -1,4 +1,5 @@
#include "ExPolygonCollection.hpp"
#include "Geometry.hpp"
namespace Slic3r {
@ -57,4 +58,13 @@ ExPolygonCollection::simplify(double tolerance)
this->expolygons = expp;
}
void
ExPolygonCollection::convex_hull(Polygon* hull) const
{
Points pp;
for (ExPolygons::const_iterator it = this->expolygons.begin(); it != this->expolygons.end(); ++it)
pp.insert(pp.end(), it->contour.points.begin(), it->contour.points.end());
Slic3r::Geometry::convex_hull(pp, hull);
}
}

View File

@ -16,6 +16,7 @@ class ExPolygonCollection
void rotate(double angle, Point* center);
bool contains_point(const Point* point) const;
void simplify(double tolerance);
void convex_hull(Polygon* hull) const;
};
}

View File

@ -2,6 +2,7 @@
#include "clipper.hpp"
#include <algorithm>
#include <map>
#include <vector>
namespace Slic3r { namespace Geometry {
@ -11,45 +12,33 @@ sort_points (Point a, Point b)
return (a.x < b.x) || (a.x == b.x && a.y < b.y);
}
/* This implementation is based on Steffen Mueller's work for
the Perl module Math::ConvexHull::MonotoneChain (available
on CPAN under the GPL terms) */
/* This implementation is based on Andrew's monotone chain 2D convex hull algorithm */
void
convex_hull(Points points, Polygon &hull)
convex_hull(Points &points, Polygon* hull)
{
assert(points.size() >= 3);
// sort input points
std::sort(points.begin(), points.end(), sort_points);
typedef const Point* PointPtr;
PointPtr* out_hull = (PointPtr*)malloc(points.size()*2*sizeof(PointPtr));
/* lower hull */
size_t k = 0;
for (Points::const_iterator it = points.begin(); it != points.end(); ++it) {
while (k >= 2 && it->ccw(out_hull[k-2], out_hull[k-1]) <= 0) --k;
Point pz = *&*it;
out_hull[k++] = &*it;
int n = points.size(), k = 0;
hull->points.resize(2*n);
// Build lower hull
for (int i = 0; i < n; i++) {
while (k >= 2 && points[i].ccw(hull->points[k-2], hull->points[k-1]) <= 0) k--;
hull->points[k++] = points[i];
}
/* upper hull */
size_t t = k+1;
for (Points::const_iterator it = points.end() - 2; it != points.begin(); --it) {
while (k >= t && it->ccw(out_hull[k-2], out_hull[k-1]) <= 0) --k;
out_hull[k++] = &*it;
// Build upper hull
for (int i = n-2, t = k+1; i >= 0; i--) {
while (k >= t && points[i].ccw(hull->points[k-2], hull->points[k-1]) <= 0) k--;
hull->points[k++] = points[i];
}
hull->points.resize(k);
// we assume hull is empty
hull.points.reserve(k);
for (size_t i = 0; i < k; ++i) {
hull.points.push_back(*(out_hull[i]));
}
// not sure why this happens randomly
if (hull.points.front().coincides_with(hull.points.back()))
hull.points.pop_back();
free(out_hull);
assert( hull->points.front().coincides_with(hull->points.back()) );
hull->points.pop_back();
}
/* accepts an arrayref of points and returns a list of indices

View File

@ -5,7 +5,7 @@
namespace Slic3r { namespace Geometry {
void convex_hull(Points points, Polygon &hull);
void convex_hull(Points &points, Polygon* hull);
void chained_path(Points &points, std::vector<Points::size_type> &retval, Point start_near);
void chained_path(Points &points, std::vector<Points::size_type> &retval);
template<class T> void chained_path_items(Points &points, T &items, T &retval);

View File

@ -109,6 +109,9 @@ Point::distance_to(const Line &line) const
/* Three points are a counter-clockwise turn if ccw > 0, clockwise if
* ccw < 0, and collinear if ccw = 0 because ccw is a determinant that
* gives the signed area of the triangle formed by p1, p2 and this point.
* In other words it is the 2D cross product of p1-p2 and p1-this, i.e.
* z-component of their 3D cross product.
* We return double because it must be big enough to hold 2*max(|coordinate|)^2
*/
double
Point::ccw(const Point &p1, const Point &p2) const

View File

@ -191,6 +191,8 @@ TriangleMesh::slice(const std::vector<double> &z, std::vector<Polygons> &layers)
FUTURE: parallelize slice_facet() and make_loops()
*/
if (!this->repaired) this->repair();
// build a table to map a facet_idx to its three edge indices
if (this->stl.v_shared == NULL) stl_generate_shared_vertices(&(this->stl));
typedef std::pair<int,int> t_edge;
@ -603,7 +605,7 @@ TriangleMesh::horizontal_projection(ExPolygons &retval) const
}
void
TriangleMesh::convex_hull(Polygon &hull)
TriangleMesh::convex_hull(Polygon* hull)
{
if (this->stl.v_shared == NULL) stl_generate_shared_vertices(&(this->stl));
Points pp;

View File

@ -33,7 +33,7 @@ class TriangleMesh
TriangleMeshPtrs split() const;
void merge(const TriangleMesh* mesh);
void horizontal_projection(ExPolygons &retval) const;
void convex_hull(Polygon &hull);
void convex_hull(Polygon* hull);
stl_file stl;
bool repaired;

View File

@ -20,6 +20,7 @@ extern "C" {
#define EPSILON 1e-4
#define SCALING_FACTOR 0.000001
#define scale_(val) (val / SCALING_FACTOR)
#define unscale(val) (val * SCALING_FACTOR)
namespace Slic3r {}

View File

@ -68,5 +68,15 @@ ExPolygonCollection::append(...)
THIS->expolygons.push_back(expolygon);
}
Polygon*
ExPolygonCollection::convex_hull()
PREINIT:
const char* CLASS = "Slic3r::Polygon";
CODE:
RETVAL = new Polygon ();
THIS->convex_hull(RETVAL);
OUTPUT:
RETVAL
%}
};

View File

@ -17,7 +17,7 @@ convex_hull(points)
const char* CLASS = "Slic3r::Polygon";
CODE:
RETVAL = new Polygon ();
Slic3r::Geometry::convex_hull(points, *RETVAL);
Slic3r::Geometry::convex_hull(points, RETVAL);
OUTPUT:
RETVAL

View File

@ -167,7 +167,7 @@ TriangleMesh::convex_hull()
const char* CLASS = "Slic3r::Polygon";
CODE:
RETVAL = new Polygon ();
THIS->convex_hull(*RETVAL);
THIS->convex_hull(RETVAL);
OUTPUT:
RETVAL