Merge branch 'xs-model'
Conflicts: lib/Slic3r/Model.pm
This commit is contained in:
commit
21ca1901c1
@ -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;
|
||||
|
@ -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 ],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
@ -557,19 +572,14 @@ 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 $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;
|
||||
@ -579,31 +589,38 @@ sub split_object {
|
||||
# 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);
|
||||
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),
|
||||
);
|
||||
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] }) {
|
||||
if (defined first { $_->contains_point($pos) } map @{$_->instance_thumbnails}, @{ $parent->{objects} }) {
|
||||
$cursor = Wx::Cursor->new(wxCURSOR_HAND);
|
||||
last;
|
||||
}
|
||||
}
|
||||
$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 {
|
||||
@ -1121,6 +1112,7 @@ sub object_preview_dialog {
|
||||
|
||||
my $dlg = Slic3r::GUI::Plater::ObjectPreviewDialog->new($self,
|
||||
object => $self->{objects}[$obj_idx],
|
||||
model_object => $self->{model}->objects->[$obj_idx],
|
||||
);
|
||||
$dlg->ShowModal;
|
||||
}
|
||||
@ -1140,6 +1132,7 @@ sub object_settings_dialog {
|
||||
|
||||
my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self,
|
||||
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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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]);
|
||||
|
@ -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
|
||||
die "Grid duplication is not supported with multiple objects\n"
|
||||
if @{$self->objects} > 1;
|
||||
|
||||
my ($copies, @positions) = $self->_arrange(
|
||||
config => $config,
|
||||
items => $self->objects,
|
||||
);
|
||||
my $object = $self->objects->[0];
|
||||
@{$object->instances} = ();
|
||||
|
||||
# apply positions to objects
|
||||
foreach my $object (@{$self->objects}) {
|
||||
$object->align_to_origin;
|
||||
|
||||
$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
|
||||
# this will append more instances to each object
|
||||
# and then automatically rearrange everything
|
||||
sub duplicate_objects {
|
||||
my ($self, $config, $copies_num) = @_;
|
||||
|
||||
# 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) {
|
||||
my @instances = @{$object->instances};
|
||||
foreach my $instance (@instances) {
|
||||
### $object->add_instance($instance->clone); if we had clone()
|
||||
$object->add_instance(
|
||||
offset => $_,
|
||||
offset => [ @{$instance->offset} ],
|
||||
rotation => $instance->rotation,
|
||||
scaling_factor => $instance->scaling_factor,
|
||||
) for move_points($instance->offset, @positions);
|
||||
) 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 => [ $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}),
|
||||
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,22 +257,17 @@ 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 ],
|
||||
offset => [ @{$_->offset} ],
|
||||
rotation => $_->rotation,
|
||||
scaling_factor => $_->scaling_factor,
|
||||
) for @{ $object->instances // [] };
|
||||
@ -302,16 +305,15 @@ 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 '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 => extruder_idx }
|
||||
has 'material_mapping' => (is => 'rw', default => sub { {} }); # { material_id => region_idx }
|
||||
has '_bounding_box' => (is => 'rw');
|
||||
|
||||
sub add_volume {
|
||||
@ -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,8 +500,6 @@ sub print_info {
|
||||
}
|
||||
}
|
||||
|
||||
sub clone { dclone($_[0]) }
|
||||
|
||||
package Slic3r::Model::Volume;
|
||||
use Moo;
|
||||
|
||||
@ -480,6 +513,14 @@ 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 '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;
|
||||
|
@ -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
|
||||
my %meshes = (); # region_id => TriangleMesh
|
||||
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};
|
||||
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 //= $unmapped_materials{$volume->material_id};
|
||||
if (!defined $region_id) {
|
||||
$region_id = $unmapped_materials{$volume->material_id} = scalar(keys %unmapped_materials);
|
||||
$region_id = $matmap{ $volume->material_id };
|
||||
} else {
|
||||
$region_id = 0;
|
||||
}
|
||||
}
|
||||
$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;
|
||||
# instantiate region if it does not exist
|
||||
$self->regions->[$region_id] //= Slic3r::Print::Region->new;
|
||||
|
||||
# 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);
|
||||
}
|
||||
$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
|
||||
# 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
|
||||
if ($object->instances && @{$object->instances}) {
|
||||
$mesh->rotate($object->instances->[0]->rotation, $object->center_2D);
|
||||
$mesh->scale($object->instances->[0]->scaling_factor);
|
||||
}
|
||||
$mesh->repair;
|
||||
$object->instances->[0]->transform_mesh($mesh);
|
||||
}
|
||||
|
||||
# we also align object after transformations so that we only work with positive coordinates
|
||||
# we align object also 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;
|
||||
my $bb2 = Slic3r::Geometry::BoundingBox->merge(map $_->bounding_box, values %meshes);
|
||||
$_->translate(@{$bb2->vector_to_origin}) for values %meshes;
|
||||
|
||||
my $scaled_bb = $bb->clone;
|
||||
# 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 => [ @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],
|
||||
],
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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} ]);
|
||||
|
@ -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;
|
||||
|
||||
|
30
slic3r.pl
30
slic3r.pl
@ -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},
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
int n = points.size(), k = 0;
|
||||
hull->points.resize(2*n);
|
||||
|
||||
/* 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;
|
||||
// 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];
|
||||
}
|
||||
|
||||
// we assume hull is empty
|
||||
hull.points.reserve(k);
|
||||
for (size_t i = 0; i < k; ++i) {
|
||||
hull.points.push_back(*(out_hull[i]));
|
||||
}
|
||||
hull->points.resize(k);
|
||||
|
||||
// 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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {}
|
||||
|
@ -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
|
||||
|
||||
%}
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user