From da1b6117df277fcf4ff504b5ba34e5c20b7048f7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 19 Aug 2013 12:16:19 +0200 Subject: [PATCH 01/17] Minor fix in debug code --- lib/Slic3r/SVG.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 5b4d0a19..30d03a3f 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -79,7 +79,7 @@ sub output { ); $g->$method( %$path, - 'marker-end' => $arrows ? "" : "url(#endArrow)", + 'marker-end' => !$arrows ? "" : "url(#endArrow)", ); } } elsif ($type =~ /^(?:(.+?)_)?points$/) { @@ -117,7 +117,7 @@ sub output { style => { 'stroke' => $colour || 'black', }, - 'marker-end' => $arrows ? "" : "url(#endArrow)", + 'marker-end' => !$arrows ? "" : "url(#endArrow)", ); } } From 097912755b327c6e734fb03083f91b5ed881d34e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 19 Aug 2013 16:53:54 +0200 Subject: [PATCH 02/17] Fix handling of dirty models with overlapping facets, where some holes became filled because of wrong slice nesting --- lib/Slic3r/Layer/Region.pm | 42 +++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index aa758a86..1144e23a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -92,6 +92,16 @@ sub make_surfaces { return if !@$loops; $self->slices([ _merge_loops($loops) ]); + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("surfaces.svg", + #polylines => $loops, + red_polylines => [ grep $_->is_counter_clockwise, @$loops ], + green_polylines => [ grep !$_->is_counter_clockwise, @$loops ], + expolygons => [ map $_->expolygon, @{$self->slices} ], + ); + } + # detect thin walls by offsetting slices by half extrusion inwards if ($Slic3r::Config->thin_walls) { $self->thin_walls([]); @@ -111,14 +121,6 @@ sub make_surfaces { Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}); } } - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("surfaces.svg", - polygons => [ map $_->contour, @{$self->slices} ], - red_polygons => [ map $_->p, map @{$_->holes}, @{$self->slices} ], - ); - } } sub _merge_loops { @@ -130,14 +132,26 @@ sub _merge_loops { # would ignore holes inside two concentric contours. # So we're ordering loops and collapse consecutive concentric loops having the same # winding order. - # TODO: find a faster algorithm for this. - my @loops = sort { $a->encloses_point($b->[0]) ? 0 : 1 } @$loops; # outer first + # TODO: find a faster algorithm for this, maybe with some sort of binary search. + # If we computed a "nesting tree" we could also just remove the consecutive loops + # having the same winding order, and remove the extra one(s) so that we could just + # supply everything to offset_ex() instead of performing several union/diff calls. + + # we sort by area assuming that the outermost loops have larger area; + # the previous sorting method, based on $b->encloses_point($a->[0]), failed to nest + # loops correctly in some edge cases when original model had overlapping facets + my @abs_area = map abs($_), my @area = map $_->area, @$loops; + my @sorted = sort { $abs_area[$b] <=> $abs_area[$a] } 0..$#$loops; # outer first + # we don't perform a safety offset now because it might reverse cw loops my $slices = []; - foreach my $loop (@loops) { - $slices = $loop->is_counter_clockwise - ? union([ $loop, @$slices ]) - : diff($slices, [$loop]); + for my $i (@sorted) { + # we rely on the already computed area to determine the winding order + # of the loops, since the Orientation() function provided by Clipper + # would do the same, thus repeating the calculation + $slices = ($area[$i] >= 0) + ? union([ $loops->[$i], @$slices ]) + : diff($slices, [$loops->[$i]]); } # perform a safety offset to merge very close facets (TODO: find test case for this) From af687a98e23f088692c9d095910c31c98ea8ea98 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 Aug 2013 11:59:51 +0200 Subject: [PATCH 03/17] Better compatibility with Windows in Build.PL --- Build.PL | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index 07a7f0b3..16951d7a 100644 --- a/Build.PL +++ b/Build.PL @@ -89,6 +89,11 @@ EOF # make sure our cpanm is updated (old ones don't support the ~ syntax) system $cpanm, 'App::cpanminus'; + # install the Windows-compatible Math::Libm + if ($^O eq 'MSWin32') { + system $cpanm, 'https://github.com/alexrj/Math-Libm/tarball/master'; + } + my %modules = (%prereqs, %recommends); foreach my $module (sort keys %modules) { my $version = $modules{$module}; @@ -99,7 +104,18 @@ EOF # temporarily require this dev version until this upstream bug # is resolved: https://rt.cpan.org/Ticket/Display.html?id=86367 system $cpanm, 'SMUELLER/ExtUtils-ParseXS-3.18_04.tar.gz'; - system './xs/Build', 'distclean' if -e './xs/Build'; + + # clean xs directory before reinstalling, to make sure Build is called + # with current perl binary + if (-e './xs/Build') { + if ($^O eq 'MSWin32') { + system 'pushd', 'xs'; + system 'Build', 'distclean'; + system 'popd'; + } else { + system './xs/Build', 'distclean'; + } + } system $cpanm, '--reinstall', './xs'; } From e3b42cd21e3bd7770b492821a724bb233f7019df Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 Aug 2013 12:01:56 +0200 Subject: [PATCH 04/17] Avoid reinstalling Math::Libm on Windows every time --- Build.PL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index 16951d7a..8f4b53ff 100644 --- a/Build.PL +++ b/Build.PL @@ -90,7 +90,7 @@ EOF system $cpanm, 'App::cpanminus'; # install the Windows-compatible Math::Libm - if ($^O eq 'MSWin32') { + if ($^O eq 'MSWin32' && !eval "use Math::Libm; 1") { system $cpanm, 'https://github.com/alexrj/Math-Libm/tarball/master'; } From ed0344e8615a741e96b9670e5186ee3f4b2c1a2f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 24 Aug 2013 12:12:46 +0200 Subject: [PATCH 05/17] Explain user that he doesn't need to worry if a failed module was optional. One more fix for Windows, also --- Build.PL | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Build.PL b/Build.PL index 8f4b53ff..10a82710 100644 --- a/Build.PL +++ b/Build.PL @@ -98,7 +98,13 @@ EOF foreach my $module (sort keys %modules) { my $version = $modules{$module}; my $res = system $cpanm, "$module~$version"; - $missing_prereqs = 1 if $res != 0 && exists $prereqs{$module}; + if ($res != 0) { + if (exists $prereqs{$module}) { + $missing_prereqs = 1; + } else { + printf "Don't worry, this module is optional.\n"; + } + } } # temporarily require this dev version until this upstream bug @@ -109,9 +115,9 @@ EOF # with current perl binary if (-e './xs/Build') { if ($^O eq 'MSWin32') { - system 'pushd', 'xs'; + system 'cd', 'xs'; system 'Build', 'distclean'; - system 'popd'; + system 'cd', '..'; } else { system './xs/Build', 'distclean'; } From 6f1fd51c2e526336b8621f3deb37ab50dbe8ec06 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 02:07:51 +0200 Subject: [PATCH 06/17] Keep objects vertical in 3D preview --- lib/Slic3r/GUI/PreviewCanvas.pm | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index c5c82e94..0b37d50a 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -13,15 +13,19 @@ use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(quat dirty init mview_init mesh_center mesh_size - verts norms initpos) ); + verts norms initpos + sphi stheta) ); use constant TRACKBALLSIZE => 0.8; +use constant TURNTABLE_MODE => 1; sub new { my ($class, $parent, $mesh) = @_; my $self = $class->SUPER::new($parent); $self->quat((0, 0, 0, 1)); + $self->sphi(45); + $self->stheta(-45); # prepare mesh { @@ -189,12 +193,17 @@ sub handle_rotation { my $orig = $self->initpos; my $new = $e->GetPosition(); my $size = $self->GetClientSize(); - my @quat = trackball($orig->x / ($size->width / 2) - 1, - 1 - $orig->y / ($size->height / 2), #/ - $new->x / ($size->width / 2) - 1, - 1 - $new->y / ($size->height / 2), #/ - ); - $self->quat(mulquats($self->quat, \@quat)); + if (TURNTABLE_MODE) { + $self->sphi($self->sphi + ($new->x - $orig->x)*TRACKBALLSIZE); + $self->stheta($self->stheta + ($new->y - $orig->y)*TRACKBALLSIZE); #- + } else { + my @quat = trackball($orig->x / ($size->width / 2) - 1, + 1 - $orig->y / ($size->height / 2), #/ + $new->x / ($size->width / 2) - 1, + 1 - $new->y / ($size->height / 2), #/ + ); + $self->quat(mulquats($self->quat, \@quat)); + } $self->initpos($new); $self->Refresh; } @@ -344,6 +353,8 @@ sub Render { glTranslatef(0, 0, -max(@$mesh_size[0..1])); my @rotmat = quat_to_rotmatrix($self->quat); glMultMatrixd_p(@rotmat[0..15]); + glRotatef($self->stheta, 1, 0, 0); + glRotatef($self->sphi, 0, 0, 1); glTranslatef(map -$_, @{ $self->mesh_center }); $self->draw_mesh; From 945250c8c2edf9d2cc3ed033f284df0a5e70c9a6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 02:58:50 +0200 Subject: [PATCH 07/17] Show axes and ground --- lib/Slic3r/GUI/PreviewCanvas.pm | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 0b37d50a..493d3f0f 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -29,6 +29,7 @@ sub new { # prepare mesh { + $mesh->align_to_origin; $self->mesh_center($mesh->center); $self->mesh_size($mesh->size); @@ -358,6 +359,54 @@ sub Render { glTranslatef(map -$_, @{ $self->mesh_center }); $self->draw_mesh; + + # draw axes + { + my $axis_len = 2 * max(@{ $self->mesh_size }); + glLineWidth(2); + glBegin(GL_LINES); + # draw line for x axis + glColor3f(1, 0, 0); + glVertex3f(0, 0, 0); + glVertex3f($axis_len, 0, 0); + # draw line for y axis + glColor3f(0, 1, 0); + glVertex3f(0, 0, 0); + glVertex3f(0, $axis_len, 0); + # draw line for Z axis + glColor3f(0, 0, 1); + glVertex3f(0, 0, 0); + glVertex3f(0, 0, $axis_len); + glEnd(); + + # draw ground + my $ground_z = -0.02; + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + glColor4f(1, 1, 1, 0.5); + glVertex3f(-$axis_len, -$axis_len, $ground_z); + glVertex3f($axis_len, -$axis_len, $ground_z); + glVertex3f($axis_len, $axis_len, $ground_z); + glVertex3f(-$axis_len, $axis_len, $ground_z); + glEnd(); + glEnable(GL_CULL_FACE); + glDisable(GL_BLEND); + + # draw grid + glBegin(GL_LINES); + glColor3f(1, 1, 1); + for (my $x = -$axis_len; $x <= $axis_len; $x += 10) { + glVertex3f($x, -$axis_len, $ground_z); + glVertex3f($x, $axis_len, $ground_z); + } + for (my $y = -$axis_len; $y <= $axis_len; $y += 10) { + glVertex3f(-$axis_len, $y, $ground_z); + glVertex3f($axis_len, $y, $ground_z); + } + glEnd(); + } glPopMatrix(); glFlush(); @@ -376,6 +425,7 @@ sub draw_mesh { glCullFace(GL_BACK); glNormalPointer_p($self->norms); + glColor3f(1, 1, 1); glDrawArrays(GL_TRIANGLES, 0, $self->verts->elements / 3); glDisableClientState(GL_NORMAL_ARRAY); From 2dd6325bf887ff672b1e6a5580e64cd2eb4f6bd5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 03:21:20 +0200 Subject: [PATCH 08/17] Expose an Open button in the plater --- MANIFEST | 1 + lib/Slic3r/GUI/Plater.pm | 19 ++++++++++++------- var/package.png | Bin 0 -> 853 bytes 3 files changed, 13 insertions(+), 7 deletions(-) create mode 100755 var/package.png diff --git a/MANIFEST b/MANIFEST index 3abe3b6f..57f9e42e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -122,6 +122,7 @@ var/funnel.png var/hourglass.png var/layers.png var/note.png +var/package.png var/page_white_go.png var/printer_empty.png var/shading.png diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 57ad8e8d..7a1dca89 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -15,6 +15,7 @@ use base 'Wx::Panel'; use constant TB_MORE => &Wx::NewId; use constant TB_LESS => &Wx::NewId; +use constant TB_INFO => &Wx::NewId; use constant TB_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; use constant TB_ROTATE => &Wx::NewId; @@ -61,21 +62,21 @@ 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_INFO, "Open", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_LESS, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ROTATE, "Rotate…", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), ''); } else { - my %tbar_buttons = (increase => "More", decrease => "Less", rotate45ccw => "45°", rotate45cw => "45°", + my %tbar_buttons = (info => "Open", increase => "More", decrease => "Less", rotate45ccw => "45°", rotate45cw => "45°", rotate => "Rotate…", changescale => "Scale…", split => "Split"); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); - for (qw(increase decrease rotate45ccw rotate45cw rotate changescale split)) { + for (qw(open increase decrease rotate45ccw rotate45cw rotate changescale split)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } @@ -114,6 +115,7 @@ sub new { export_gcode cog_go.png export_stl brick_go.png + open package.png increase add.png decrease delete.png rotate45cw arrow_rotate_clockwise.png @@ -138,12 +140,14 @@ sub new { if ($self->{htoolbar}) { EVT_TOOL($self, TB_MORE, \&increase); EVT_TOOL($self, TB_LESS, \&decrease); + EVT_TOOL($self, TB_INFO, sub { $self->list_item_activated(undef, $self->{selected_objects}->[0][0]) }); EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) }); EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) }); EVT_TOOL($self, TB_ROTATE, sub { $_[0]->rotate(undef) }); EVT_TOOL($self, TB_SCALE, \&changescale); EVT_TOOL($self, TB_SPLIT, \&split_object); } else { + EVT_BUTTON($self, $self->{btn_open}, sub { $self->list_item_activated(undef, $self->{selected_objects}->[0][0]) }); EVT_BUTTON($self, $self->{btn_increase}, \&increase); EVT_BUTTON($self, $self->{btn_decrease}, \&decrease); EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) }); @@ -303,6 +307,7 @@ 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; @@ -313,7 +318,7 @@ sub load_file { 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), + name => $basename, input_file => $input_file, input_file_object_id => $i, model_object => $model->objects->[$i], @@ -335,7 +340,7 @@ sub load_file { } $process_dialog->Destroy; - $self->statusbar->SetStatusText("Loaded $input_file"); + $self->statusbar->SetStatusText("Loaded $basename - Double click object for more info"); } sub object_loaded { @@ -1004,11 +1009,11 @@ sub selection_changed { my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw rotate changescale split); + for grep $self->{"btn_$_"}, qw(remove open increase decrease rotate45cw rotate45ccw rotate changescale split); if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_MORE, TB_LESS, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); + for (TB_INFO, TB_MORE, TB_LESS, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); } } diff --git a/var/package.png b/var/package.png new file mode 100755 index 0000000000000000000000000000000000000000..da3c2a2d74bab159ba0f65d7db601768258afcb2 GIT binary patch literal 853 zcmV-b1FHOqP)5TQ^(M5v$(QKVE?W+9X! z*o}&~6c?_FreF)9NJB7b5Nbn{G0n4+%uJhR9(V5R|NFTpb|HgjefT!tIhLx@DR+N) zV+fHiR5Yt19}k|KnCsND{tH-`IMJ)3AE?OtyZ4>Un|6(d%h#JK`i&a7^xW9>`yBy` zS4SOHeOpC7$?hH5-#7Rswiue_8Ju*2N@$58=a#2OTA3png`w3v->gWif7t%e$ z$NLVS!tFT#8WL|Wa&K~+{%4P2cRfwesYV1_!F=3OaRVHl(>=`%&{x*s30c}#CNE@&;ItrAv!f!)Oy$Q9t$uS=(sD$-J{T*^(8Eez1E-l3}} zPrfHZ1`qsIFe&gipuL8-IZbo2Yg{lFGKs?ZZWcOaOdk*3`5T;$?AjbG1#`B510Er^h2)2r3Y{!8_2Gj=$KzuN5 zaErtW8W_Y2iJJjY)5pmTVJoPJYpanPOEuYHclM^C1F>${hFRpdi8a<2H|Xudf78bm(zwJ9`K%6I?q*Ua~ fW9JvIbn5*B+_J)rUMBs>00000NkvXXu0mjfH&TkY literal 0 HcmV?d00001 From 87b54ba2a0b665fe61cb6307aa2f03a05618ec85 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 12:22:05 +0200 Subject: [PATCH 09/17] Plater rearrange experiment --- lib/Slic3r/GUI/Plater.pm | 147 ++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 49 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7a1dca89..92f7017d 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -13,6 +13,12 @@ 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_REMOVE => &Wx::NewId; +use constant TB_RESET => &Wx::NewId; +use constant TB_ARRANGE => &Wx::NewId; +use constant TB_EXPORT_GCODE => &Wx::NewId; +use constant TB_EXPORT_STL => &Wx::NewId; use constant TB_MORE => &Wx::NewId; use constant TB_LESS => &Wx::NewId; use constant TB_INFO => &Wx::NewId; @@ -28,7 +34,7 @@ my $MESSAGE_DIALOG_EVENT : shared = Wx::NewEventType; my $EXPORT_COMPLETED_EVENT : shared = Wx::NewEventType; my $EXPORT_FAILED_EVENT : shared = Wx::NewEventType; -use constant CANVAS_SIZE => [300,300]; +use constant CANVAS_SIZE => [335,335]; use constant CANVAS_TEXT => join('-', +(localtime)[3,4]) eq '13-8' ? 'What do you want to print today? ™' # Sept. 13, 2006. The first part ever printed by a RepRap to make another RepRap. : 'Drag your objects here'; @@ -62,6 +68,11 @@ 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_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, "Autoarrange", Wx::Bitmap->new("$Slic3r::var/bricks.png", wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_INFO, "Open", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_LESS, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), ''); @@ -76,15 +87,15 @@ sub new { my %tbar_buttons = (info => "Open", increase => "More", decrease => "Less", rotate45ccw => "45°", rotate45cw => "45°", rotate => "Rotate…", changescale => "Scale…", split => "Split"); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); - for (qw(open increase decrease rotate45ccw rotate45cw rotate changescale split)) { + for (qw(load remove reset arrange open increase decrease rotate45ccw rotate45cw rotate changescale split)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } } - $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, [-1, 180], wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS); - $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 300); - $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 50); + $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, wxLC_SINGLE_SEL | wxLC_REPORT | wxBORDER_SUNKEN | wxTAB_TRAVERSAL | wxWANTS_CHARS); + $self->{list}->InsertColumn(0, "Name", wxLIST_FORMAT_LEFT, 145); + $self->{list}->InsertColumn(1, "Copies", wxLIST_FORMAT_CENTER, 45); $self->{list}->InsertColumn(2, "Scale", wxLIST_FORMAT_CENTER, wxLIST_AUTOSIZE_USEHEADER); EVT_LIST_ITEM_SELECTED($self, $self->{list}, \&list_item_selected); EVT_LIST_ITEM_DESELECTED($self, $self->{list}, \&list_item_deselected); @@ -98,13 +109,11 @@ sub new { } }); - # general buttons - $self->{btn_load} = Wx::Button->new($self, -1, "Add…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - $self->{btn_remove} = Wx::Button->new($self, -1, "Delete", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - $self->{btn_reset} = Wx::Button->new($self, -1, "Delete All", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); - $self->{btn_arrange} = Wx::Button->new($self, -1, "Autoarrange", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + # right pane buttons $self->{btn_export_gcode} = Wx::Button->new($self, -1, "Export G-code…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); $self->{btn_export_stl} = Wx::Button->new($self, -1, "Export STL…", wxDefaultPosition, wxDefaultSize, wxBU_LEFT); + $self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); + $self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); if (&Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/) { my %icons = qw( @@ -130,14 +139,14 @@ sub new { } $self->selection_changed(0); $self->object_list_changed; - EVT_BUTTON($self, $self->{btn_load}, \&load); - 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); EVT_BUTTON($self, $self->{btn_export_gcode}, \&export_gcode); EVT_BUTTON($self, $self->{btn_export_stl}, \&export_stl); if ($self->{htoolbar}) { + EVT_TOOL($self, TB_LOAD, \&load); + 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); EVT_TOOL($self, TB_MORE, \&increase); EVT_TOOL($self, TB_LESS, \&decrease); EVT_TOOL($self, TB_INFO, sub { $self->list_item_activated(undef, $self->{selected_objects}->[0][0]) }); @@ -194,33 +203,9 @@ sub new { $self->recenter; { - my $buttons = Wx::GridSizer->new(2, 3, 5, 5); - $buttons->Add($self->{"btn_load"}, 0, wxEXPAND | wxALL); - $buttons->Add($self->{"btn_arrange"}, 0, wxEXPAND | wxALL); - $buttons->Add($self->{"btn_export_gcode"}, 0, wxEXPAND | wxALL); - $buttons->Add($self->{"btn_remove"}, 0, wxEXPAND | wxALL); - $buttons->Add($self->{"btn_reset"}, 0, wxEXPAND | wxALL); - $buttons->Add($self->{"btn_export_stl"}, 0, wxEXPAND | wxALL); - # force sane tab order - my @taborder = qw/btn_load btn_arrange btn_export_gcode btn_remove btn_reset btn_export_stl/; - $self->{$taborder[$_]}->MoveAfterInTabOrder($self->{$taborder[$_-1]}) for (1..$#taborder); - - my $vertical_sizer = Wx::BoxSizer->new(wxVERTICAL); - $vertical_sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; - $vertical_sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar}; - $vertical_sizer->Add($self->{list}, 1, wxEXPAND | wxBOTTOM, 10); - $vertical_sizer->Add($buttons, 0, wxEXPAND); - - my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); - $hsizer->Add($self->{canvas}, 0, wxALL, 10); - $hsizer->Add($vertical_sizer, 1, wxEXPAND | wxALL, 10); - - my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add($hsizer, 1, wxEXPAND | wxBOTTOM, 10); - + my $presets; if ($self->skeinpanel->{mode} eq 'expert') { - my $presets = Wx::BoxSizer->new(wxHORIZONTAL); - $presets->AddStretchSpacer(1); + $presets = Wx::BoxSizer->new(wxVERTICAL); my %group_labels = ( print => 'Print settings', filament => 'Filament', @@ -230,20 +215,65 @@ sub new { $self->{preset_choosers_sizers} = {}; for my $group (qw(print filament printer)) { my $text = Wx::StaticText->new($self, -1, "$group_labels{$group}:", wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT); - my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], []); + $text->SetFont($Slic3r::GUI::small_font); + my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [140, -1], []); + $choice->SetFont($Slic3r::GUI::small_font); $self->{preset_choosers}{$group} = [$choice]; EVT_CHOICE($choice, $choice, sub { $self->on_select_preset($group, @_) }); $self->{preset_choosers_sizers}{$group} = Wx::BoxSizer->new(wxVERTICAL); $self->{preset_choosers_sizers}{$group}->Add($choice, 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); - $presets->Add($text, 0, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); - $presets->Add($self->{preset_choosers_sizers}{$group}, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 15); + $presets->Add($text, 0, wxALIGN_LEFT | wxRIGHT, 4); + $presets->Add($self->{preset_choosers_sizers}{$group}, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, 8); } - $presets->AddStretchSpacer(1); - $sizer->Add($presets, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); } + my $object_info_sizer; + { + my $box = Wx::StaticBox->new($self, -1, "Info"); + $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); + my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); + $object_info_sizer->Add($grid_sizer); + + my @info = ( + size => "Size", + volume => "Volume", + ); + while (my $field = shift @info) { + my $label = shift @info; + my $text = Wx::StaticText->new($self, -1, "$label:", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $text->SetFont($Slic3r::GUI::small_font); + $grid_sizer->Add($text, 0); + + $self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); + $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); + $grid_sizer->Add($self->{"object_info_$field"}, 0); + } + } + + my $right_buttons_sizer = Wx::BoxSizer->new(wxVERTICAL); + $right_buttons_sizer->Add($presets, 0, wxEXPAND, 0) if defined $presets; + $right_buttons_sizer->Add($self->{btn_export_gcode}, 0, wxEXPAND | wxTOP, 8); + $right_buttons_sizer->Add($self->{btn_export_stl}, 0, wxEXPAND | wxTOP, 2); + + my $right_top_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $right_top_sizer->Add($self->{list}, 1, wxEXPAND | wxLEFT, 5); + $right_top_sizer->Add($right_buttons_sizer, 0, wxEXPAND | wxALL, 10); + + my $right_sizer = Wx::BoxSizer->new(wxVERTICAL); + $right_sizer->Add($right_top_sizer, 1, wxEXPAND | wxBOTTOM, 10); + $right_sizer->Add($object_info_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, 5); + + my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL); + $hsizer->Add($self->{canvas}, 0, wxTOP, 1); + $hsizer->Add($right_sizer, 1, wxEXPAND | wxBOTTOM, 0); + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add($self->{htoolbar}, 0, wxEXPAND, 0) if $self->{htoolbar}; + $sizer->Add($self->{btoolbar}, 0, wxEXPAND, 0) if $self->{btoolbar}; + $sizer->Add($hsizer, 1, wxEXPAND, 0); + $sizer->SetSizeHints($self); $self->SetSizer($sizer); } @@ -349,6 +379,8 @@ sub object_loaded { my $object = $self->{objects}[$obj_idx]; $self->{list}->InsertStringItem($obj_idx, $object->name); + $self->{list}->SetItemFont($obj_idx, Wx::Font->new(10, wxDEFAULT, wxNORMAL, wxNORMAL)); + $self->{list}->SetItem($obj_idx, 1, $object->instances_count); $self->{list}->SetItem($obj_idx, 2, ($object->scale * 100) . "%"); @@ -435,6 +467,7 @@ sub rotate { } $object->rotate($object->rotate + $angle); + $self->selection_changed(1); # refresh info (size etc.) $self->recenter; $self->{canvas}->Refresh; } @@ -453,6 +486,7 @@ sub changescale { $self->{list}->SetItem($obj_idx, 2, "$scale%"); $object->changescale($scale / 100); + $self->selection_changed(1); # refresh info (size, volume etc.) $self->arrange; } @@ -856,9 +890,11 @@ sub repaint { } # draw frame - $dc->SetPen(wxBLACK_PEN); - $dc->SetBrush($parent->{transparent_brush}); - $dc->DrawRectangle(0, 0, @size); + if (0) { + $dc->SetPen(wxBLACK_PEN); + $dc->SetBrush($parent->{transparent_brush}); + $dc->DrawRectangle(0, 0, @size); + } # draw text if plate is empty if (!@{$parent->{objects}}) { @@ -1013,7 +1049,20 @@ sub selection_changed { if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_INFO, TB_MORE, TB_LESS, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); + for (TB_REMOVE, TB_INFO, TB_MORE, TB_LESS, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); + } + + 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", @{$object->transformed_size}); + + if (my $stats = $object->mesh_stats) { + $self->{object_info_volume}->SetLabel(sprintf('%.2f', $stats->{volume} * ($object->scale**3))); + } + } else { + $self->{"object_info_$_"}->SetLabel("") for qw(size volume); + } } } From 3d6fb1b05cde99bceb3bde1abaa1a6b4ef19a8ee Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 14:37:50 +0200 Subject: [PATCH 10/17] New feature: ability to override specific settings for individual objects in the plater. #344 --- lib/Slic3r/Config.pm | 54 ++++++++++++- lib/Slic3r/Fill.pm | 9 ++- lib/Slic3r/GCode.pm | 6 +- lib/Slic3r/GUI.pm | 15 +++- lib/Slic3r/GUI/OptionsGroup.pm | 13 +++- lib/Slic3r/GUI/Plater.pm | 2 + lib/Slic3r/GUI/Plater/ObjectDialog.pm | 107 ++++++++++++++++++++++++++ lib/Slic3r/Layer.pm | 2 +- lib/Slic3r/Layer/Region.pm | 18 ++--- lib/Slic3r/Model.pm | 3 + lib/Slic3r/Print.pm | 7 +- lib/Slic3r/Print/Object.pm | 67 +++++++++------- 12 files changed, 248 insertions(+), 55 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index a48f2f0f..bd1cc822 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -397,9 +397,12 @@ our $Options = { default => 0.35, }, 'infill_every_layers' => { - label => 'Infill every', + label => 'Combine infill every', + full_label => 'Combine infill every n layers', tooltip => 'This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy.', sidetext => 'layers', + scope => 'object', + category => 'Infill', cli => 'infill-every-layers=i', type => 'i', min => 1, @@ -409,6 +412,8 @@ our $Options = { label => 'Solid infill every', tooltip => 'This feature allows to force a solid layer every given number of layers. Zero to disable.', sidetext => 'layers', + scope => 'object', + category => 'Infill', cli => 'solid-infill-every-layers=i', type => 'i', min => 0, @@ -417,6 +422,8 @@ our $Options = { 'infill_only_where_needed' => { label => 'Only infill where needed', tooltip => 'This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material).', + scope => 'object', + category => 'Infill', cli => 'infill-only-where-needed!', type => 'bool', default => 0, @@ -506,7 +513,9 @@ our $Options = { # print options 'perimeters' => { label => 'Perimeters (minimum)', - tooltip => 'This option sets the number of perimeters to generate for each layer. Note that Slic3r will increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters.', + tooltip => 'This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled.', + scope => 'object', + category => 'Layers and Perimeters', cli => 'perimeters=i', type => 'i', aliases => [qw(perimeter_offsets)], @@ -521,14 +530,20 @@ our $Options = { }, 'top_solid_layers' => { label => 'Top', + full_label => 'Top solid layers', tooltip => 'Number of solid layers to generate on top surfaces.', + scope => 'object', + category => 'Layers and Perimeters', cli => 'top-solid-layers=i', type => 'i', default => 3, }, 'bottom_solid_layers' => { label => 'Bottom', + full_label => 'Bottom solid layers', tooltip => 'Number of solid layers to generate on bottom surfaces.', + scope => 'object', + category => 'Layers and Perimeters', cli => 'bottom-solid-layers=i', type => 'i', default => 3, @@ -536,6 +551,8 @@ our $Options = { 'fill_pattern' => { label => 'Fill pattern', tooltip => 'Fill pattern for general low-density infill.', + scope => 'object', + category => 'Infill', cli => 'fill-pattern=s', type => 'select', values => [qw(rectilinear line concentric honeycomb hilbertcurve archimedeanchords octagramspiral)], @@ -545,6 +562,8 @@ our $Options = { 'solid_fill_pattern' => { label => 'Top/bottom fill pattern', tooltip => 'Fill pattern for top/bottom infill.', + scope => 'object', + category => 'Infill', cli => 'solid-fill-pattern=s', type => 'select', values => [qw(rectilinear concentric hilbertcurve archimedeanchords octagramspiral)], @@ -554,6 +573,8 @@ our $Options = { 'fill_density' => { label => 'Fill density', tooltip => 'Density of internal infill, expressed in the range 0 - 1.', + scope => 'object', + category => 'Infill', cli => 'fill-density=f', type => 'f', default => 0.4, @@ -571,6 +592,8 @@ our $Options = { label => 'Solid infill threshold area', tooltip => 'Force solid infill for regions having a smaller area than the specified threshold.', sidetext => 'mm²', + scope => 'object', + category => 'Infill', cli => 'solid-infill-below-area=f', type => 'f', default => 70, @@ -578,6 +601,8 @@ our $Options = { 'extra_perimeters' => { label => 'Extra perimeters if needed', tooltip => 'Add more perimeters when needed for avoiding gaps in sloping walls.', + scope => 'object', + category => 'Layers and Perimeters', cli => 'extra-perimeters!', type => 'bool', default => 1, @@ -606,6 +631,8 @@ our $Options = { 'thin_walls' => { label => 'Detect thin walls', tooltip => 'Detect single-width walls (parts where two extrusions don\'t fit and we need to collapse them into a single trace).', + scope => 'object', + category => 'Layers and Perimeters', cli => 'thin-walls!', type => 'bool', default => 1, @@ -613,6 +640,8 @@ our $Options = { 'overhangs' => { label => 'Detect overhangs', tooltip => 'Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan.', + scope => 'object', + category => 'Layers and Perimeters', cli => 'overhangs!', type => 'bool', default => 1, @@ -647,6 +676,8 @@ our $Options = { }, 'support_material' => { label => 'Generate support material', + scope => 'object', + category => 'Support material', tooltip => 'Enable support material generation.', cli => 'support-material!', type => 'bool', @@ -656,6 +687,8 @@ our $Options = { label => 'Overhang threshold', tooltip => 'Support material will not generated for overhangs whose slope angle is above the given threshold. Set to zero for automatic detection.', sidetext => '°', + scope => 'object', + category => 'Support material', cli => 'support-material-threshold=i', type => 'i', default => 0, @@ -663,6 +696,8 @@ our $Options = { 'support_material_pattern' => { label => 'Pattern', tooltip => 'Pattern used to generate support material.', + scope => 'object', + category => 'Support material', cli => 'support-material-pattern=s', type => 'select', values => [qw(rectilinear rectilinear-grid honeycomb)], @@ -673,6 +708,8 @@ our $Options = { label => 'Pattern spacing', tooltip => 'Spacing between support material lines.', sidetext => 'mm', + scope => 'object', + category => 'Support material', cli => 'support-material-spacing=f', type => 'f', default => 2.5, @@ -680,6 +717,8 @@ our $Options = { 'support_material_angle' => { label => 'Pattern angle', tooltip => 'Use this setting to rotate the support material pattern on the horizontal plane.', + scope => 'object', + category => 'Support material', sidetext => '°', cli => 'support-material-angle=i', type => 'i', @@ -689,6 +728,8 @@ our $Options = { label => 'Interface layers', tooltip => 'Number of interface layers to insert between the object(s) and support material.', sidetext => 'layers', + scope => 'object', + category => 'Support material', cli => 'support-material-interface-layers=i', type => 'i', default => 3, @@ -696,6 +737,8 @@ our $Options = { 'support_material_interface_spacing' => { label => 'Interface pattern spacing', tooltip => 'Spacing between interface lines. Set zero to get a solid interface.', + scope => 'object', + category => 'Support material', sidetext => 'mm', cli => 'support-material-interface-spacing=f', type => 'f', @@ -703,8 +746,11 @@ our $Options = { }, 'support_material_enforce_layers' => { label => 'Enforce support for the first', + full_label => 'Enforce support for the first n layers', tooltip => 'Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate.', sidetext => 'layers', + scope => 'object', + category => 'Support material', cli => 'support-material-enforce-layers=f', type => 'i', default => 0, @@ -713,6 +759,8 @@ our $Options = { label => 'Raft layers', tooltip => 'The object will be raised by this number of layers, and support material will be generated under it.', sidetext => 'layers', + scope => 'object', + category => 'Support material', cli => 'raft-layers=i', type => 'i', default => 0, @@ -1098,7 +1146,7 @@ sub new { sub new_from_defaults { my $class = shift; - my @opt_keys = + return $class->new( map { $_ => $Options->{$_}{default} } grep !$Options->{$_}{shortcut}, diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index e6949f97..725697fa 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -50,12 +50,13 @@ sub make_fill { my ($layerm) = @_; Slic3r::debugf "Filling layer %d:\n", $layerm->id; + my $fill_density = $layerm->config->fill_density; my @surfaces = (); # if hollow object is requested, remove internal surfaces # (this needs to be done after internal-solid shells are created) - if ($Slic3r::Config->fill_density == 0) { + if ($fill_density == 0) { @surfaces = grep $_->surface_type != S_TYPE_INTERNAL, @surfaces; } @@ -140,8 +141,8 @@ sub make_fill { my @fills_ordering_points = (); SURFACE: foreach my $surface (@surfaces) { next if $surface->surface_type == S_TYPE_INTERNALVOID; - my $filler = $Slic3r::Config->fill_pattern; - my $density = $Slic3r::Config->fill_density; + my $filler = $layerm->config->fill_pattern; + my $density = $fill_density; my $flow = ($surface->surface_type == S_TYPE_TOP) ? $layerm->top_infill_flow : $surface->is_solid @@ -154,7 +155,7 @@ sub make_fill { # force 100% density and rectilinear fill for external surfaces if ($surface->surface_type != S_TYPE_INTERNAL) { $density = 1; - $filler = $Slic3r::Config->solid_fill_pattern; + $filler = $layerm->config->solid_fill_pattern; if ($is_bridge) { $filler = 'rectilinear'; $flow_spacing = $layerm->extruders->{infill}->bridge_flow->spacing; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 873479ec..bace5803 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -108,7 +108,7 @@ sub change_layer { $self->_upper_layer_islands([]); } $self->_layer_overhangs( - $layer->id > 0 && ($Slic3r::Config->overhangs || $Slic3r::Config->start_perimeters_at_non_overhang) + $layer->id > 0 && ($layer->config->overhangs || $Slic3r::Config->start_perimeters_at_non_overhang) ? [ map $_->expolygon, grep $_->surface_type == S_TYPE_BOTTOM, map @{$_->slices}, @{$layer->regions} ] : [] ); @@ -227,7 +227,7 @@ sub extrude_loop { my @paths = (); # detect overhanging/bridging perimeters - if ($Slic3r::Config->overhangs && $extrusion_path->is_perimeter && @{$self->_layer_overhangs}) { + if ($self->layer->config->overhangs && $extrusion_path->is_perimeter && @{$self->_layer_overhangs}) { # get non-overhang paths by subtracting overhangs from the loop push @paths, $extrusion_path->subtract_expolygons($self->_layer_overhangs); @@ -256,7 +256,7 @@ sub extrude_loop { $self->wipe_path($extrusion_path->polyline) if $self->enable_wipe; # make a little move inwards before leaving loop - if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->config->perimeters > 1) { + if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && defined $self->layer && $self->layer->object->config->perimeters > 1) { # detect angle between last and first segment # the side depends on the original winding order of the polygon (left for contours, right for holes) my @points = $was_clockwise ? (-2, 1) : (1, -2); diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 8b5d48cf..7b8eca39 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -17,7 +17,7 @@ use Slic3r::GUI::Tab; our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1"; use Wx 0.9901 qw(:bitmap :dialog :frame :icon :id :misc :systemsettings :toplevelwindow); -use Wx::Event qw(EVT_CLOSE EVT_MENU); +use Wx::Event qw(EVT_CLOSE EVT_MENU EVT_IDLE); use base 'Wx::App'; use constant MI_LOAD_CONF => &Wx::NewId; @@ -47,6 +47,7 @@ our $datadir; our $no_plater; our $mode; our $autosave; +our @cb; our $Settings = { _ => { @@ -210,6 +211,12 @@ sub OnInit { && ($Settings->{_}{version_check} // 1) && (!$Settings->{_}{last_version_check} || (time - $Settings->{_}{last_version_check}) >= 86400); + EVT_IDLE($frame, sub { + while (my $cb = shift @cb) { + $cb->(); + } + }); + return 1; } @@ -324,6 +331,12 @@ sub output_path { : $dir; } +sub CallAfter { + my $class = shift; + my ($cb) = @_; + push @cb, $cb; +} + package Slic3r::GUI::ProgressStatusBar; use Wx qw(:gauge :misc); use base 'Wx::StatusBar'; diff --git a/lib/Slic3r/GUI/OptionsGroup.pm b/lib/Slic3r/GUI/OptionsGroup.pm index b228ea59..343dd802 100644 --- a/lib/Slic3r/GUI/OptionsGroup.pm +++ b/lib/Slic3r/GUI/OptionsGroup.pm @@ -36,6 +36,7 @@ Slic3r::GUI::OptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containing one on_change => sub { print "new value for $_[0] is $_[1]\n" }, no_labels => 0, label_width => 180, + extra_column => sub { ... }, ); $sizer->Add($optgroup->sizer); @@ -48,6 +49,7 @@ has 'lines' => (is => 'lazy'); has 'on_change' => (is => 'ro', default => sub { sub {} }); has 'no_labels' => (is => 'ro', default => sub { 0 }); has 'label_width' => (is => 'ro', default => sub { 180 }); +has 'extra_column' => (is => 'ro'); has 'sizer' => (is => 'rw'); has '_triggers' => (is => 'ro', default => sub { {} }); @@ -63,7 +65,8 @@ sub BUILD { $self->sizer(Wx::StaticBoxSizer->new($box, wxVERTICAL)); } - my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$self->options}), 2, 0, 0); + my $num_columns = $self->extra_column ? 3 : 2; + my $grid_sizer = Wx::FlexGridSizer->new(scalar(@{$self->options}), $num_columns, 0, 0); $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); $grid_sizer->AddGrowableCol($self->no_labels ? 0 : 1); @@ -113,6 +116,10 @@ sub _build_line { my $self = shift; my ($line, $grid_sizer) = @_; + if ($self->extra_column) { + $grid_sizer->Add($self->extra_column->($line), 0, wxALIGN_CENTER_VERTICAL, 0); + } + my $label; if (!$self->no_labels) { $label = Wx::StaticText->new($self->parent, -1, $line->{label} ? "$line->{label}:" : "", wxDefaultPosition, [$self->label_width, -1]); @@ -291,6 +298,7 @@ Slic3r::GUI::ConfigOptionsGroup - pre-filled Wx::StaticBoxSizer wrapper containi use List::Util qw(first); has 'config' => (is => 'ro', required => 1); +has 'full_labels' => (is => 'ro', default => sub {0}); sub _trigger_options { my $self = shift; @@ -304,7 +312,8 @@ sub _trigger_options { $opt = { opt_key => $full_key, config => 1, - (map { $_ => $config_opt->{$_} } qw(type label tooltip sidetext width height full_width min max labels values multiline readonly)), + label => ($self->full_labels && defined $config_opt->{full_label}) ? $config_opt->{full_label} : $config_opt->{label}, + (map { $_ => $config_opt->{$_} } qw(type tooltip sidetext width height full_width min max labels values multiline readonly)), default => $self->_get_config($opt_key, $index), on_change => sub { $self->_set_config($opt_key, $index, $_[0]) }, }; diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7a1dca89..3ad70831 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -713,6 +713,7 @@ sub make_model { my $new_model_object = $model->add_object( vertices => $model_object->vertices, input_file => $plater_object->input_file, + config => $plater_object->config, layer_height_ranges => $plater_object->layer_height_ranges, ); foreach my $volume (@{$model_object->volumes}) { @@ -1090,6 +1091,7 @@ has 'instances' => (is => 'rw', default => sub { [] }); # upward Y a has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail); 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 'mesh_stats' => (is => 'rw'); diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 214df0e2..9b851ff7 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -17,11 +17,13 @@ sub new { $self->{tabpanel}->AddPage($self->{preview} = Slic3r::GUI::Plater::ObjectDialog::PreviewTab->new($self->{tabpanel}, object => $self->{object}), "Preview") if $Slic3r::GUI::have_OpenGL; $self->{tabpanel}->AddPage($self->{info} = Slic3r::GUI::Plater::ObjectDialog::InfoTab->new($self->{tabpanel}, object => $self->{object}), "Info"); + $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"); my $buttons = $self->CreateStdDialogButtonSizer(wxOK); EVT_BUTTON($self, wxID_OK, sub { # validate user input + return if !$self->{settings}->CanClose; return if !$self->{layers}->CanClose; # notify tabs @@ -124,6 +126,111 @@ sub new { return $self; } +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'; + +sub new { + my $class = shift; + my ($parent, %params) = @_; + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); + $self->{object} = $params{object}; + + $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); + + # descriptive text + { + my $label = Wx::StaticText->new($self, -1, "You can use this section to override some settings just for this object.", + wxDefaultPosition, [-1, 25]); + $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + $self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); + } + + # option selector + { + # get all options with object scope and sort them by category+label + my %settings = map { $_ => sprintf('%s > %s', $Slic3r::Config::Options->{$_}{category}, $Slic3r::Config::Options->{$_}{full_label} // $Slic3r::Config::Options->{$_}{label}) } + grep { ($Slic3r::Config::Options->{$_}{scope} // '') eq 'object' } + keys %$Slic3r::Config::Options; + $self->{options} = [ sort { $settings{$a} cmp $settings{$b} } keys %settings ]; + my $choice = Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [ map $settings{$_}, @{$self->{options}} ]); + + # create the button + my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG)); + EVT_BUTTON($self, $btn, sub { + my $opt_key = $self->{options}[$choice->GetSelection]; + $self->{object}->config->apply(Slic3r::Config->new_from_defaults($opt_key)); + $self->update_optgroup; + }); + + my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $h_sizer->Add($choice, 1, wxEXPAND | wxALL, 0); + $h_sizer->Add($btn, 0, wxEXPAND | wxLEFT, 10); + $self->{sizer}->Add($h_sizer, 0, wxEXPAND | wxALL, 10); + } + + $self->{options_sizer} = Wx::BoxSizer->new(wxVERTICAL); + $self->{sizer}->Add($self->{options_sizer}, 0, wxEXPAND | wxALL, 10); + + $self->update_optgroup; + + $self->SetSizer($self->{sizer}); + $self->{sizer}->SetSizeHints($self); + + return $self; +} + +sub update_optgroup { + my $self = shift; + + $self->{options_sizer}->Clear(1); + + my $config = $self->{object}->config; + my %categories = (); + foreach my $opt_key (keys %$config) { + my $category = $Slic3r::Config::Options->{$opt_key}{category}; + $categories{$category} ||= []; + push @{$categories{$category}}, $opt_key; + } + foreach my $category (sort keys %categories) { + my $optgroup = Slic3r::GUI::ConfigOptionsGroup->new( + parent => $self, + title => $category, + config => $config, + options => [ sort @{$categories{$category}} ], + full_labels => 1, + extra_column => sub { + my ($line) = @_; + my ($opt_key) = @{$line->{options}}; # we assume that we have one option per line + my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG)); + EVT_BUTTON($self, $btn, sub { + delete $self->{object}->config->{$opt_key}; + Slic3r::GUI->CallAfter(sub { $self->update_optgroup }); + }); + return $btn; + }, + ); + $self->{options_sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxBOTTOM, 10); + } + $self->Layout; +} + +sub CanClose { + my $self = shift; + + # 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); + eval { + $config->validate; + }; + return 0 if Slic3r::GUI::catch_error($self); + return 1; +} + package Slic3r::GUI::Plater::ObjectDialog::LayersTab; use Wx qw(:dialog :id :misc :sizer :systemsettings); use Wx::Grid; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index a1b56b91..d6554740 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -6,7 +6,7 @@ use Slic3r::Geometry qw(scale); use Slic3r::Geometry::Clipper qw(union_ex); has 'id' => (is => 'rw', required => 1, trigger => 1); # sequential number of layer, 0-based -has 'object' => (is => 'ro', weak_ref => 1, required => 1); +has 'object' => (is => 'ro', weak_ref => 1, required => 1, handles => [qw(print config)]); has 'regions' => (is => 'ro', default => sub { [] }); has 'slicing_errors' => (is => 'rw'); diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 1144e23a..b846e286 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -14,7 +14,7 @@ has 'layer' => ( weak_ref => 1, required => 1, trigger => 1, - handles => [qw(id slice_z print_z height flow)], + handles => [qw(id slice_z print_z height flow object print config)], ); has 'region' => (is => 'ro', required => 1, handles => [qw(extruders)]); has 'perimeter_flow' => (is => 'rw'); @@ -103,7 +103,7 @@ sub make_surfaces { } # detect thin walls by offsetting slices by half extrusion inwards - if ($Slic3r::Config->thin_walls) { + if ($self->config->thin_walls) { $self->thin_walls([]); # we use spacing here because there could be a case where # the slice collapses with width but doesn't collapse with spacing, @@ -183,7 +183,7 @@ sub make_perimeters { # extra perimeters for each one foreach my $surface (@{$self->slices}) { # detect how many perimeters must be generated for this island - my $loop_number = $Slic3r::Config->perimeters + ($surface->extra_perimeters || 0); + my $loop_number = $self->config->perimeters + ($surface->extra_perimeters || 0); # generate loops # (one more than necessary so that we can detect gaps even after the desired @@ -203,7 +203,7 @@ sub make_perimeters { # where offset2() collapses the expolygon, then there's no room for an inner loop # and we can extract the gap for later processing - if ($Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0) { + if ($Slic3r::Config->gap_fill_speed > 0 && $self->object->config->fill_density > 0) { my $diff = diff_ex( [ offset(\@last, -0.5*$spacing) ], # +2 on the offset here makes sure that Clipper float truncation @@ -411,16 +411,16 @@ sub prepare_fill_surfaces { my $self = shift; # if no solid layers are requested, turn top/bottom surfaces to internal - if ($Slic3r::Config->top_solid_layers == 0) { + if ($self->config->top_solid_layers == 0) { $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}; } - if ($Slic3r::Config->bottom_solid_layers == 0) { + if ($self->config->bottom_solid_layers == 0) { $_->surface_type(S_TYPE_INTERNAL) for grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; } # turn too small internal regions into solid regions according to the user setting - if ($Slic3r::Config->fill_density > 0) { - my $min_area = scale scale $Slic3r::Config->solid_infill_below_area; # scaling an area requires two calls! + if ($self->object->config->fill_density > 0) { + my $min_area = scale scale $self->config->solid_infill_below_area; # scaling an area requires two calls! my @small = grep $_->surface_type == S_TYPE_INTERNAL && $_->expolygon->contour->area <= $min_area, @{$self->fill_surfaces}; $_->surface_type(S_TYPE_INTERNALSOLID) for @small; Slic3r::debugf "identified %d small solid surfaces at layer %d\n", scalar(@small), $self->id if @small > 0; @@ -459,7 +459,7 @@ sub process_external_surfaces { # if we're slicing with no infill, we can't extend external surfaces # over non-existent infill - my @fill_boundaries = $Slic3r::Config->fill_density > 0 + my @fill_boundaries = $self->object->config->fill_density > 0 ? @{$self->fill_surfaces} : grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces}; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index b07125f6..28ca25b1 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -35,6 +35,7 @@ sub merge { my $new_object = $new_model->add_object( input_file => $object->input_file, vertices => $object->vertices, + config => $object->config, layer_height_ranges => $object->layer_height_ranges, ); @@ -255,6 +256,7 @@ sub split_meshes { foreach my $mesh ($volume->mesh->split_mesh) { my $new_object = $self->add_object( input_file => $object->input_file, + config => $object->config, layer_height_ranges => $object->layer_height_ranges, ); $new_object->add_volume( @@ -302,6 +304,7 @@ has 'model' => (is => 'ro', weak_ref => 1, required => 1); has 'vertices' => (is => 'ro', default => sub { [] }); 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 'mesh_stats' => (is => 'rw'); has '_bounding_box' => (is => 'rw'); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 54485191..7552101a 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -80,9 +80,9 @@ sub _trigger_config { sub _build_has_support_material { my $self = shift; - return $self->config->support_material - || $self->config->raft_layers > 0 - || $self->config->support_material_enforce_layers > 0; + return (first { $_->config->support_material } @{$self->objects}) + || (first { $_->config->raft_layers > 0 } @{$self->objects}) + || (first { $_->config->support_material_enforce_layers > 0 } @{$self->objects}); } # caller is responsible for supplying models whose objects don't collide @@ -160,6 +160,7 @@ sub add_model { ], size => $bb->size, # transformed size input_file => $object->input_file, + config_overrides => $object->config, layer_height_ranges => $object->layer_height_ranges, ); } diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 99c73b4f..9b0d4850 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -15,18 +15,22 @@ has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordina has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'support_layers' => (is => 'rw', default => sub { [] }); +has 'config_overrides' => (is => 'rw', default => sub { Slic3r::Config->new }); +has 'config' => (is => 'rw'); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] has 'fill_maker' => (is => 'lazy'); has '_slice_z_table' => (is => 'lazy'); sub BUILD { my $self = shift; - + + $self->init_config; + # make layers taking custom heights into account my $print_z = my $slice_z = my $height = 0; # add raft layers - for my $id (0 .. $Slic3r::Config->raft_layers-1) { + for my $id (0 .. $self->config->raft_layers-1) { $height = ($id == 0) ? $Slic3r::Config->get_value('first_layer_height') : $Slic3r::Config->layer_height; @@ -99,6 +103,11 @@ sub _trigger_copies { @{$self->copies} = @{chained_path_points($self->copies)} } +sub init_config { + my $self = shift; + $self->config(Slic3r::Config->merge($self->print->config, $self->config_overrides)); +} + sub layer_count { my $self = shift; return scalar @{ $self->layers }; @@ -249,7 +258,7 @@ sub slice { } # remove empty layers from bottom - my $first_object_layer_id = $Slic3r::Config->raft_layers; + my $first_object_layer_id = $self->config->raft_layers; while (@{$self->layers} && !@{$self->layers->[$first_object_layer_id]->slices} && !map @{$_->thin_walls}, @{$self->layers->[$first_object_layer_id]->regions}) { splice @{$self->layers}, $first_object_layer_id, 1; for (my $i = $first_object_layer_id; $i <= $#{$self->layers}; $i++) { @@ -268,7 +277,7 @@ sub make_perimeters { # but we don't generate any extra perimeter if fill density is zero, as they would be floating # inside the object - infill_only_where_needed should be the method of choice for printing # hollow objects - if ($Slic3r::Config->extra_perimeters && $Slic3r::Config->perimeters > 0 && $Slic3r::Config->fill_density > 0) { + if ($self->config->extra_perimeters && $self->config->perimeters > 0 && $self->config->fill_density > 0) { for my $region_id (0 .. ($self->print->regions_count-1)) { for my $layer_id (0 .. $self->layer_count-2) { my $layerm = $self->layers->[$layer_id]->regions->[$region_id]; @@ -278,7 +287,7 @@ sub make_perimeters { my $overlap = $perimeter_spacing; # one perimeter my $diff = diff( - [ offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($Slic3r::Config->perimeters * $perimeter_spacing)) ], + [ offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($self->config->perimeters * $perimeter_spacing)) ], [ offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap) ], ); next if !@$diff; @@ -299,8 +308,8 @@ sub make_perimeters { # of our slice $extra_perimeters++; my $hypothetical_perimeter = diff( - [ offset($slice->expolygon, -($perimeter_spacing * ($Slic3r::Config->perimeters + $extra_perimeters-1))) ], - [ offset($slice->expolygon, -($perimeter_spacing * ($Slic3r::Config->perimeters + $extra_perimeters))) ], + [ offset($slice->expolygon, -($perimeter_spacing * ($self->config->perimeters + $extra_perimeters-1))) ], + [ offset($slice->expolygon, -($perimeter_spacing * ($self->config->perimeters + $extra_perimeters))) ], ); last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible @@ -451,7 +460,7 @@ sub detect_surfaces_type { sub clip_fill_surfaces { my $self = shift; - return unless $Slic3r::Config->infill_only_where_needed; + return unless $self->config->infill_only_where_needed; # We only want infill under ceilings; this is almost like an # internal support material. @@ -498,7 +507,7 @@ sub clip_fill_surfaces { sub bridge_over_infill { my $self = shift; - return if $Slic3r::Config->fill_density == 1; + return if $self->config->fill_density == 1; for my $layer_id (1..$#{$self->layers}) { my $layer = $self->layers->[$layer_id]; @@ -578,8 +587,8 @@ sub discover_horizontal_shells { for (my $i = 0; $i < $self->layer_count; $i++) { my $layerm = $self->layers->[$i]->regions->[$region_id]; - if ($Slic3r::Config->solid_infill_every_layers && $Slic3r::Config->fill_density > 0 - && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { + if ($self->config->solid_infill_every_layers && $self->config->fill_density > 0 + && ($i % $self->config->solid_infill_every_layers) == 0) { $_->surface_type(S_TYPE_INTERNALSOLID) for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; } @@ -597,8 +606,8 @@ sub discover_horizontal_shells { Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP ? 'top' : 'bottom'); my $solid_layers = ($type == S_TYPE_TOP) - ? $Slic3r::Config->top_solid_layers - : $Slic3r::Config->bottom_solid_layers; + ? $self->config->top_solid_layers + : $self->config->bottom_solid_layers; NEIGHBOR: for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; abs($n - $i) <= $solid_layers-1; $type == S_TYPE_TOP ? $n-- : $n++) { @@ -636,7 +645,7 @@ sub discover_horizontal_shells { # if some parts are going to collapse, use a different strategy according to fill density if (@$too_narrow) { - if ($Slic3r::Config->fill_density > 0) { + if ($self->config->fill_density > 0) { # if we have internal infill, grow the collapsing parts and add the extra area to # the neighbor layer as well as to our original surfaces so that we support this # additional area in the next shell too @@ -704,8 +713,8 @@ sub discover_horizontal_shells { # combine fill surfaces across layers sub combine_infill { my $self = shift; - return unless $Slic3r::Config->infill_every_layers > 1 && $Slic3r::Config->fill_density > 0; - my $every = $Slic3r::Config->infill_every_layers; + return unless $self->config->infill_every_layers > 1 && $self->config->fill_density > 0; + my $every = $self->config->infill_every_layers; my $layer_count = $self->layer_count; my @layer_heights = map $self->layers->[$_]->height, 0 .. $layer_count-1; @@ -768,7 +777,7 @@ sub combine_infill { + $layerms[-1]->perimeter_flow->scaled_width / 2 # Because fill areas for rectilinear and honeycomb are grown # later to overlap perimeters, we need to counteract that too. - + (($type == S_TYPE_INTERNALSOLID || $Slic3r::Config->fill_pattern =~ /(rectilinear|honeycomb)/) + + (($type == S_TYPE_INTERNALSOLID || $self->config->fill_pattern =~ /(rectilinear|honeycomb)/) ? $layerms[-1]->solid_infill_flow->scaled_width * &Slic3r::INFILL_OVERLAP_OVER_SPACING : 0) ), @$intersection; @@ -813,7 +822,7 @@ sub combine_infill { sub generate_support_material { my $self = shift; - return if $self->layer_count < 2; + return unless $self->config->support_material && $self->layer_count >= 2; my $flow = $self->print->support_material_flow; @@ -826,8 +835,8 @@ sub generate_support_material { # if user specified a custom angle threshold, convert it to radians my $threshold_rad; - if ($Slic3r::Config->support_material_threshold) { - $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive + if ($self->config->support_material_threshold) { + $threshold_rad = deg2rad($self->config->support_material_threshold + 1); # +1 makes the threshold inclusive Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); } @@ -851,7 +860,7 @@ sub generate_support_material { my $diff; # If a threshold angle was specified, use a different logic for detecting overhangs. - if (defined $threshold_rad || $layer_id <= $Slic3r::Config->support_material_enforce_layers) { + if (defined $threshold_rad || $layer_id <= $self->config->support_material_enforce_layers) { my $d = defined $threshold_rad ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad)) : 0; @@ -963,14 +972,14 @@ sub generate_support_material { # we now know the upper and lower boundaries for our support material object # (@contact_z and @top_z), so we can generate intermediate layers - my @support_layers = _compute_support_layers(\@contact_z, \@top_z, $Slic3r::Config, $flow); + my @support_layers = _compute_support_layers(\@contact_z, \@top_z, $self->config, $flow); # if we wanted to apply some special logic to the first support layers lying on # object's top surfaces this is the place to detect them # let's now generate interface layers below contact areas my %interface = (); # layer_id => [ polygons ] - my $interface_layers = $Slic3r::Config->support_material_interface_layers; + my $interface_layers = $self->config->support_material_interface_layers; for my $layer_id (0 .. $#support_layers) { my $z = $support_layers[$layer_id]; my $this = $contact{$z} // next; @@ -1029,8 +1038,8 @@ sub generate_support_material { Slic3r::debugf "Generating patterns\n"; # prepare fillers - my $pattern = $Slic3r::Config->support_material_pattern; - my @angles = ($Slic3r::Config->support_material_angle); + my $pattern = $self->config->support_material_pattern; + my @angles = ($self->config->support_material_angle); if ($pattern eq 'rectilinear-grid') { $pattern = 'rectilinear'; push @angles, $angles[0] + 90; @@ -1041,10 +1050,10 @@ sub generate_support_material { support => $self->fill_maker->filler($pattern), ); - my $interface_angle = $Slic3r::Config->support_material_angle + 90; - my $interface_spacing = $Slic3r::Config->support_material_interface_spacing + $flow->spacing; + my $interface_angle = $self->config->support_material_angle + 90; + my $interface_spacing = $self->config->support_material_interface_spacing + $flow->spacing; my $interface_density = $interface_spacing == 0 ? 1 : $flow->spacing / $interface_spacing; - my $support_spacing = $Slic3r::Config->support_material_spacing + $flow->spacing; + my $support_spacing = $self->config->support_material_spacing + $flow->spacing; my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing; my $process_layer = sub { @@ -1167,7 +1176,7 @@ sub generate_support_material { # base flange if ($layer_id == 0) { $filler = $fillers{interface}; - $filler->angle($Slic3r::Config->support_material_angle + 90); + $filler->angle($self->config->support_material_angle + 90); $density = 0.5; $flow_spacing = $self->print->first_layer_support_material_flow->spacing; } else { From 2fb725405f25267d6a321c821076b76fcdec2a30 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 15:45:22 +0200 Subject: [PATCH 11/17] GUI fixes for Windows --- lib/Slic3r/GUI/Plater.pm | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3ad70831..a90dff86 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -14,7 +14,7 @@ use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT use base 'Wx::Panel'; use constant TB_MORE => &Wx::NewId; -use constant TB_LESS => &Wx::NewId; +use constant TB_FEWER => &Wx::NewId; use constant TB_INFO => &Wx::NewId; use constant TB_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; @@ -64,7 +64,7 @@ sub new { $self->{htoolbar} = Wx::ToolBar->new($self, -1, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL | wxTB_TEXT | wxBORDER_SIMPLE | wxTAB_TRAVERSAL); $self->{htoolbar}->AddTool(TB_INFO, "Open", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddTool(TB_LESS, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_45CCW, "45° ccw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_anticlockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); @@ -73,10 +73,10 @@ sub new { $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), ''); } else { - my %tbar_buttons = (info => "Open", increase => "More", decrease => "Less", rotate45ccw => "45°", rotate45cw => "45°", + my %tbar_buttons = (info => "Open", increase => "More", decrease => "Fewer", rotate45ccw => "45°", rotate45cw => "45°", rotate => "Rotate…", changescale => "Scale…", split => "Split"); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); - for (qw(open increase decrease rotate45ccw rotate45cw rotate changescale split)) { + for (qw(info increase decrease rotate45ccw rotate45cw rotate changescale split)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } @@ -115,7 +115,7 @@ sub new { export_gcode cog_go.png export_stl brick_go.png - open package.png + info package.png increase add.png decrease delete.png rotate45cw arrow_rotate_clockwise.png @@ -139,15 +139,15 @@ sub new { if ($self->{htoolbar}) { EVT_TOOL($self, TB_MORE, \&increase); - EVT_TOOL($self, TB_LESS, \&decrease); - EVT_TOOL($self, TB_INFO, sub { $self->list_item_activated(undef, $self->{selected_objects}->[0][0]) }); + EVT_TOOL($self, TB_FEWER, \&decrease); + EVT_TOOL($self, TB_INFO, sub { $_[0]->object_dialog }); EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) }); EVT_TOOL($self, TB_45CCW, sub { $_[0]->rotate(45) }); EVT_TOOL($self, TB_ROTATE, sub { $_[0]->rotate(undef) }); EVT_TOOL($self, TB_SCALE, \&changescale); EVT_TOOL($self, TB_SPLIT, \&split_object); } else { - EVT_BUTTON($self, $self->{btn_open}, sub { $self->list_item_activated(undef, $self->{selected_objects}->[0][0]) }); + EVT_BUTTON($self, $self->{btn_info}, sub { $_[0]->object_dialog }); EVT_BUTTON($self, $self->{btn_increase}, \&increase); EVT_BUTTON($self, $self->{btn_decrease}, \&decrease); EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) }); @@ -945,8 +945,7 @@ sub mouse_event { $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); } elsif ($event->ButtonDClick) { - $parent->list_item_activated(undef, $parent->{selected_objects}->[0][0]) - if @{$parent->{selected_objects}}; + $parent->object_dialog if @{$parent->{selected_objects}}; } elsif ($event->Dragging) { return if !$self->{drag_start_pos}; # concurrency problems for my $preview ($self->{drag_object}) { @@ -990,7 +989,18 @@ sub list_item_activated { my ($self, $event, $obj_idx) = @_; $obj_idx //= $event->GetIndex; - my $dlg = Slic3r::GUI::Plater::ObjectDialog->new($self, + $self->object_dialog($obj_idx); +} + +sub object_dialog { + my $self = shift; + my ($obj_idx) = @_; + + if (!defined $obj_idx) { + ($obj_idx, undef) = $self->selected_object; + } + + my $dlg = Slic3r::GUI::Plater::ObjectDialog->new($self, object => $self->{objects}[$obj_idx], ); $dlg->ShowModal; @@ -1010,11 +1020,11 @@ sub selection_changed { my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove open increase decrease rotate45cw rotate45ccw rotate changescale split); + for grep $self->{"btn_$_"}, qw(remove info increase decrease rotate45cw rotate45ccw rotate changescale split); if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_INFO, TB_MORE, TB_LESS, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); + for (TB_INFO, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); } } From fde6e371a923319eafdc392ba45f5e11f982fcee Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 16:35:21 +0200 Subject: [PATCH 12/17] Use multiple colors for multimaterial models in 3D preview --- lib/Slic3r/GUI/Plater/ObjectDialog.pm | 2 +- lib/Slic3r/GUI/PreviewCanvas.pm | 73 +++++++++++++++++---------- utils/view-mesh.pl | 6 +-- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 9b851ff7..c7f46f2a 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -119,7 +119,7 @@ sub new { $self->{object} = $params{object}; my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object), 1, wxEXPAND, 0); $self->SetSizer($sizer); $sizer->SetSizeHints($self); diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 493d3f0f..065027e8 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -7,37 +7,56 @@ use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL use OpenGL qw(:glconstants :glfunctions :glufunctions); use base qw(Wx::GLCanvas Class::Accessor); use Math::Trig qw(asin); -use List::Util qw(reduce min max); +use List::Util qw(reduce min max first); use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan); use Wx::GLCanvas qw(:all); __PACKAGE__->mk_accessors( qw(quat dirty init mview_init - mesh_center mesh_size - verts norms initpos + object_center object_size + volumes initpos sphi stheta) ); use constant TRACKBALLSIZE => 0.8; use constant TURNTABLE_MODE => 1; +use constant COLORS => [ [1,1,1], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ]; sub new { - my ($class, $parent, $mesh) = @_; + my ($class, $parent, $object) = @_; my $self = $class->SUPER::new($parent); $self->quat((0, 0, 0, 1)); $self->sphi(45); $self->stheta(-45); - # prepare mesh - { - $mesh->align_to_origin; - $self->mesh_center($mesh->center); - $self->mesh_size($mesh->size); + $object->align_to_origin; + $self->object_center($object->center); + $self->object_size($object->size); + + # group mesh(es) by material + my @materials = (); + $self->volumes([]); + foreach my $volume (@{$object->volumes}) { + my $mesh = $volume->mesh; - my @verts = map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; - $self->verts(OpenGL::Array->new_list(GL_FLOAT, @verts)); + my $material_id = $volume->material_id // '_'; + my $color_idx = first { $materials[$_] eq $material_id } 0..$#materials; + if (!defined $color_idx) { + push @materials, $material_id; + $color_idx = $#materials; + } + push @{$self->volumes}, my $v = { + color => COLORS->[ $color_idx % scalar(@{&COLORS}) ], + }; - my @norms = map { @$_, @$_, @$_ } map normalize(triangle_normal(map $mesh->vertices->[$_], @$_)), @{$mesh->facets}; - $self->norms(OpenGL::Array->new_list(GL_FLOAT, @norms)); + { + my @verts = map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; + $v->{verts} = OpenGL::Array->new_list(GL_FLOAT, @verts); + } + + { + my @norms = map { @$_, @$_, @$_ } map normalize(triangle_normal(map $mesh->vertices->[$_], @$_)), @{$mesh->facets}; + $v->{norms} = OpenGL::Array->new_list(GL_FLOAT, @norms); + } } EVT_PAINT($self, sub { @@ -277,7 +296,7 @@ sub ResetModelView { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); my $win_size = $self->GetClientSize(); - my $ratio = $factor * min($win_size->width, $win_size->height) / max(@{ $self->mesh_size }); + my $ratio = $factor * min($win_size->width, $win_size->height) / max(@{ $self->object_size }); glScalef($ratio, $ratio, 1); } @@ -292,8 +311,8 @@ sub Resize { glMatrixMode(GL_PROJECTION); glLoadIdentity(); - my $mesh_size = $self->mesh_size; - glOrtho(-$x/2, $x/2, -$y/2, $y/2, 0.5, 2 * max(@$mesh_size)); + my $object_size = $self->object_size; + glOrtho(-$x/2, $x/2, -$y/2, $y/2, 0.5, 2 * max(@$object_size)); glMatrixMode(GL_MODELVIEW); unless ($self->mview_init) { @@ -350,19 +369,19 @@ sub Render { glPushMatrix(); - my $mesh_size = $self->mesh_size; - glTranslatef(0, 0, -max(@$mesh_size[0..1])); + my $object_size = $self->object_size; + glTranslatef(0, 0, -max(@$object_size[0..1])); my @rotmat = quat_to_rotmatrix($self->quat); glMultMatrixd_p(@rotmat[0..15]); glRotatef($self->stheta, 1, 0, 0); glRotatef($self->sphi, 0, 0, 1); - glTranslatef(map -$_, @{ $self->mesh_center }); + glTranslatef(map -$_, @{ $self->object_center }); $self->draw_mesh; # draw axes { - my $axis_len = 2 * max(@{ $self->mesh_size }); + my $axis_len = 2 * max(@{ $self->object_size }); glLineWidth(2); glBegin(GL_LINES); # draw line for x axis @@ -421,12 +440,14 @@ sub draw_mesh { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); - glVertexPointer_p(3, $self->verts); - - glCullFace(GL_BACK); - glNormalPointer_p($self->norms); - glColor3f(1, 1, 1); - glDrawArrays(GL_TRIANGLES, 0, $self->verts->elements / 3); + foreach my $volume (@{$self->volumes}) { + glVertexPointer_p(3, $volume->{verts}); + + glCullFace(GL_BACK); + glNormalPointer_p($volume->{norms}); + glColor3f(@{ $volume->{color} }); + glDrawArrays(GL_TRIANGLES, 0, $volume->{verts}->elements / 3); + } glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl index 3e4d2723..e4d36267 100644 --- a/utils/view-mesh.pl +++ b/utils/view-mesh.pl @@ -27,7 +27,7 @@ my %opt = (); { my $model = Slic3r::Model->read_from_file($ARGV[0]); - $Slic3r::ViewMesh::mesh = $model->mesh; + $Slic3r::ViewMesh::object = $model->objects->[0]; my $app = Slic3r::ViewMesh->new; $app->MainLoop; } @@ -49,7 +49,7 @@ package Slic3r::ViewMesh; use Wx qw(:sizer); use base qw(Wx::App); -our $mesh; +our $object; sub OnInit { my $self = shift; @@ -58,7 +58,7 @@ sub OnInit { my $panel = Wx::Panel->new($frame, -1); my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add(Slic3r::GUI::PreviewCanvas->new($panel, $mesh), 1, wxEXPAND, 0); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($panel, $object), 1, wxEXPAND, 0); $panel->SetSizer($sizer); $sizer->SetSizeHints($panel); From e96d7b1d6a12c7b5797dc4185e5fcbd315e25532 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 17:26:55 +0200 Subject: [PATCH 13/17] Show facets, materials and manifoldness in info panel --- MANIFEST | 1 + lib/Slic3r/GUI/Plater.pm | 48 +++++++++++++++++++++++++++++++++------ var/error.png | Bin 0 -> 666 bytes 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100755 var/error.png diff --git a/MANIFEST b/MANIFEST index 57f9e42e..c3162de9 100644 --- a/MANIFEST +++ b/MANIFEST @@ -118,6 +118,7 @@ var/cog_go.png var/cross.png var/delete.png var/disk.png +var/error.png var/funnel.png var/hourglass.png var/layers.png diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8ab6d72f..a7fdc76e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -249,12 +249,18 @@ sub new { { my $box = Wx::StaticBox->new($self, -1, "Info"); $object_info_sizer = Wx::StaticBoxSizer->new($box, wxVERTICAL); - my $grid_sizer = Wx::FlexGridSizer->new(2, 2, 5, 5); - $object_info_sizer->Add($grid_sizer); + my $grid_sizer = Wx::FlexGridSizer->new(3, 4, 5, 5); + $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + $grid_sizer->AddGrowableCol(1, 1); + $grid_sizer->AddGrowableCol(3, 1); + $object_info_sizer->Add($grid_sizer, 0, wxEXPAND); my @info = ( - size => "Size", - volume => "Volume", + size => "Size", + volume => "Volume", + facets => "Facets", + materials => "Materials", + manifold => "Manifold", ); while (my $field = shift @info) { my $label = shift @info; @@ -264,7 +270,17 @@ sub new { $self->{"object_info_$field"} = Wx::StaticText->new($self, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT); $self->{"object_info_$field"}->SetFont($Slic3r::GUI::small_font); - $grid_sizer->Add($self->{"object_info_$field"}, 0); + if ($field eq 'manifold') { + $self->{object_info_manifold_warning_icon} = Wx::StaticBitmap->new($self, -1, Wx::Bitmap->new("$Slic3r::var/error.png", wxBITMAP_TYPE_PNG)); + $self->{object_info_manifold_warning_icon}->Hide; + + my $h_sizer = Wx::BoxSizer->new(wxHORIZONTAL); + $h_sizer->Add($self->{object_info_manifold_warning_icon}, 0); + $h_sizer->Add($self->{"object_info_$field"}, 0); + $grid_sizer->Add($h_sizer, 0, wxEXPAND); + } else { + $grid_sizer->Add($self->{"object_info_$field"}, 0); + } } } @@ -1083,14 +1099,32 @@ 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", @{$object->transformed_size}); + $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$object->transformed_size})); + $self->{object_info_materials}->SetLabel($object->materials); 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 $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; + + my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges', + @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)}; + $self->{object_info_manifold}->SetToolTipString($message); + $self->{object_info_manifold_warning_icon}->SetToolTipString($message); + } else { + $self->{object_info_manifold}->SetLabel("Yes"); + } + } else { + $self->{object_info_facets}->SetLabel($object->facets); } } else { - $self->{"object_info_$_"}->SetLabel("") for qw(size volume); + $self->{"object_info_$_"}->SetLabel("") for qw(size volume facets materials manifold); + $self->{object_info_manifold_warning_icon}->Hide; + $self->{object_info_manifold}->SetToolTipString(""); } + $self->Layout; } } diff --git a/var/error.png b/var/error.png new file mode 100755 index 0000000000000000000000000000000000000000..628cf2dae3d419ae220c8928ac71393b480745a3 GIT binary patch literal 666 zcmV;L0%iS)P)eOSYYtbpBV}~vsBnU!_?2tr-P=|^T zED%wc9ezHgW@NMb!^uT_|SvCpFLJylbx zY%bpaTGI8IYXMN$9w<3j9VkA~NYOKEQXsj?6a9_hcwfU$acAhJhB)zb_w@MVUEy@S zX&I>K-R!bhu3?(6bHWIg$HEl7{9g>>&l_qdd+UYb(1~BCo9LptNq&8>!yoJ3Ui(i5 zRJ|XnYBklL!{@$-7=3mJ>P@1c=7Oc79e-V7yf+%lD2!I;Y&nXBZ>=B!5?CB>LvEx6 znI%n)qqi$#X#wKB(U7XP2P=+4{b@j#r%9-K(8UqtSDk>0UKzf*HM9yqMZ1D!$2MdZ zR=`U>0zhOH1XqN?nY@AQqB7)Fp4{v&dKXvb43hZKvnN8;Po;+jY*}~*Z|W9Q0W%{D z^T}Cc<|r(Su=1K=P5>Z4 zg`et&Va}tdzBS-G-ZcO)zCWpJvGQwrHZ`@wpM420ac@bI5~KkTFfGEM3sPWO8co4^fI6lPnA)Y{ef%@{+SnoUk0+dW+*{8WvF8}}l07*qoM6N<$g7cXs A&j0`b literal 0 HcmV?d00001 From ae21a4588645b6fc37c2d6a9f94efd0f1b52ebe7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 18:01:59 +0200 Subject: [PATCH 14/17] Distinct dialogs for 3D preview and object settings --- MANIFEST | 3 +- lib/Slic3r/GUI.pm | 4 +- lib/Slic3r/GUI/Plater.pm | 59 ++++++++---- lib/Slic3r/GUI/Plater/ObjectPreviewDialog.pm | 24 +++++ ...bjectDialog.pm => ObjectSettingsDialog.pm} | 91 +------------------ lib/Slic3r/Model.pm | 12 ++- 6 files changed, 85 insertions(+), 108 deletions(-) create mode 100644 lib/Slic3r/GUI/Plater/ObjectPreviewDialog.pm rename lib/Slic3r/GUI/Plater/{ObjectDialog.pm => ObjectSettingsDialog.pm} (73%) diff --git a/MANIFEST b/MANIFEST index c3162de9..62df31da 100644 --- a/MANIFEST +++ b/MANIFEST @@ -37,7 +37,8 @@ lib/Slic3r/GUI/AboutDialog.pm lib/Slic3r/GUI/ConfigWizard.pm lib/Slic3r/GUI/OptionsGroup.pm lib/Slic3r/GUI/Plater.pm -lib/Slic3r/GUI/Plater/ObjectDialog.pm +lib/Slic3r/GUI/Plater/ObjectPreviewDialog.pm +lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm lib/Slic3r/GUI/Preferences.pm lib/Slic3r/GUI/PreviewCanvas.pm lib/Slic3r/GUI/SkeinPanel.pm diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 7b8eca39..e5d92ec3 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -7,7 +7,8 @@ use FindBin; use Slic3r::GUI::AboutDialog; use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Plater; -use Slic3r::GUI::Plater::ObjectDialog; +use Slic3r::GUI::Plater::ObjectPreviewDialog; +use Slic3r::GUI::Plater::ObjectSettingsDialog; use Slic3r::GUI::Preferences; use Slic3r::GUI::OptionsGroup; use Slic3r::GUI::SkeinPanel; @@ -56,6 +57,7 @@ our $Settings = { }, }; +our $have_button_icons = &Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/; our $small_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); $small_font->SetPointSize(11) if !&Wx::wxMSW; our $medium_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a7fdc76e..d1ea164d 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -21,12 +21,13 @@ use constant TB_EXPORT_GCODE => &Wx::NewId; use constant TB_EXPORT_STL => &Wx::NewId; use constant TB_MORE => &Wx::NewId; use constant TB_FEWER => &Wx::NewId; -use constant TB_INFO => &Wx::NewId; use constant TB_45CW => &Wx::NewId; use constant TB_45CCW => &Wx::NewId; use constant TB_ROTATE => &Wx::NewId; use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; +use constant TB_VIEW => &Wx::NewId; +use constant TB_SETTINGS => &Wx::NewId; my $THUMBNAIL_DONE_EVENT : shared = Wx::NewEventType; my $PROGRESS_BAR_EVENT : shared = Wx::NewEventType; @@ -73,7 +74,6 @@ sub new { $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), ''); $self->{htoolbar}->AddSeparator; - $self->{htoolbar}->AddTool(TB_INFO, "Open", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_MORE, "More", Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_FEWER, "Fewer", Wx::Bitmap->new("$Slic3r::var/delete.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; @@ -81,15 +81,16 @@ sub new { $self->{htoolbar}->AddTool(TB_45CW, "45° cw", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_ROTATE, "Rotate…", Wx::Bitmap->new("$Slic3r::var/arrow_rotate_clockwise.png", wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddTool(TB_SCALE, "Scale…", Wx::Bitmap->new("$Slic3r::var/arrow_out.png", wxBITMAP_TYPE_PNG), ''); - $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SPLIT, "Split", Wx::Bitmap->new("$Slic3r::var/shape_ungroup.png", wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddSeparator; + $self->{htoolbar}->AddTool(TB_VIEW, "View", Wx::Bitmap->new("$Slic3r::var/package.png", wxBITMAP_TYPE_PNG), ''); + $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new("$Slic3r::var/cog.png", wxBITMAP_TYPE_PNG), ''); } else { my %tbar_buttons = ( load => "Add…", remove => "Delete", reset => "Delete All", arrange => "Arrange", - info => "Open", increase => "", decrease => "", rotate45ccw => "", @@ -97,9 +98,11 @@ sub new { rotate => "Rotate…", changescale => "Scale…", split => "Split", + view => "View", + settings => "Settings…", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); - for (qw(load remove reset arrange info increase decrease rotate45ccw rotate45cw rotate changescale split)) { + for (qw(load remove reset arrange increase decrease rotate45ccw rotate45cw rotate changescale split view settings)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } @@ -127,7 +130,7 @@ sub new { $self->{btn_export_gcode}->SetFont($Slic3r::GUI::small_font); $self->{btn_export_stl}->SetFont($Slic3r::GUI::small_font); - if (&Wx::wxVERSION_STRING =~ / 2\.9\.[1-9]/) { + if ($Slic3r::GUI::have_button_icons) { my %icons = qw( load brick_add.png remove brick_delete.png @@ -159,7 +162,6 @@ sub new { 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); - EVT_TOOL($self, TB_INFO, sub { $_[0]->object_dialog }); EVT_TOOL($self, TB_MORE, \&increase); EVT_TOOL($self, TB_FEWER, \&decrease); EVT_TOOL($self, TB_45CW, sub { $_[0]->rotate(-45) }); @@ -167,12 +169,13 @@ sub new { EVT_TOOL($self, TB_ROTATE, sub { $_[0]->rotate(undef) }); EVT_TOOL($self, TB_SCALE, \&changescale); EVT_TOOL($self, TB_SPLIT, \&split_object); + 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_remove}, sub { $self->remove() }); # explicitly pass no argument to remove EVT_BUTTON($self, $self->{btn_reset}, \&reset); EVT_BUTTON($self, $self->{btn_arrange}, \&arrange); - EVT_BUTTON($self, $self->{btn_info}, sub { $_[0]->object_dialog }); EVT_BUTTON($self, $self->{btn_increase}, \&increase); EVT_BUTTON($self, $self->{btn_decrease}, \&decrease); EVT_BUTTON($self, $self->{btn_rotate45cw}, sub { $_[0]->rotate(-45) }); @@ -180,6 +183,8 @@ sub new { EVT_BUTTON($self, $self->{btn_changescale}, \&changescale); EVT_BUTTON($self, $self->{btn_rotate}, sub { $_[0]->rotate(undef) }); EVT_BUTTON($self, $self->{btn_split}, \&split_object); + EVT_BUTTON($self, $self->{btn_view}, sub { $_[0]->object_preview_dialog }); + EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) @@ -402,7 +407,7 @@ sub load_file { } $process_dialog->Destroy; - $self->statusbar->SetStatusText("Loaded $basename - Double click object for more info"); + $self->statusbar->SetStatusText("Loaded $basename"); } sub object_loaded { @@ -1014,7 +1019,7 @@ sub mouse_event { $self->{drag_object} = undef; $self->SetCursor(wxSTANDARD_CURSOR); } elsif ($event->ButtonDClick) { - $parent->object_dialog if @{$parent->{selected_objects}}; + $parent->object_preview_dialog if @{$parent->{selected_objects}}; } elsif ($event->Dragging) { return if !$self->{drag_start_pos}; # concurrency problems for my $preview ($self->{drag_object}) { @@ -1058,10 +1063,10 @@ sub list_item_activated { my ($self, $event, $obj_idx) = @_; $obj_idx //= $event->GetIndex; - $self->object_dialog($obj_idx); + $self->object_preview_dialog($obj_idx); } -sub object_dialog { +sub object_preview_dialog { my $self = shift; my ($obj_idx) = @_; @@ -1069,7 +1074,21 @@ sub object_dialog { ($obj_idx, undef) = $self->selected_object; } - my $dlg = Slic3r::GUI::Plater::ObjectDialog->new($self, + my $dlg = Slic3r::GUI::Plater::ObjectPreviewDialog->new($self, + object => $self->{objects}[$obj_idx], + ); + $dlg->ShowModal; +} + +sub object_settings_dialog { + my $self = shift; + my ($obj_idx) = @_; + + if (!defined $obj_idx) { + ($obj_idx, undef) = $self->selected_object; + } + + my $dlg = Slic3r::GUI::Plater::ObjectSettingsDialog->new($self, object => $self->{objects}[$obj_idx], ); $dlg->ShowModal; @@ -1078,9 +1097,15 @@ sub object_dialog { sub object_list_changed { my $self = shift; - my $method = @{$self->{objects}} ? 'Enable' : 'Disable'; + my $have_objects = @{$self->{objects}} ? 1 : 0; + my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method for grep $self->{"btn_$_"}, qw(reset arrange export_gcode export_stl); + + if ($self->{htoolbar}) { + $self->{htoolbar}->EnableTool($_, $have_objects) + for (TB_RESET, TB_ARRANGE); + } } sub selection_changed { @@ -1089,11 +1114,11 @@ sub selection_changed { my $method = $have_sel ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(remove info increase decrease rotate45cw rotate45ccw rotate changescale split); + for grep $self->{"btn_$_"}, qw(remove increase decrease rotate45cw rotate45ccw rotate changescale split view settings); if ($self->{htoolbar}) { $self->{htoolbar}->EnableTool($_, $have_sel) - for (TB_REMOVE, TB_INFO, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT); + for (TB_REMOVE, TB_MORE, TB_FEWER, TB_45CW, TB_45CCW, TB_ROTATE, TB_SCALE, TB_SPLIT, TB_VIEW, TB_SETTINGS); } if ($self->{object_info_size}) { # have we already loaded the info pane? @@ -1109,6 +1134,8 @@ sub selection_changed { $self->{object_info_manifold}->SetLabel(sprintf("Auto-repaired (%d errors)", $errors)); $self->{object_info_manifold_warning_icon}->Show; + # we don't show normals_fixed because we never provide normals + # to admesh, so it generates normals for all facets my $message = sprintf '%d degenerate facets, %d edges fixed, %d facets removed, %d facets added, %d facets reversed, %d backwards edges', @$stats{qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)}; $self->{object_info_manifold}->SetToolTipString($message); diff --git a/lib/Slic3r/GUI/Plater/ObjectPreviewDialog.pm b/lib/Slic3r/GUI/Plater/ObjectPreviewDialog.pm new file mode 100644 index 00000000..fbcc5d77 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/ObjectPreviewDialog.pm @@ -0,0 +1,24 @@ +package Slic3r::GUI::Plater::ObjectPreviewDialog; +use strict; +use warnings; +use utf8; + +use Wx qw(:dialog :id :misc :sizer :systemsettings :notebook wxTAB_TRAVERSAL); +use Wx::Event qw(EVT_BUTTON); +use base 'Wx::Dialog'; + +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}; + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object), 1, wxEXPAND, 0); + $self->SetSizer($sizer); + $self->SetMinSize($self->GetSize); + + return $self; +} + +1; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm similarity index 73% rename from lib/Slic3r/GUI/Plater/ObjectDialog.pm rename to lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index c7f46f2a..f284db67 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -1,4 +1,4 @@ -package Slic3r::GUI::Plater::ObjectDialog; +package Slic3r::GUI::Plater::ObjectSettingsDialog; use strict; use warnings; use utf8; @@ -10,13 +10,10 @@ use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + 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->{tabpanel} = Wx::Notebook->new($self, -1, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL); - $self->{tabpanel}->AddPage($self->{preview} = Slic3r::GUI::Plater::ObjectDialog::PreviewTab->new($self->{tabpanel}, object => $self->{object}), "Preview") - if $Slic3r::GUI::have_OpenGL; - $self->{tabpanel}->AddPage($self->{info} = Slic3r::GUI::Plater::ObjectDialog::InfoTab->new($self->{tabpanel}, object => $self->{object}), "Info"); $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"); @@ -42,90 +39,6 @@ sub new { return $self; } -package Slic3r::GUI::Plater::ObjectDialog::InfoTab; -use Wx qw(:dialog :id :misc :sizer :systemsettings); -use base 'Wx::Panel'; - -sub new { - my $class = shift; - my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); - $self->{object} = $params{object}; - - my $grid_sizer = Wx::FlexGridSizer->new(3, 2, 5, 5); - $grid_sizer->SetFlexibleDirection(wxHORIZONTAL); - $grid_sizer->AddGrowableCol(1); - - my $label_font = Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); - $label_font->SetPointSize(10); - - my $properties = $self->get_properties; - foreach my $property (@$properties) { - my $label = Wx::StaticText->new($self, -1, $property->[0] . ":"); - my $value = Wx::StaticText->new($self, -1, $property->[1]); - $label->SetFont($label_font); - $grid_sizer->Add($label, 1, wxALIGN_BOTTOM); - $grid_sizer->Add($value, 0); - } - - $self->SetSizer($grid_sizer); - $grid_sizer->SetSizeHints($self); - - return $self; -} - -sub get_properties { - my $self = shift; - - my $object = $self->{object}; - my $properties = [ - ['Name' => $object->name], - ['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->transformed_size}], - ['Facets' => $object->facets], - ['Vertices' => $object->vertices], - ['Materials' => $object->materials], - ]; - - if (my $stats = $object->mesh_stats) { - push @$properties, - [ 'Shells' => $stats->{number_of_parts} ], - [ 'Volume' => sprintf('%.2f', $stats->{volume} * ($object->scale**3)) ], - [ 'Degenerate facets' => $stats->{degenerate_facets} ], - [ 'Edges fixed' => $stats->{edges_fixed} ], - [ 'Facets removed' => $stats->{facets_removed} ], - [ 'Facets added' => $stats->{facets_added} ], - [ 'Facets reversed' => $stats->{facets_reversed} ], - [ 'Backwards edges' => $stats->{backwards_edges} ], - # we don't show normals_fixed because we never provide normals - # to admesh, so it generates normals for all facets - ; - } else { - push @$properties, - ['Two-Manifold' => $object->is_manifold ? 'Yes' : 'No'], - ; - } - - return $properties; -} - -package Slic3r::GUI::Plater::ObjectDialog::PreviewTab; -use Wx qw(:dialog :id :misc :sizer :systemsettings); -use base 'Wx::Panel'; - -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); - $sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object), 1, wxEXPAND, 0); - $self->SetSizer($sizer); - $sizer->SetSizeHints($self); - - return $self; -} - package Slic3r::GUI::Plater::ObjectDialog::SettingsTab; use Wx qw(:dialog :id :misc :sizer :systemsettings :button :icon); use Wx::Grid; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 28ca25b1..0c5d03fa 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -295,7 +295,7 @@ package Slic3r::Model::Object; use Moo; use File::Basename qw(basename); -use List::Util qw(first); +use List::Util qw(first sum); use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D); use Storable qw(dclone); @@ -430,6 +430,11 @@ sub materials_count { return scalar keys %materials; } +sub facets_count { + my $self = shift; + return sum(map $_->facets_count, @{$self->volumes}); +} + sub check_manifoldness { my $self = shift; return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1; @@ -485,6 +490,11 @@ sub mesh { ); } +sub facets_count { + my $self = shift; + return scalar(@{$self->facets}); # TODO: optimize in XS +} + package Slic3r::Model::Instance; use Moo; From ae9f13ed54fd746793db440b66f84e0e31c1d6d2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 18:04:45 +0200 Subject: [PATCH 15/17] Minor fix to font size --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index d1ea164d..04c0ae50 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -870,6 +870,7 @@ sub on_config_change { while (@$choices < $value) { my @presets = $choices->[0]->GetStrings; push @$choices, Wx::Choice->new($self, -1, wxDefaultPosition, [150, -1], [@presets]); + $choices->[-1]->SetFont($Slic3r::GUI::small_font); $self->{preset_choosers_sizers}{filament}->Add($choices->[-1], 0, wxEXPAND | wxBOTTOM, FILAMENT_CHOOSERS_SPACING); EVT_CHOICE($choices->[-1], $choices->[-1], sub { $self->on_select_preset('filament', @_) }); my $i = first { $choices->[-1]->GetString($_) eq ($Slic3r::GUI::Settings->{presets}{"filament_" . $#$choices} || '') } 0 .. $#presets; From cb0ee9729f0ddf199e279eb84d30053d88d89b41 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 18:08:56 +0200 Subject: [PATCH 16/17] A couple fixes for Windows --- lib/Slic3r/GUI/Plater.pm | 3 ++- lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 04c0ae50..a5124fa8 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -139,7 +139,6 @@ sub new { export_gcode cog_go.png export_stl brick_go.png - info package.png increase add.png decrease delete.png rotate45cw arrow_rotate_clockwise.png @@ -147,6 +146,8 @@ sub new { rotate arrow_rotate_clockwise.png changescale arrow_out.png split shape_ungroup.png + view package.png + settings cog.png ); for (grep $self->{"btn_$_"}, keys %icons) { $self->{"btn_$_"}->SetBitmap(Wx::Bitmap->new("$Slic3r::var/$icons{$_}", wxBITMAP_TYPE_PNG)); diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index f284db67..fb7b6820 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -73,7 +73,9 @@ sub new { # create the button my $btn = Wx::BitmapButton->new($self, -1, Wx::Bitmap->new("$Slic3r::var/add.png", wxBITMAP_TYPE_PNG)); EVT_BUTTON($self, $btn, sub { - my $opt_key = $self->{options}[$choice->GetSelection]; + 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->update_optgroup; }); From 026e0c06e4c0d1e418416c66aa9236cccaa7d27c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Aug 2013 19:52:32 +0200 Subject: [PATCH 17/17] Ability to customize how materials are mapped to extruders. #1020 --- lib/Slic3r/Format/AMF/Parser.pm | 4 +- lib/Slic3r/GUI/Plater.pm | 45 ++++++++----- lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm | 66 +++++++++++++++++++ lib/Slic3r/Model.pm | 23 +++++++ lib/Slic3r/Print.pm | 28 ++++---- lib/Slic3r/Print/Object.pm | 2 +- 6 files changed, 134 insertions(+), 34 deletions(-) diff --git a/lib/Slic3r/Format/AMF/Parser.pm b/lib/Slic3r/Format/AMF/Parser.pm index 22e4204b..19fc60e6 100644 --- a/lib/Slic3r/Format/AMF/Parser.pm +++ b/lib/Slic3r/Format/AMF/Parser.pm @@ -27,14 +27,14 @@ sub start_element { $self->{_coordinate} = $data->{LocalName}; } elsif ($data->{LocalName} eq 'volume') { $self->{_volume} = $self->{_object}->add_volume( - material_id => $self->_get_attribute($data, 'materialid') || undef, + material_id => $self->_get_attribute($data, 'materialid') // undef, ); } elsif ($data->{LocalName} eq 'triangle') { $self->{_triangle} = ["", "", ""]; } elsif ($self->{_triangle} && $data->{LocalName} =~ /^v([123])$/ && $self->{_tree}[-1] eq 'triangle') { $self->{_vertex_idx} = $1-1; } elsif ($data->{LocalName} eq 'material') { - my $material_id = $self->_get_attribute($data, 'id') || '_'; + my $material_id = $self->_get_attribute($data, 'id') // '_'; $self->{_material} = $self->{_model}->set_material($material_id); } elsif ($data->{LocalName} eq 'metadata' && $self->{_tree}[-1] eq 'material') { $self->{_material_metadata_type} = $self->_get_attribute($data, 'type'); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a5124fa8..950aa9d1 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -389,8 +389,9 @@ sub load_file { name => $basename, input_file => $input_file, input_file_object_id => $i, - model_object => $model->objects->[$i], - mesh_stats => $model->objects->[$i]->mesh_stats, # so that we can free model_object + 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}) @@ -589,7 +590,8 @@ sub split_object { name => basename($current_object->input_file), input_file => $current_object->input_file, input_file_object_id => undef, - model_object => $model_object, + model => $new_model, + model_object_idx => $#{$new_model->objects}, instances => [ map $bb->min_point, 1..$current_copies_num ], ); push @{ $self->{objects} }, $object; @@ -629,6 +631,8 @@ 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); @@ -788,6 +792,7 @@ sub make_model { 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( @@ -834,7 +839,6 @@ sub on_thumbnail_made { my $self = shift; my ($obj_idx) = @_; - $self->{objects}[$obj_idx]->free_model_object; $self->recenter; $self->{canvas}->Refresh; } @@ -1221,7 +1225,8 @@ 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_object' => (is => 'rw', required => 1, trigger => 1); +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'); # 3D bb of original object (aligned to origin) with no rotation or scaling has 'convex_hull' => (is => 'rw'); # 2D convex hull of original object (aligned to origin) with no rotation or scaling has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail); @@ -1232,6 +1237,7 @@ 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 => 'rw'); # statistics @@ -1242,16 +1248,17 @@ has 'is_manifold' => (is => 'rw'); sub _trigger_model_object { my $self = shift; - if ($self->model_object) { - $self->model_object->align_to_origin; - $self->bounding_box($self->model_object->bounding_box); + 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); - my $mesh = $self->model_object->mesh; + my $mesh = $model_object->mesh; $self->convex_hull(Slic3r::Polygon->new(@{Math::ConvexHull::MonotoneChain::convex_hull($mesh->used_vertices)})); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); - $self->materials($self->model_object->materials_count); + $self->materials($model_object->materials_count); } } @@ -1290,18 +1297,21 @@ sub free_model_object { # 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_object(undef); + $self->model(undef); + $self->model_object_idx(undef); } sub get_model_object { my $self = shift; - my $object = $self->model_object; - if (!$object) { - my $model = Slic3r::Model->read_from_file($self->input_file); - $object = $model->objects->[$self->input_file_object_id]; + if ($self->model) { + return $self->model->objects->[$self->model_object_idx]; } - return $object; + + return Slic3r::Model + ->read_from_file($self->input_file) + ->objects + ->[$self->input_file_object_id]; } sub instances_count { @@ -1312,7 +1322,7 @@ sub instances_count { sub make_thumbnail { my $self = shift; - my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin + my $mesh = $self->get_model_object->mesh; # $self->model_object is already aligned to origin my $thumbnail = Slic3r::ExPolygon::Collection->new; if (@{$mesh->facets} <= 5000) { $thumbnail->append(@{ $mesh->horizontal_projection }); @@ -1331,7 +1341,6 @@ sub make_thumbnail { $thumbnail->scale(&Slic3r::SCALING_FACTOR); $self->thumbnail($thumbnail); # ignored in multi-threaded environments - $self->free_model_object; return $thumbnail; } diff --git a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm index fb7b6820..32b9c38f 100644 --- a/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectSettingsDialog.pm @@ -16,6 +16,7 @@ sub new { $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"); my $buttons = $self->CreateStdDialogButtonSizer(wxOK); EVT_BUTTON($self, wxID_OK, sub { @@ -25,6 +26,7 @@ sub new { # notify tabs $self->{layers}->Closing; + $self->{materials}->Closing; $self->EndModal(wxID_OK); }); @@ -254,4 +256,68 @@ sub _get_ranges { return sort { $a->[0] <=> $b->[0] } @ranges; } +package Slic3r::GUI::Plater::ObjectDialog::MaterialsTab; +use Wx qw(:dialog :id :misc :sizer :systemsettings :button :icon); +use Wx::Grid; +use Wx::Event qw(EVT_BUTTON); +use base 'Wx::Panel'; + +sub new { + my $class = shift; + my ($parent, %params) = @_; + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize); + $self->{object} = $params{object}; + + $self->{sizer} = Wx::BoxSizer->new(wxVERTICAL); + + # descriptive text + { + my $label = Wx::StaticText->new($self, -1, "In this section you can assign object materials to your extruders.", + wxDefaultPosition, [-1, 25]); + $label->SetFont(Wx::SystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); + $self->{sizer}->Add($label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10); + } + + # get unique materials used in this object + $self->{materials} = [ $self->{object}->get_model_object->unique_materials ]; + + # build an OptionsGroup + $self->{mapping} = { + (map { $self->{materials}[$_] => $_+1 } 0..$#{ $self->{materials} }), # defaults + %{$self->{object}->material_mapping}, + }; + my $optgroup = Slic3r::GUI::OptionsGroup->new( + parent => $self, + title => 'Extruders', + label_width => 300, + options => [ + map { + my $i = $_; + my $material_id = $self->{materials}[$i]; + { + opt_key => "material_extruder_$_", + type => 'i', + label => $self->{object}->get_model_object->model->get_material_name($material_id), + min => 1, + default => $self->{mapping}{$material_id}, + on_change => sub { $self->{mapping}{$material_id} = $_[0] }, + } + } 0..$#{ $self->{materials} } + ], + ); + $self->{sizer}->Add($optgroup->sizer, 0, wxEXPAND | wxALL, 10); + + $self->SetSizer($self->{sizer}); + $self->{sizer}->SetSizeHints($self); + + return $self; +} + +sub Closing { + my $self = shift; + + # save mappings into the plater object + $self->{object}->material_mapping($self->{mapping}); +} + 1; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 0c5d03fa..5418a099 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -285,6 +285,20 @@ sub print_info { $_->print_info for @{$self->objects}; } +sub get_material_name { + my $self = shift; + my ($material_id) = @_; + + my $name; + if (exists $self->materials->{$material_id}) { + $name //= $self->materials->{$material_id}->attributes->{$_} for qw(Name name); + } elsif ($material_id eq '_') { + $name = 'Default material'; + } + $name //= $material_id; + return $name; +} + package Slic3r::Model::Region; use Moo; @@ -306,6 +320,7 @@ 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 => extruder_idx } has 'mesh_stats' => (is => 'rw'); has '_bounding_box' => (is => 'rw'); @@ -430,6 +445,14 @@ sub materials_count { return scalar keys %materials; } +sub unique_materials { + my $self = shift; + + my %materials = (); + $materials{ $_->material_id // '_' } = 1 for @{$self->volumes}; + return sort keys %materials; +} + sub facets_count { my $self = shift; return sum(map $_->facets_count, @{$self->volumes}); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 7552101a..39082372 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -91,18 +91,6 @@ sub add_model { my $self = shift; my ($model) = @_; - # append/merge materials and preserve a mapping between the original material ID - # and our numeric material index - my %materials = (); - { - my @material_ids = sort keys %{$model->materials}; - @material_ids = (0) if !@material_ids; - for (my $i = $self->regions_count; $i < @material_ids; $i++) { - push @{$self->regions}, Slic3r::Print::Region->new; - $materials{$material_ids[$i]} = $#{$self->regions}; - } - } - # optimization: if avoid_crossing_perimeters is enabled, split # this mesh into distinct objects so that we reduce the complexity # of the graphs @@ -112,6 +100,7 @@ sub add_model { # -- thing before the split. ###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; + my %unmapped_materials = (); foreach my $object (@{ $model->objects }) { # we align object to origin before applying transformations my @align = $object->align_to_origin; @@ -119,13 +108,26 @@ sub add_model { # extract meshes by material my @meshes = (); # by region_id foreach my $volume (@{$object->volumes}) { - my $region_id = defined $volume->material_id ? $materials{$volume->material_id} : 0; + 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); + } + } + $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) { $mesh->check_manifoldness; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 9b0d4850..3803382d 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -143,7 +143,7 @@ sub slice { # process facets for my $region_id (0 .. $#{$self->meshes}) { - my $mesh = $self->meshes->[$region_id]; # ignore undef meshes + my $mesh = $self->meshes->[$region_id] // next; # ignore undef meshes my %lines = (); # layer_id => [ lines ] my $apply_lines = sub {