From 907de1011fba78b8273fae40acb5a4ae064fdc46 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 3 Jul 2014 09:24:19 +0200 Subject: [PATCH] Toolpaths preview --- lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/MainFrame.pm | 4 + lib/Slic3r/GUI/Plater.pm | 13 + lib/Slic3r/GUI/Plater/2DToolpaths.pm | 412 +++++++++++++++++++++++++++ lib/Slic3r/GUI/PreviewCanvas.pm | 5 +- lib/Slic3r/Model.pm | 10 + lib/Slic3r/Print.pm | 4 +- lib/Slic3r/Print/Simple.pm | 2 +- utils/view-toolpaths.pl | 85 ++++++ 9 files changed, 532 insertions(+), 4 deletions(-) create mode 100644 lib/Slic3r/GUI/Plater/2DToolpaths.pm create mode 100755 utils/view-toolpaths.pl diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 65ba7a36a..bee7e54f5 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -12,6 +12,7 @@ use Slic3r::GUI::MainFrame; use Slic3r::GUI::Notifier; use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::2D; +use Slic3r::GUI::Plater::2DToolpaths; use Slic3r::GUI::Plater::ObjectPartsPanel; use Slic3r::GUI::Plater::ObjectCutDialog; use Slic3r::GUI::Plater::ObjectPreviewDialog; diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 103b6c160..09b43f149 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -190,6 +190,10 @@ sub _init_menubar { $self->_append_menu_item($self->{plater_menu}, "Export AMF...", 'Export current plate as AMF', sub { $plater->export_amf; }); + $self->{plater_menu}->AppendSeparator(); + $self->_append_menu_item($self->{plater_menu}, "Toolpaths preview…", 'Open a viewer with toolpaths preview', sub { + $plater->toolpaths_preview; + }); $self->{object_menu} = $self->{plater}->object_menu; $self->on_plater_selection_changed(0); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4124d329a..f02c37c88 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1286,6 +1286,19 @@ sub object_settings_dialog { } } +sub toolpaths_preview { + my ($self) = @_; + + # TODO: we should check whether steps are done in $print rather then checking the thread + if ($self->{process_thread}) { + Slic3r::GUI::show_error($self, "Unable to show preview while toolpaths are being generated."); + return; + } + + my $dlg = Slic3r::GUI::Plater::2DToolpaths::Dialog->new($self, $self->{print}); + $dlg->ShowModal; +} + sub object_list_changed { my $self = shift; diff --git a/lib/Slic3r/GUI/Plater/2DToolpaths.pm b/lib/Slic3r/GUI/Plater/2DToolpaths.pm new file mode 100644 index 000000000..487683a34 --- /dev/null +++ b/lib/Slic3r/GUI/Plater/2DToolpaths.pm @@ -0,0 +1,412 @@ +package Slic3r::GUI::Plater::2DToolpaths; +use strict; +use warnings; +use utf8; + +use List::Util qw(); +use Slic3r::Geometry qw(); +use Wx qw(:misc :sizer :slider); +use Wx::Event qw(EVT_SLIDER); +use base 'Wx::Panel'; + +sub new { + my $class = shift; + my ($parent, $print) = @_; + + my $self = $class->SUPER::new($parent, -1, wxDefaultPosition); + $self->{print} = $print; + my $sizer = Wx::BoxSizer->new(wxHORIZONTAL); + + my $canvas = $self->{canvas} = Slic3r::GUI::Plater::2DToolpaths::Canvas->new($self, $print); + $sizer->Add($canvas, 1, wxALL | wxEXPAND, 10); + + my $slider = $self->{slider} = Wx::Slider->new( + $self, -1, + 0, # default + 0, # min + $print->total_layer_count-1, # max + wxDefaultPosition, + wxDefaultSize, + wxVERTICAL | wxSL_INVERSE, + ); + $sizer->Add($slider, 0, wxALL | wxEXPAND, 10); + + EVT_SLIDER($self, $slider, sub { + $canvas->set_layer($slider->GetValue); + }); + + $self->SetSizer($sizer); + $self->SetMinSize($self->GetSize); + $sizer->SetSizeHints($self); + + $canvas->set_layer(0); + + return $self; +} + + +package Slic3r::GUI::Plater::2DToolpaths::Canvas; + +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); +use OpenGL qw(:glconstants :glfunctions :glufunctions); +use base qw(Wx::GLCanvas Class::Accessor); +use Wx::GLCanvas qw(:all); +use List::Util qw(min); +use Slic3r::Geometry qw(scale unscale); + +__PACKAGE__->mk_accessors(qw(print layer_id init dirty bb)); + +# make OpenGL::Array thread-safe +{ + no warnings 'redefine'; + *OpenGL::Array::CLONE_SKIP = sub { 1 }; +} + +sub new { + my ($class, $parent, $print) = @_; + + my $self = $class->SUPER::new($parent); + $self->print($print); + $self->bb($self->print->bounding_box); + + EVT_PAINT($self, sub { + my $dc = Wx::PaintDC->new($self); + $self->Render($dc); + }); + EVT_SIZE($self, sub { $self->dirty(1) }); + EVT_IDLE($self, sub { + return unless $self->dirty; + return if !$self->IsShownOnScreen; + $self->Resize( $self->GetSizeWH ); + $self->Refresh; + }); + + return $self; +} + +sub set_layer { + my ($self, $layer_id) = @_; + + $self->layer_id($layer_id); + $self->dirty(1); +} + +sub Render { + my ($self, $dc) = @_; + + # prevent calling SetCurrent() when window is not shown yet + return unless $self->IsShownOnScreen; + return unless my $context = $self->GetContext; + $self->SetCurrent($context); + $self->InitGL; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + my $bb = $self->bb; + my ($x1, $y1, $x2, $y2) = ($bb->x_min, $bb->y_min, $bb->x_max, $bb->y_max); + my ($x, $y) = $self->GetSizeWH; + if (($x2 - $x1)/($y2 - $y1) > $x/$y) { + # adjust Y + my $new_y = $y * ($x2 - $x1) / $x; + $y1 = ($y2 + $y1)/2 - $new_y/2; + $y2 = $y1 + $new_y; + } else { + my $new_x = $x * ($y2 - $y1) / $y; + $x1 = ($x2 + $x1)/2 - $new_x/2; + $x2 = $x1 + $new_x; + } + glOrtho($x1, $x2, $y1, $y2, 0, 1); + glDisable(GL_DEPTH_TEST); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glClearColor(1, 1, 1, 0); + glClear(GL_COLOR_BUFFER_BIT); + + foreach my $object (@{$self->print->objects}) { + my $layer = $object->get_layer($self->layer_id); + foreach my $layerm (@{$layer->regions}) { + glColor3f(0.7, 0, 0); + $self->_draw_extrusionpath($object, $_) for @{$layerm->perimeters}; + + glColor3f(0, 0, 0.7); + $self->_draw_extrusionpath($object, $_) for map @$_, @{$layerm->fills}; + } + } + + glFlush(); + $self->SwapBuffers; +} + +sub _draw_extrusionpath { + my ($self, $object, $path) = @_; + + my $polyline = $path->isa('Slic3r::ExtrusionLoop') + ? $path->polygon->split_at_first_point + : $path->polyline; + + glLineWidth(1); + foreach my $copy (@{ $object->_shifted_copies }) { + foreach my $line (@{$polyline->lines}) { + $line->translate(@$copy); + glBegin(GL_LINES); + glVertex2f(@{$line->a}); + glVertex2f(@{$line->b}); + glEnd(); + } + } +} + +sub InitGL { + my $self = shift; + + return if $self->init; + return unless $self->GetContext; + $self->init(1); + + +} + +sub GetContext { + my ($self) = @_; + + if (Wx::wxVERSION >= 2.009) { + return $self->{context} ||= Wx::GLContext->new($self); + } else { + return $self->SUPER::GetContext; + } +} + +sub SetCurrent { + my ($self, $context) = @_; + + if (Wx::wxVERSION >= 2.009) { + return $self->SUPER::SetCurrent($context); + } else { + return $self->SUPER::SetCurrent; + } +} + +sub Resize { + my ($self, $x, $y) = @_; + + return unless $self->GetContext; + $self->dirty(0); + + $self->SetCurrent($self->GetContext); + + my ($x1, $y1, $x2, $y2) = (0, 0, $x, $y); + if (0 && $x > $y) { + $x2 = $y; + $x1 = ($x - $y)/2; + } + if (0 && $y > $x) { + $y2 = $x; + $y1 = ($y - $x)/2; + } + glViewport($x1, $y1, $x2, $y2); +} + +sub line { + my ( + $x1, $y1, $x2, $y2, # coordinates of the line + $w, # width/thickness of the line in pixel + $Cr, $Cg, $Cb, # RGB color components + $Br, $Bg, $Bb, # color of background when alphablend=false + # Br=alpha of color when alphablend=true + $alphablend, # use alpha blend or not + ) = @_; + + my $t; + my $R; + my $f = $w - int($w); + my $A; + + if ($alphablend) { + $A = $Br; + } else { + $A = 1; + } + + # determine parameters t,R + if ($w >= 0 && $w < 1) { + $t = 0.05; $R = 0.48 + 0.32 * $f; + if (!$alphablend) { + $Cr += 0.88 * (1-$f); + $Cg += 0.88 * (1-$f); + $Cb += 0.88 * (1-$f); + $Cr = 1.0 if ($Cr > 1.0); + $Cg = 1.0 if ($Cg > 1.0); + $Cb = 1.0 if ($Cb > 1.0); + } else { + $A *= $f; + } + } elsif ($w >= 1.0 && $w < 2.0) { + $t = 0.05 + $f*0.33; $R = 0.768 + 0.312*$f; + } elsif ($w >= 2.0 && $w < 3.0) { + $t = 0.38 + $f*0.58; $R = 1.08; + } elsif ($w >= 3.0 && $w < 4.0) { + $t = 0.96 + $f*0.48; $R = 1.08; + } elsif ($w >= 4.0 && $w < 5.0) { + $t= 1.44 + $f*0.46; $R = 1.08; + } elsif ($w >= 5.0 && $w < 6.0) { + $t= 1.9 + $f*0.6; $R = 1.08; + } elsif ($w >= 6.0) { + my $ff = $w - 6.0; + $t = 2.5 + $ff*0.50; $R = 1.08; + } + #printf( "w=%f, f=%f, C=%.4f\n", $w, $f, $C); + + # determine angle of the line to horizontal + my $tx = 0; my $ty = 0; # core thinkness of a line + my $Rx = 0; my $Ry = 0; # fading edge of a line + my $cx = 0; my $cy = 0; # cap of a line + my $ALW = 0.01; + my $dx = $x2 - $x1; + my $dy = $y2 - $y1; + if (abs($dx) < $ALW) { + # vertical + $tx = $t; $ty = 0; + $Rx = $R; $Ry = 0; + if ($w > 0.0 && $w < 1.0) { + $tx *= 8; + } elsif ($w == 1.0) { + $tx *= 10; + } + } elsif (abs($dy) < $ALW) { + #horizontal + $tx = 0; $ty = $t; + $Rx = 0; $Ry = $R; + if ($w > 0.0 && $w < 1.0) { + $ty *= 8; + } elsif ($w == 1.0) { + $ty *= 10; + } + } else { + if ($w < 3) { # approximate to make things even faster + my $m = $dy/$dx; + # and calculate tx,ty,Rx,Ry + if ($m > -0.4142 && $m <= 0.4142) { + # -22.5 < $angle <= 22.5, approximate to 0 (degree) + $tx = $t * 0.1; $ty = $t; + $Rx = $R * 0.6; $Ry = $R; + } elsif ($m > 0.4142 && $m <= 2.4142) { + # 22.5 < $angle <= 67.5, approximate to 45 (degree) + $tx = $t * -0.7071; $ty = $t * 0.7071; + $Rx = $R * -0.7071; $Ry = $R * 0.7071; + } elsif ($m > 2.4142 || $m <= -2.4142) { + # 67.5 < $angle <= 112.5, approximate to 90 (degree) + $tx = $t; $ty = $t*0.1; + $Rx = $R; $Ry = $R*0.6; + } elsif ($m > -2.4142 && $m < -0.4142) { + # 112.5 < angle < 157.5, approximate to 135 (degree) + $tx = $t * 0.7071; $ty = $t * 0.7071; + $Rx = $R * 0.7071; $Ry = $R * 0.7071; + } else { + # error in determining angle + printf("error in determining angle: m=%.4f\n", $m); + } + } else { # calculate to exact + $dx= $y1 - $y2; + $dy= $x2 - $x1; + my $L = sqrt($dx*$dx + $dy*$dy); + $dx /= $L; + $dy /= $L; + $cx = -0.6*$dy; $cy=0.6*$dx; + $tx = $t*$dx; $ty = $t*$dy; + $Rx = $R*$dx; $Ry = $R*$dy; + } + } + + # draw the line by triangle strip + glBegin(GL_TRIANGLE_STRIP); + if (!$alphablend) { + glColor3f($Br, $Bg, $Bb); + } else { + glColor4f($Cr, $Cg, $Cb, 0); + } + glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry); # fading edge + glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry); + + if (!$alphablend) { + glColor3f($Cr, $Cg, $Cb); + } else { + glColor4f($Cr, $Cg, $Cb, $A); + } + glVertex2f($x1 - $tx, $y1 - $ty); # core + glVertex2f($x2 - $tx, $y2 - $ty); + glVertex2f($x1 + $tx, $y1 + $ty); + glVertex2f($x2 + $tx, $y2 + $ty); + + if ((abs($dx) < $ALW || abs($dy) < $ALW) && $w <= 1.0) { + # printf("skipped one fading edge\n"); + } else { + if (!$alphablend) { + glColor3f($Br, $Bg, $Bb); + } else { + glColor4f($Cr, $Cg, $Cb, 0); + } + glVertex2f($x1 + $tx+ $Rx, $y1 + $ty + $Ry); # fading edge + glVertex2f($x2 + $tx+ $Rx, $y2 + $ty + $Ry); + } + glEnd(); + + # cap + if ($w < 3) { + # do not draw cap + } else { + # draw cap + glBegin(GL_TRIANGLE_STRIP); + if (!$alphablend) { + glColor3f($Br, $Bg, $Bb); + } else { + glColor4f($Cr, $Cg, $Cb, 0); + } + glVertex2f($x1 - $Rx + $cx, $y1 - $Ry + $cy); + glVertex2f($x1 + $Rx + $cx, $y1 + $Ry + $cy); + glColor3f($Cr, $Cg, $Cb); + glVertex2f($x1 - $tx - $Rx, $y1 - $ty - $Ry); + glVertex2f($x1 + $tx + $Rx, $y1 + $ty + $Ry); + glEnd(); + glBegin(GL_TRIANGLE_STRIP); + if (!$alphablend) { + glColor3f($Br, $Bg, $Bb); + } else { + glColor4f($Cr, $Cg, $Cb, 0); + } + glVertex2f($x2 - $Rx - $cx, $y2 - $Ry - $cy); + glVertex2f($x2 + $Rx - $cx, $y2 + $Ry - $cy); + glColor3f($Cr, $Cg, $Cb); + glVertex2f($x2 - $tx - $Rx, $y2 - $ty - $Ry); + glVertex2f($x2 + $tx + $Rx, $y2 + $ty + $Ry); + glEnd(); + } +} + + +package Slic3r::GUI::Plater::2DToolpaths::Dialog; + +use Wx qw(:dialog :id :misc :sizer); +use Wx::Event qw(EVT_CLOSE); +use base 'Wx::Dialog'; + +sub new { + my $class = shift; + my ($parent, $print) = @_; + my $self = $class->SUPER::new($parent, -1, "Toolpaths", wxDefaultPosition, [500,500], wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($self, $print), 1, wxEXPAND, 0); + $self->SetSizer($sizer); + $self->SetMinSize($self->GetSize); + + # needed to actually free memory + EVT_CLOSE($self, sub { + $self->EndModal(wxID_OK); + $self->Destroy; + }); + + return $self; +} + +1; diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 193a044a6..3f76486d8 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -24,7 +24,10 @@ use constant SELECTED_COLOR => [0,1,0,1]; use constant COLORS => [ [1,1,1], [1,0.5,0.5], [0.5,1,0.5], [0.5,0.5,1] ]; # make OpenGL::Array thread-safe -*OpenGL::Array::CLONE_SKIP = sub { 1 }; +{ + no warnings 'redefine'; + *OpenGL::Array::CLONE_SKIP = sub { 1 }; +} sub new { my ($class, $parent, $object) = @_; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 71ca015ec..04b5b6f52 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -390,6 +390,16 @@ sub raw_mesh { return $mesh; } +sub raw_bounding_box { + my $self = shift; + + my @meshes = map $_->mesh, grep !$_->modifier, @{ $self->volumes }; + die "No meshes found" if !@meshes; + my $bb = (shift @meshes)->bounding_box; + $bb->merge($_->bounding_box) for @meshes; + return $bb; +} + # flattens all volumes and instances into a single mesh sub mesh { my $self = shift; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c3b326ada..a6b73691e 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -167,9 +167,9 @@ sub add_model_object { # initialize print object and store it at the given position my $o; if (defined $obj_idx) { - $o = $self->set_new_object($obj_idx, $object, $object->bounding_box); + $o = $self->set_new_object($obj_idx, $object, $object->raw_bounding_box); } else { - $o = $self->add_object($object, $object->bounding_box); + $o = $self->add_object($object, $object->raw_bounding_box); } $o->set_copies([ map Slic3r::Point->new_scale(@{ $_->offset }), @{ $object->instances } ]); diff --git a/lib/Slic3r/Print/Simple.pm b/lib/Slic3r/Print/Simple.pm index bdb89e79e..a6ed9ba7d 100644 --- a/lib/Slic3r/Print/Simple.pm +++ b/lib/Slic3r/Print/Simple.pm @@ -8,7 +8,7 @@ has '_print' => ( default => sub { Slic3r::Print->new }, handles => [qw(apply_config extruders expanded_output_filepath total_used_filament total_extruded_volume - placeholder_parser)], + placeholder_parser process)], ); has 'duplicate' => ( diff --git a/utils/view-toolpaths.pl b/utils/view-toolpaths.pl new file mode 100755 index 000000000..4b03bbbea --- /dev/null +++ b/utils/view-toolpaths.pl @@ -0,0 +1,85 @@ +#!/usr/bin/perl +# This script displays 3D preview of a mesh + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Getopt::Long qw(:config no_auto_abbrev); +use Slic3r; +use Slic3r::GUI; +use Slic3r::GUI::PreviewCanvas; +$|++; + +my %opt = (); +{ + my %options = ( + 'help' => sub { usage() }, + 'load=s' => \$opt{load}, + ); + GetOptions(%options) or usage(1); + $ARGV[0] or usage(1); +} + +{ + # load model + my $model = Slic3r::Model->read_from_file($ARGV[0]); + + # load config + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 0); + if ($opt{load}) { + $config->apply(Slic3r::Config->load($opt{load})); + } + + # init print + my $sprint = Slic3r::Print::Simple->new; + $sprint->apply_config($config); + $sprint->set_model($model); + $sprint->process; + + # visualize toolpaths + $Slic3r::ViewToolpaths::print = $sprint->_print; + my $app = Slic3r::ViewToolpaths->new; + $app->MainLoop; +} + + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: view-toolpaths.pl [ OPTIONS ] file.stl + + --help Output this usage screen and exit + --load CONFIG Loads the supplied config file + +EOF + exit ($exit_code || 0); +} + + +package Slic3r::ViewToolpaths; +use Wx qw(:sizer); +use base qw(Wx::App); + +our $print; + +sub OnInit { + my $self = shift; + + my $frame = Wx::Frame->new(undef, -1, 'Toolpaths', [-1, -1], [500, 500]); + my $panel = Wx::Panel->new($frame, -1); + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add(Slic3r::GUI::Plater::2DToolpaths->new($panel, $print), 1, wxEXPAND, 0); + $panel->SetSizer($sizer); + + $frame->Show(1); +} + +__END__