From 769ec0cb030b0705cd50003ec12d02ef7ac03c1e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 26 Mar 2013 13:04:57 +0100 Subject: [PATCH 01/78] Better clipping of honeycomb paths --- lib/Slic3r/Fill/Honeycomb.pm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 9e4ff8a27c..a2386d6038 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -78,13 +78,14 @@ sub fill_surface { $self->cache->{$cache_id} = [@polygons]; } - # build polylines from polygons without re-appending the initial point: + # consider polygons as polylines without re-appending the initial point: # this cuts the last segment on purpose, so that the jump to the next # path is more straight - my @paths = map Slic3r::Polyline->new(@$_), map @$_, @{intersection_ex( - $self->cache->{$cache_id}, - $expolygon, - )}; + my @paths = map Slic3r::Polyline->new($_), + @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( + $expolygon, + $self->cache->{$cache_id}, + ) }; return { flow_spacing => $params{flow_spacing} }, Slic3r::Polyline::Collection->new(polylines => \@paths)->chained_path; From 55c413627f8545fb5d2b75efb157d30689985d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 23 Apr 2013 16:17:43 +0300 Subject: [PATCH 02/78] Bugfix: configuration wizard crash 2 #1077 --- lib/Slic3r/GUI/SkeinPanel.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 1bcaa4d90e..1a822a054c 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -234,7 +234,9 @@ sub load_config { my ($config) = @_; foreach my $tab (values %{$self->{options_tabs}}) { - $tab->set_value($_, $config->$_) for keys %$config; + if ($self->{mode} eq 'expert') { + $tab->set_value($_, $config->$_) for keys %$config; + } } } From fc1a23f5b0447c9d43e7060b49e278fad81ee7f4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 12:01:38 +0200 Subject: [PATCH 03/78] Quick and dirty OpenGL mockup --- MANIFEST | 1 + lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater/ObjectDialog.pm | 19 +++ lib/Slic3r/GUI/PreviewCanvas.pm | 182 ++++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 lib/Slic3r/GUI/PreviewCanvas.pm diff --git a/MANIFEST b/MANIFEST index bc338a5cea..24253e6bf5 100644 --- a/MANIFEST +++ b/MANIFEST @@ -37,6 +37,7 @@ lib/Slic3r/GUI/OptionsGroup.pm lib/Slic3r/GUI/Plater.pm lib/Slic3r/GUI/Plater/ObjectDialog.pm lib/Slic3r/GUI/Preferences.pm +lib/Slic3r/GUI/PreviewCanvas.pm lib/Slic3r/GUI/SkeinPanel.pm lib/Slic3r/GUI/SimpleTab.pm lib/Slic3r/GUI/Tab.pm diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 1c60f85732..399b978b0c 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -10,6 +10,7 @@ use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::ObjectDialog; use Slic3r::GUI::Preferences; use Slic3r::GUI::OptionsGroup; +use Slic3r::GUI::PreviewCanvas; use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 6543d50da1..c1723632b4 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -14,6 +14,7 @@ sub new { $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"); $self->{tabpanel}->AddPage($self->{info} = Slic3r::GUI::Plater::ObjectDialog::InfoTab->new($self->{tabpanel}, object => $self->{object}), "Info"); $self->{tabpanel}->AddPage($self->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers"); @@ -83,6 +84,24 @@ sub get_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::Cube->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); + $self->SetSizer($sizer); + $sizer->SetSizeHints($self); + + return $self; +} + package Slic3r::GUI::Plater::ObjectDialog::LayersTab; use Wx qw(:dialog :id :misc :sizer :systemsettings); use Wx::Grid; diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm new file mode 100644 index 0000000000..122df54b8b --- /dev/null +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -0,0 +1,182 @@ +package Slic3r::GUI::PreviewCanvas; + +use strict; + +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER); +# must load OpenGL *before* Wx::GLCanvas +use OpenGL qw(:glconstants :glfunctions); +use base qw(Wx::GLCanvas Class::Accessor::Fast); +use Wx::GLCanvas qw(:all); + +__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh) ); + +sub new { + my( $class, $parent, $mesh ) = @_; + my $self = $class->SUPER::new($parent); + $self->mesh($mesh); + + my $timer = $self->timer( Wx::Timer->new( $self ) ); + $timer->Start( 50 ); + + $self->x_rot( 0 ); + $self->y_rot( 0 ); + + 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; + $self->Resize( $self->GetSizeWH ); + $self->Refresh; + } ); + EVT_TIMER( $self, -1, sub { + my( $self, $e ) = @_; + + $self->x_rot( $self->x_rot - 1 ); + $self->y_rot( $self->y_rot + 2 ); + + $self->dirty( 1 ); + Wx::WakeUpIdle; + } ); + + return $self; +} + +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 ); + glViewport( 0, 0, $x, $y ); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + my_gluPerspective( 45, $x/$y, .5, 100 ); + + glMatrixMode(GL_MODELVIEW); +} + +use Math::Trig; + +sub my_gluPerspective { + my( $fov, $ratio, $near, $far ) = @_; + + my $top = tan(deg2rad($fov)*0.5) * $near; + my $bottom = -$top; + my $left = $ratio * $bottom; + my $right = $ratio * $top; + + glFrustum( $left, $right, $bottom, $top, $near, $far ); +} + +sub DESTROY { + my( $self ) = @_; + + $self->timer->Stop; + $self->timer( undef ); +} + +package Slic3r::GUI::PreviewCanvas::Cube; + +# must load OpenGL *before* Wx::GLCanvas +use OpenGL qw(:glconstants :glfunctions); +use base qw(Slic3r::GUI::PreviewCanvas); +use Slic3r::Geometry qw(X Y Z MIN MAX); + +sub cube { + my( @v ) = ( [ 1, 1, 1 ], [ -1, 1, 1 ], + [ -1, -1, 1 ], [ 1, -1, 1 ], + [ 1, 1, -1 ], [ -1, 1, -1 ], + [ -1, -1, -1 ], [ 1, -1, -1 ] ); + my( @c ) = ( [ 1, 1, 0 ], [ 1, 0, 1 ], + [ 0, 1, 1 ], [ 1, 1, 1 ], + [ 0, 0, 1 ], [ 0, 1, 0 ], + [ 1, 0, 1 ], [ 1, 1, 0 ] ); + my( @s ) = ( [ 0, 1, 2, 3 ], [ 4, 5, 6, 7 ], + [ 0, 1, 5, 4 ], [ 2, 3, 7, 6 ], + [ 1, 2, 6, 5 ], [ 0, 3, 7, 4 ] ); + + for my $i ( 0 .. 5 ) { + my $s = $s[$i]; + glBegin(GL_QUADS); + foreach my $j ( @$s ) { + glColor3f( @{$c[$j]} ); + glVertex3f( @{$v[$j]} ); + } + glEnd(); + } +} + +sub draw_mesh { + my $self = shift; + + my $mesh = $self->mesh; + $mesh->align_to_origin; + glBegin(GL_TRIANGLES); + for my $facet (@{$mesh->facets}) { + glVertex3f( map 0.1 * $_, @{ $mesh->vertices->[$_] } ) for @$facet; + } + glEnd(); +} + +sub InitGL { + my $self = shift; + + return if $self->init; + return unless $self->GetContext; + $self->init( 1 ); + + glDisable( GL_LIGHTING ); + glDepthFunc( GL_LESS ); + glEnable( GL_DEPTH_TEST ); +} + +sub Render { + my( $self, $dc ) = @_; + + return unless $self->GetContext; + $self->SetCurrent( $self->GetContext ); + $self->InitGL; + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix(); + glTranslatef( 0, 0, -5 ); + glRotatef( $self->x_rot, 1, 0, 0 ); + glRotatef( $self->y_rot, 0, 0, 1 ); + + #cube(); + $self->draw_mesh; + + glPopMatrix(); + glFlush(); + + $self->SwapBuffers(); +} + +1; \ No newline at end of file From 228c84ddc1bbac6c544a40138723a2d089700356 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 16 May 2013 13:42:19 +0200 Subject: [PATCH 04/78] Use glDrawArrays() --- lib/Slic3r/GUI/PreviewCanvas.pm | 55 +++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 122df54b8b..2c776e8598 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -136,12 +136,32 @@ sub draw_mesh { my $self = shift; my $mesh = $self->mesh; - $mesh->align_to_origin; - glBegin(GL_TRIANGLES); - for my $facet (@{$mesh->facets}) { - glVertex3f( map 0.1 * $_, @{ $mesh->vertices->[$_] } ) for @$facet; - } - glEnd(); + + #glEnable(GL_CULL_FACE); + glEnableClientState(GL_VERTEX_ARRAY); + #glEnableClientState(GL_NORMAL_ARRAY); + + my @verts = map 0.1 * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; + my $verts = OpenGL::Array->new_list(GL_FLOAT, @verts); + + #my @norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; + #my $norms = OpenGL::Array->new_list(GL_FLOAT, @norms); + + #my @inv_norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(reverse map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; + #my $inv_norms = OpenGL::Array->new_list(GL_FLOAT, @inv_norms); + + glVertexPointer_p(3, $verts); + + #glCullFace(GL_BACK); + #glNormalPointer_p($norms); + glDrawArrays(GL_TRIANGLES, 0, scalar @verts); + + #glCullFace(GL_FRONT); + #glNormalPointer_p($inv_norms); + #glDrawArrays(GL_TRIANGLES, 0, scalar @verts); + + #glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); } sub InitGL { @@ -151,9 +171,32 @@ sub InitGL { return unless $self->GetContext; $self->init( 1 ); + $self->mesh->align_to_origin; + glDisable( GL_LIGHTING ); glDepthFunc( GL_LESS ); glEnable( GL_DEPTH_TEST ); + + if (0) { + # Settings for our light. + my @Light_Ambient = ( 0.1, 0.1, 0.1, 1.0 ); + my @Light_Diffuse = ( 1.2, 1.2, 1.2, 1.0 ); + my @Light_Position = ( 2.0, 2.0, 0.0, 1.0 ); + + + # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + glShadeModel(GL_SMOOTH); + + # Set up a light, turn it on. + glLightfv_p(GL_LIGHT1, GL_POSITION, @Light_Position); + glLightfv_p(GL_LIGHT1, GL_AMBIENT, @Light_Ambient); + glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @Light_Diffuse); + glEnable(GL_LIGHT1); + + # A handy trick -- have surface material mirror the color. + glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); + } } sub Render { From 5c74fd095b93151fc3956d158bbbdfd3ccb2dd96 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 14:14:33 +0200 Subject: [PATCH 05/78] Very basic implementation of 3D preview - install Wx::GLCanvas to get it working --- MANIFEST | 1 + lib/Slic3r/GUI.pm | 3 +- lib/Slic3r/GUI/Plater/ObjectDialog.pm | 8 +- lib/Slic3r/GUI/PreviewCanvas.pm | 263 ++++++++++++-------------- lib/Slic3r/Geometry.pm | 15 +- lib/Slic3r/TriangleMesh.pm | 13 ++ utils/view-mesh.pl | 67 +++++++ 7 files changed, 221 insertions(+), 149 deletions(-) create mode 100644 utils/view-mesh.pl diff --git a/MANIFEST b/MANIFEST index 24253e6bf5..22766599f7 100644 --- a/MANIFEST +++ b/MANIFEST @@ -89,6 +89,7 @@ utils/post-processing/decimate.pl utils/post-processing/flowrate.pl utils/split_stl.pl utils/stl-to-amf.pl +utils/view-mesh.pl utils/zsh/functions/_slic3r utils/zsh/README.markdown var/add.png diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 399b978b0c..09aa80a98b 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -10,11 +10,12 @@ use Slic3r::GUI::Plater; use Slic3r::GUI::Plater::ObjectDialog; use Slic3r::GUI::Preferences; use Slic3r::GUI::OptionsGroup; -use Slic3r::GUI::PreviewCanvas; use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; 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 base 'Wx::App'; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index c1723632b4..3cb725e649 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -10,11 +10,12 @@ use base 'Wx::Dialog'; sub new { my $class = shift; my ($parent, %params) = @_; - my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,350]); + my $self = $class->SUPER::new($parent, -1, "Object", wxDefaultPosition, [500,350], 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"); + $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->{layers} = Slic3r::GUI::Plater::ObjectDialog::LayersTab->new($self->{tabpanel}, object => $self->{object}), "Layers"); @@ -34,6 +35,7 @@ sub new { $sizer->Add($buttons, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); $self->SetSizer($sizer); + $self->SetMinSize($self->GetSize); return $self; } @@ -95,7 +97,7 @@ sub new { $self->{object} = $params{object}; my $sizer = Wx::BoxSizer->new(wxVERTICAL); - $sizer->Add(Slic3r::GUI::PreviewCanvas::Cube->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($self, $self->{object}->get_model_object->mesh), 1, wxEXPAND, 0); $self->SetSizer($sizer); $sizer->SetSizeHints($self); diff --git a/lib/Slic3r/GUI/PreviewCanvas.pm b/lib/Slic3r/GUI/PreviewCanvas.pm index 2c776e8598..f70a0bba66 100644 --- a/lib/Slic3r/GUI/PreviewCanvas.pm +++ b/lib/Slic3r/GUI/PreviewCanvas.pm @@ -1,90 +1,111 @@ package Slic3r::GUI::PreviewCanvas; - use strict; +use warnings; -use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER); +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_TIMER EVT_MOUSEWHEEL); # must load OpenGL *before* Wx::GLCanvas use OpenGL qw(:glconstants :glfunctions); -use base qw(Wx::GLCanvas Class::Accessor::Fast); +use base qw(Wx::GLCanvas Class::Accessor); +use Slic3r::Geometry qw(X Y Z MIN MAX triangle_normal normalize deg2rad tan); use Wx::GLCanvas qw(:all); -__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh) ); +__PACKAGE__->mk_accessors( qw(timer x_rot y_rot dirty init mesh_center zoom + verts norms) ); sub new { - my( $class, $parent, $mesh ) = @_; + my ($class, $parent, $mesh) = @_; my $self = $class->SUPER::new($parent); - $self->mesh($mesh); - my $timer = $self->timer( Wx::Timer->new( $self ) ); - $timer->Start( 50 ); - - $self->x_rot( 0 ); - $self->y_rot( 0 ); - - 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; - $self->Resize( $self->GetSizeWH ); - $self->Refresh; - } ); - EVT_TIMER( $self, -1, sub { - my( $self, $e ) = @_; - - $self->x_rot( $self->x_rot - 1 ); - $self->y_rot( $self->y_rot + 2 ); - - $self->dirty( 1 ); - Wx::WakeUpIdle; - } ); - + # prepare mesh + { + $self->mesh_center($mesh->center); + $self->zoom(0.1); + + my @verts = map $self->zoom * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; + $self->verts(OpenGL::Array->new_list(GL_FLOAT, @verts)); + + my @norms = map { @$_, @$_, @$_ } map normalize(triangle_normal(map $mesh->vertices->[$_], @$_)), @{$mesh->facets}; + $self->norms(OpenGL::Array->new_list(GL_FLOAT, @norms)); + } + + my $timer = $self->timer( Wx::Timer->new($self) ); + $timer->Start(50); + + $self->x_rot(0); + $self->y_rot(0); + + 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; + }); + EVT_TIMER($self, -1, sub { + my ($self, $e) = @_; + + $self->x_rot( $self->x_rot - 1 ); + $self->y_rot( $self->y_rot + 2 ); + + $self->dirty(1); + Wx::WakeUpIdle; + }); + EVT_MOUSEWHEEL($self, sub { + my ($self, $e) = @_; + + my $zoom = $self->zoom * (1.0 - $e->GetWheelRotation() / $e->GetWheelDelta() / 10); + $zoom = 0.001 if $zoom < 0.001; + $zoom = 0.1 if $zoom > 0.1; + $self->zoom($zoom); + + $self->Refresh; + }); + return $self; } sub GetContext { - my( $self ) = @_; - - if( Wx::wxVERSION >= 2.009 ) { - return $self->{context} ||= Wx::GLContext->new( $self ); + 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 ); + my ($self, $context) = @_; + + if (Wx::wxVERSION >= 2.009) { + return $self->SUPER::SetCurrent($context); } else { return $self->SUPER::SetCurrent; } } sub Resize { - my( $self, $x, $y ) = @_; + my ($self, $x, $y) = @_; return unless $self->GetContext; - $self->dirty( 0 ); + $self->dirty(0); - $self->SetCurrent( $self->GetContext ); - glViewport( 0, 0, $x, $y ); + $self->SetCurrent($self->GetContext); + glViewport(0, 0, $x, $y); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - my_gluPerspective( 45, $x/$y, .5, 100 ); + my_gluPerspective(45, $x/$y, .5, 100); glMatrixMode(GL_MODELVIEW); } -use Math::Trig; - sub my_gluPerspective { - my( $fov, $ratio, $near, $far ) = @_; + my ($fov, $ratio, $near, $far) = @_; my $top = tan(deg2rad($fov)*0.5) * $near; my $bottom = -$top; @@ -95,73 +116,10 @@ sub my_gluPerspective { } sub DESTROY { - my( $self ) = @_; + my $self = shift; $self->timer->Stop; - $self->timer( undef ); -} - -package Slic3r::GUI::PreviewCanvas::Cube; - -# must load OpenGL *before* Wx::GLCanvas -use OpenGL qw(:glconstants :glfunctions); -use base qw(Slic3r::GUI::PreviewCanvas); -use Slic3r::Geometry qw(X Y Z MIN MAX); - -sub cube { - my( @v ) = ( [ 1, 1, 1 ], [ -1, 1, 1 ], - [ -1, -1, 1 ], [ 1, -1, 1 ], - [ 1, 1, -1 ], [ -1, 1, -1 ], - [ -1, -1, -1 ], [ 1, -1, -1 ] ); - my( @c ) = ( [ 1, 1, 0 ], [ 1, 0, 1 ], - [ 0, 1, 1 ], [ 1, 1, 1 ], - [ 0, 0, 1 ], [ 0, 1, 0 ], - [ 1, 0, 1 ], [ 1, 1, 0 ] ); - my( @s ) = ( [ 0, 1, 2, 3 ], [ 4, 5, 6, 7 ], - [ 0, 1, 5, 4 ], [ 2, 3, 7, 6 ], - [ 1, 2, 6, 5 ], [ 0, 3, 7, 4 ] ); - - for my $i ( 0 .. 5 ) { - my $s = $s[$i]; - glBegin(GL_QUADS); - foreach my $j ( @$s ) { - glColor3f( @{$c[$j]} ); - glVertex3f( @{$v[$j]} ); - } - glEnd(); - } -} - -sub draw_mesh { - my $self = shift; - - my $mesh = $self->mesh; - - #glEnable(GL_CULL_FACE); - glEnableClientState(GL_VERTEX_ARRAY); - #glEnableClientState(GL_NORMAL_ARRAY); - - my @verts = map 0.1 * $_, map @{ $mesh->vertices->[$_] }, map @$_, @{$mesh->facets}; - my $verts = OpenGL::Array->new_list(GL_FLOAT, @verts); - - #my @norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; - #my $norms = OpenGL::Array->new_list(GL_FLOAT, @norms); - - #my @inv_norms = map @$_, map {my $f = $_; Slic3r::Geometry::triangle_normal(reverse map $mesh->vertices->[$_], @$f) } @{$mesh->facets}; - #my $inv_norms = OpenGL::Array->new_list(GL_FLOAT, @inv_norms); - - glVertexPointer_p(3, $verts); - - #glCullFace(GL_BACK); - #glNormalPointer_p($norms); - glDrawArrays(GL_TRIANGLES, 0, scalar @verts); - - #glCullFace(GL_FRONT); - #glNormalPointer_p($inv_norms); - #glDrawArrays(GL_TRIANGLES, 0, scalar @verts); - - #glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); + $self->timer(undef); } sub InitGL { @@ -169,51 +127,51 @@ sub InitGL { return if $self->init; return unless $self->GetContext; - $self->init( 1 ); - - $self->mesh->align_to_origin; - - glDisable( GL_LIGHTING ); - glDepthFunc( GL_LESS ); - glEnable( GL_DEPTH_TEST ); + $self->init(1); - if (0) { - # Settings for our light. - my @Light_Ambient = ( 0.1, 0.1, 0.1, 1.0 ); - my @Light_Diffuse = ( 1.2, 1.2, 1.2, 1.0 ); - my @Light_Position = ( 2.0, 2.0, 0.0, 1.0 ); + glEnable(GL_NORMALIZE); + glEnable(GL_LIGHTING); + glDepthFunc(GL_LESS); + glEnable(GL_DEPTH_TEST); - - # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. - glShadeModel(GL_SMOOTH); - - # Set up a light, turn it on. - glLightfv_p(GL_LIGHT1, GL_POSITION, @Light_Position); - glLightfv_p(GL_LIGHT1, GL_AMBIENT, @Light_Ambient); - glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @Light_Diffuse); - glEnable(GL_LIGHT1); - - # A handy trick -- have surface material mirror the color. - glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE); - glEnable(GL_COLOR_MATERIAL); - } + # Settings for our light. + my @LightPos = (0, 0, 2, 1.0); + my @LightAmbient = (0.1, 0.1, 0.1, 1.0); + my @LightDiffuse = (0.7, 0.5, 0.5, 1.0); + my @LightSpecular = (0.1, 0.1, 0.1, 0.1); + + # Enables Smooth Color Shading; try GL_FLAT for (lack of) fun. + glShadeModel(GL_SMOOTH); + + # Set up a light, turn it on. + glLightfv_p(GL_LIGHT1, GL_POSITION, @LightPos); + glLightfv_p(GL_LIGHT1, GL_AMBIENT, @LightAmbient); + glLightfv_p(GL_LIGHT1, GL_DIFFUSE, @LightDiffuse); + glLightfv_p(GL_LIGHT1, GL_SPECULAR, @LightSpecular); + glEnable(GL_LIGHT1); + + # A handy trick -- have surface material mirror the color. + glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); + glEnable(GL_COLOR_MATERIAL); } sub Render { - my( $self, $dc ) = @_; + my ($self, $dc) = @_; return unless $self->GetContext; - $self->SetCurrent( $self->GetContext ); + $self->SetCurrent($self->GetContext); $self->InitGL; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef( 0, 0, -5 ); + + # this needs to get a lot better... glRotatef( $self->x_rot, 1, 0, 0 ); glRotatef( $self->y_rot, 0, 0, 1 ); + glTranslatef(map -$_ * $self->zoom, @{ $self->mesh_center }); - #cube(); $self->draw_mesh; glPopMatrix(); @@ -222,4 +180,21 @@ sub Render { $self->SwapBuffers(); } -1; \ No newline at end of file +sub draw_mesh { + my $self = shift; + + glEnable(GL_CULL_FACE); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_NORMAL_ARRAY); + + glVertexPointer_p(3, $self->verts); + + glCullFace(GL_BACK); + glNormalPointer_p($self->norms); + glDrawArrays(GL_TRIANGLES, 0, $self->verts->elements / 3); + + glDisableClientState(GL_NORMAL_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); +} + +1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 4c1e23b9fe..6eaf7fac02 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_points - chained_path_items chained_path_points + chained_path_items chained_path_points normalize tan line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point point_along_segment polygon_segment_having_point polygon_has_subsegment @@ -45,6 +45,11 @@ sub scaled_epsilon () { epsilon / &Slic3r::SCALING_FACTOR } sub scale ($) { $_[0] / &Slic3r::SCALING_FACTOR } sub unscale ($) { $_[0] * &Slic3r::SCALING_FACTOR } +sub tan { + my ($angle) = @_; + return (sin $angle) / (cos $angle); +} + sub slope { my ($line) = @_; return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical @@ -461,6 +466,14 @@ sub triangle_normal { return normal($u, $v); } +sub normalize { + my ($line) = @_; + + my $len = sqrt( ($line->[X]**2) + ($line->[Y]**2) + ($line->[Z]**2) ) + or return [0, 0, 0]; # to avoid illegal division by zero + return [ map $_ / $len, @$line ]; +} + # 2D dot product sub dot { my ($u, $v) = @_; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index f093a3fe21..9ffe542fd1 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -365,6 +365,19 @@ sub align_to_origin { $self->move(map -$extents[$_][MIN], X,Y,Z); } +sub center_around_origin { + my $self = shift; + + $self->move(map -$_, @{ $self->center }); +} + +sub center { + my $self = shift; + + my @extents = $self->extents; + return [ map +($extents[$_][MAX] + $extents[$_][MIN])/2, X,Y,Z ]; +} + sub duplicate { my $self = shift; my (@shifts) = @_; diff --git a/utils/view-mesh.pl b/utils/view-mesh.pl new file mode 100644 index 0000000000..5d69525d02 --- /dev/null +++ b/utils/view-mesh.pl @@ -0,0 +1,67 @@ +#!/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; +$|++; + +my %opt = (); +{ + my %options = ( + 'help' => sub { usage() }, + ); + GetOptions(%options) or usage(1); + $ARGV[0] or usage(1); +} + +{ + my $model = Slic3r::Model->read_from_file($ARGV[0]); + + $Slic3r::ViewMesh::mesh = $model->mesh; + my $app = Slic3r::ViewMesh->new; + $app->MainLoop; +} + + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: view-mesh.pl [ OPTIONS ] file.stl + + --help Output this usage screen and exit + +EOF + exit ($exit_code || 0); +} + +package Slic3r::ViewMesh; +use Wx qw(:sizer); +use base qw(Wx::App); + +our $mesh; + +sub OnInit { + my $self = shift; + + my $frame = Wx::Frame->new(undef, -1, 'Mesh Viewer', [-1, -1], [500, 400]); + my $panel = Wx::Panel->new($frame, -1); + + my $sizer = Wx::BoxSizer->new(wxVERTICAL); + $sizer->Add(Slic3r::GUI::PreviewCanvas->new($panel, $mesh), 1, wxEXPAND, 0); + $panel->SetSizer($sizer); + $sizer->SetSizeHints($panel); + + $frame->Show(1); +} + +__END__ From 8f77d3b9456c68e026ddc7680b18d8a4da43f4c1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 15:03:42 +0200 Subject: [PATCH 06/78] Increase spacing for bridge traces. #1090 --- lib/Slic3r.pm | 1 - lib/Slic3r/Flow.pm | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1dfaf8859e..9214022f8b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -68,7 +68,6 @@ use constant SCALING_FACTOR => 0.000001; use constant RESOLUTION => 0.0125; use constant SCALED_RESOLUTION => RESOLUTION / SCALING_FACTOR; use constant OVERLAP_FACTOR => 1; -use constant BRIDGE_OVERLAP_FACTOR => 0.2; use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.45; diff --git a/lib/Slic3r/Flow.pm b/lib/Slic3r/Flow.pm index 50d7cb7f94..d618856410 100644 --- a/lib/Slic3r/Flow.pm +++ b/lib/Slic3r/Flow.pm @@ -96,8 +96,7 @@ sub _build_width { sub _build_spacing { my $self = shift; - my $width = $self->width; - return $width - (&Slic3r::BRIDGE_OVERLAP_FACTOR * $width); + return $self->width + 0.05; } 1; From 4bfbaddb59ced40d809f0a738cc13188c778a1aa Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 15:07:01 +0200 Subject: [PATCH 07/78] Update t/arcs.t and add one more test --- t/arcs.t | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/t/arcs.t b/t/arcs.t index 972620402b..3b14903c84 100644 --- a/t/arcs.t +++ b/t/arcs.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 12; +plan tests => 13; BEGIN { use FindBin; @@ -20,7 +20,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); [306517.1,219034.23], [286979.42,248012.49], [258001.16,267550.17], [222515.14,274714.47], [187029.11,267550.17], [158050.85,248012.49], [138513.17,219034.23], [131348.87,183548.2], [86948.77,175149.09], [119825.35,100585], - ), role => EXTR_ROLE_FILL); + ), role => EXTR_ROLE_FILL, flow_spacing => 0.5); my $collection = Slic3r::ExtrusionPath::Collection->new(paths => [$path]); $collection->detect_arcs(30); @@ -42,10 +42,12 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); my $path1 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(@points), role => EXTR_ROLE_FILL, + flow_spacing => 0.5, ); my $path2 = Slic3r::ExtrusionPath->new( polyline => Slic3r::Polyline->new(reverse @points), role => EXTR_ROLE_FILL, + flow_spacing => 0.5, ); my $collection1 = Slic3r::ExtrusionPath::Collection->new(paths => [$path1]); @@ -66,6 +68,7 @@ use Slic3r::Geometry qw(scaled_epsilon scale X Y); is $collection1->paths->[0]->orientation, 'cw', 'cw orientation was correctly detected'; is $collection2->paths->[0]->orientation, 'ccw', 'ccw orientation was correctly detected'; + is $collection1->paths->[0]->flow_spacing, $path1->flow_spacing, 'flow spacing was correctly preserved'; my $center1 = [ map sprintf('%.0f', $_), @{ $collection1->paths->[0]->center } ]; ok abs($center1->[X] - scale 10) < scaled_epsilon && abs($center1->[Y] - scale 10) < scaled_epsilon, 'center was correctly detected'; From ba433822b673a99bedec619c91dbf5ab74070c4f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 15:44:23 +0200 Subject: [PATCH 08/78] Disable OpenGL code in master branch for now --- lib/Slic3r/GUI.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 09aa80a98b..0f9057ed7c 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -14,7 +14,7 @@ use Slic3r::GUI::SkeinPanel; use Slic3r::GUI::SimpleTab; use Slic3r::GUI::Tab; -our $have_OpenGL = eval "use Slic3r::GUI::PreviewCanvas; 1"; +our $have_OpenGL = 0 && 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); From e33ca54943c4579ac950f8e06561ac544e20f3b7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 17 May 2013 20:03:38 +0200 Subject: [PATCH 09/78] Some code cleanup in MotionPlanner --- lib/Slic3r/GCode/MotionPlanner.pm | 58 ++++++++++++++++--------------- lib/Slic3r/Polygon.pm | 6 ++++ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 0cde4e1af6..6eff6c3bc1 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -10,6 +10,7 @@ has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of array has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point } has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... } has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool +has '_tolerance' => (is => 'lazy'); use List::Util qw(first); use Slic3r::Geometry qw(A B scale epsilon nearest_point); @@ -30,31 +31,14 @@ use constant CROSSING_FACTOR => 20; use constant INFINITY => 'inf'; +sub _build__tolerance { scale epsilon } + # setup our configuration space sub BUILD { my $self = shift; my $edges = $self->_edges; my $crossing_edges = $self->_crossing_edges; - my $tolerance = scale epsilon; - - # given an expolygon, this subroutine connects all its visible points - my $add_expolygon = sub { - my ($expolygon, $crosses_perimeter) = @_; - my @points = map @$_, @$expolygon; - for my $i (0 .. $#points) { - for my $j (($i+1) .. $#points) { - my $line = Slic3r::Line->new($points[$i], $points[$j]); - if ($expolygon->encloses_line($line, $tolerance)) { - my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1); - $edges->{$points[$i]}{$points[$j]} = $dist; - $edges->{$points[$j]}{$points[$i]} = $dist; - $crossing_edges->{$points[$i]}{$points[$j]} = 1; - $crossing_edges->{$points[$j]}{$points[$i]} = 1; - } - } - } - }; # simplify islands @{$self->islands} = map $_->simplify($self->_inner_margin), @{$self->islands}; @@ -81,19 +65,14 @@ sub BUILD { ); # lines enclosed in inner expolygons are visible - $add_expolygon->($_) for @{ $self->_inner->[$i] }; + $self->_add_expolygon($_) for @{ $self->_inner->[$i] }; # lines enclosed in expolygons covering perimeters are visible # (but discouraged) - $add_expolygon->($_, 1) for @{ $self->_contours_ex->[$i] }; + $self->_add_expolygon($_, 1) for @{ $self->_contours_ex->[$i] }; } } - my $intersects = sub { - my ($polygon, $line) = @_; - @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$polygon], [$line])} > 0; - }; - { my @outer = (map @$_, @{$self->_outer}); @@ -112,7 +91,7 @@ sub BUILD { for my $m (0 .. $#{$outer[$i]}) { for my $n (0 .. $#{$outer[$j]}) { my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]); - if (!first { $intersects->($_, $line) } @outer) { + if (!first { $_->intersects_line($line) } @outer) { # this line does not cross any polygon my $dist = $line->length; $edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist; @@ -132,7 +111,7 @@ sub BUILD { for my $m (0 .. $#{$inner[$i]}) { for my $n (0 .. $#{$inner[$j]}) { my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]); - if (!first { $intersects->($_, $line) } @inner) { + if (!first { $_->intersects_line($line) } @inner) { # this line does not cross any polygon my $dist = $line->length * CROSSING_FACTOR; $edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist; @@ -183,6 +162,29 @@ sub BUILD { } } +# given an expolygon, this subroutine connects all its visible points +sub _add_expolygon { + my $self = shift; + my ($expolygon, $crosses_perimeter) = @_; + + my $edges = $self->_edges; + my $crossing_edges = $self->_crossing_edges; + + my @points = map @$_, @$expolygon; + for my $i (0 .. $#points) { + for my $j (($i+1) .. $#points) { + my $line = Slic3r::Line->new($points[$i], $points[$j]); + if ($expolygon->encloses_line($line, $self->_tolerance)) { + my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1); + $edges->{$points[$i]}{$points[$j]} = $dist; + $edges->{$points[$j]}{$points[$i]} = $dist; + $crossing_edges->{$points[$i]}{$points[$j]} = 1; + $crossing_edges->{$points[$j]}{$points[$i]} = 1; + } + } + } +} + sub find_node { my $self = shift; my ($point, $near_to) = @_; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index 6e460f40b9..f8df6915a6 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -172,4 +172,10 @@ sub split_at_first_point { return $self->split_at_index(0); } +sub intersects_line { + my $self = shift; + my ($line) = @_; + return @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$self], [$line])} > 0; +} + 1; \ No newline at end of file From 08a0bbd7f09ef9f2f48572ba75e878567ad90690 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 18 May 2013 16:48:26 +0200 Subject: [PATCH 10/78] Optimization: split meshes automatically when avoid_crossing_perimeters is enabled, so that we reduce the complexity of the MotionPlanner graphs. This commit includes a very large refactoring of the Model class which is now responsible for duplication and arrangement --- lib/Slic3r/Fill/Base.pm | 1 + lib/Slic3r/Fill/Rectilinear.pm | 12 +- lib/Slic3r/GUI/Plater.pm | 1 + lib/Slic3r/Geometry.pm | 11 +- lib/Slic3r/Model.pm | 229 ++++++++++++++++++++++++++++++++- lib/Slic3r/Print.pm | 90 +++---------- lib/Slic3r/Print/Object.pm | 5 +- lib/Slic3r/SVG.pm | 2 +- lib/Slic3r/Test.pm | 4 +- lib/Slic3r/TriangleMesh.pm | 14 +- slic3r.pl | 16 ++- t/slice.t | 24 ++-- 12 files changed, 300 insertions(+), 109 deletions(-) diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index adc1e74c6f..7633082325 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -36,6 +36,7 @@ sub infill_direction { return [\@rotate, \@shift]; } +# this method accepts any object that implements rotate() and translate() sub rotate_points { my $self = shift; my ($expolygon, $rotate_vector) = @_; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index a65bcbc077..4ad3ff78fa 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -25,17 +25,17 @@ sub fill_surface { my $line_oscillation = $distance_between_lines - $min_spacing; my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); - my $cache_id = sprintf "d%s_s%s_a%s", + my $cache_id = sprintf "d%s_s%.2f_a%.2f", $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; if (!$self->cache->{$cache_id}) { # compute bounding box - my $bounding_box = [ @{$self->bounding_box} ]; # clone - $bounding_box->[$_] = 0 for X1, Y1; + my $bounding_box; { - my $bb_expolygon = Slic3r::ExPolygon->new(Slic3r::Polygon->new_from_bounding_box($bounding_box)); - $self->rotate_points($bb_expolygon, $rotate_vector); - $bounding_box = [ $bb_expolygon->bounding_box ]; + my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($self->bounding_box); + $bb_polygon->scale(sqrt 2); + $self->rotate_points($bb_polygon, $rotate_vector); + $bounding_box = [ $bb_polygon->bounding_box ]; } # define flow spacing according to requested density diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ae66ba8e71..4f63271d69 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -715,6 +715,7 @@ sub make_model { rotation => $plater_object->rotate, offset => [ @$_ ], ) for @{$plater_object->instances}; + $new_model_object->align_to_origin; } return $model; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 6eaf7fac02..e0848aa86b 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw( PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel line_point_belongs_to_segment points_coincide distance_between_points - chained_path_items chained_path_points normalize tan + chained_path_items chained_path_points normalize tan move_points_3D line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point point_along_segment polygon_segment_having_point polygon_has_subsegment @@ -388,6 +388,15 @@ sub move_points { return map Slic3r::Point->new($shift->[X] + $_->[X], $shift->[Y] + $_->[Y]), @points; } +sub move_points_3D { + my ($shift, @points) = @_; + return map [ + $shift->[X] + $_->[X], + $shift->[Y] + $_->[Y], + $shift->[Z] + $_->[Z], + ], @points; +} + # implementation of Liang-Barsky algorithm # polygon must be convex and ccw sub clip_segment_polygon { diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 58473fa9fb..cb740ab741 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -1,7 +1,8 @@ package Slic3r::Model; use Moo; -use Slic3r::Geometry qw(X Y Z); +use List::Util qw(first max); +use Slic3r::Geometry qw(X Y Z MIN move_points); has 'materials' => (is => 'ro', default => sub { {} }); has 'objects' => (is => 'ro', default => sub { [] }); @@ -19,6 +20,38 @@ sub read_from_file { return $model; } +sub merge { + my $class = shift; + my @models = @_; + + my $new_model = $class->new; + foreach my $model (@models) { + # merge material attributes (should we rename them in case of duplicates?) + $new_model->set_material($_, { %{$model->materials->{$_}}, %{$model->materials->{$_} || {}} }) + for keys %{$model->materials}; + + foreach my $object (@{$model->objects}) { + my $new_object = $new_model->add_object( + input_file => $object->input_file, + vertices => $object->vertices, + layer_height_ranges => $object->layer_height_ranges, + ); + + $new_object->add_volume( + material_id => $_->material_id, + facets => $_->facets, + ) for @{$object->volumes}; + + $new_object->add_instance( + offset => $_->offset, + rotation => $_->rotation, + ) for @{ $object->instances // [] }; + } + } + + return $new_model; +} + sub add_object { my $self = shift; @@ -39,10 +72,122 @@ sub set_material { sub scale { my $self = shift; - $_->scale(@_) for @{$self->objects}; } +sub arrange_objects { + my $self = shift; + my ($config) = @_; + + # do we have objects with no position? + if (first { !defined $_->instances } @{$self->objects}) { + # we shall redefine positions for all objects + + my ($copies, @positions) = $self->_arrange( + config => $config, + items => $self->objects, + ); + + # apply positions to objects + foreach my $object (@{$self->objects}) { + $object->align_to_origin; + + $object->instances([]); + $object->add_instance( + offset => $_, + rotation => 0, + ) for splice @positions, 0, $copies; + } + + } else { + # we only have objects with defined position + + # align the whole model to origin as it is + $self->align_to_origin; + + # arrange this model as a whole + my ($copies, @positions) = $self->_arrange( + config => $config, + items => [$self], + ); + + # apply positions to objects by translating the current positions + foreach my $object (@{$self->objects}) { + my @old_instances = @{$object->instances}; + $object->instances([]); + foreach my $instance (@old_instances) { + $object->add_instance( + offset => $_, + rotation => $instance->rotation, + ) for move_points($instance->offset, @positions); + } + } + } +} + +sub _arrange { + my $self = shift; + my %params = @_; + + my $config = $params{config}; + my @items = @{$params{items}}; # can be Model or Object objects, they have to implement size() + + if ($config->duplicate_grid->[X] > 1 || $config->duplicate_grid->[Y] > 1) { + if (@items > 1) { + die "Grid duplication is not supported with multiple objects\n"; + } + my @positions = (); + my $size = $items[0]->size; + my $dist = $config->duplicate_distance; + for my $x_copy (1..$config->duplicate_grid->[X]) { + for my $y_copy (1..$config->duplicate_grid->[Y]) { + push @positions, [ + ($size->[X] + $dist) * ($x_copy-1), + ($size->[Y] + $dist) * ($y_copy-1), + ]; + } + } + return ($config->duplicate_grid->[X] * $config->duplicate_grid->[Y]), @positions; + } else { + my $total_parts = $config->duplicate * @items; + my $partx = max(map $_->size->[X], @items); + my $party = max(map $_->size->[Y], @items); + return $config->duplicate, + Slic3r::Geometry::arrange + ($total_parts, $partx, $party, (map $_, @{$config->bed_size}), + $config->min_object_distance, $config); + } +} + +sub vertices { + my $self = shift; + return [ map @{$_->vertices}, @{$self->objects} ]; +} + +sub size { + my $self = shift; + return [ Slic3r::Geometry::size_3D($self->vertices) ]; +} + +sub extents { + my $self = shift; + return Slic3r::Geometry::bounding_box_3D($self->vertices); +} + +sub align_to_origin { + my $self = shift; + + # calculate the displacements needed to + # have lowest value for each axis at coordinate 0 + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); +} + +sub move { + my $self = shift; + $_->move(@_) for @{$self->objects}; +} + # flattens everything to a single mesh sub mesh { my $self = shift; @@ -64,6 +209,47 @@ sub mesh { return Slic3r::TriangleMesh->merge(@meshes); } +# this method splits objects into multiple distinct objects by walking their meshes +sub split_meshes { + my $self = shift; + + my @objects = @{$self->objects}; + @{$self->objects} = (); + + foreach my $object (@objects) { + if (@{$object->volumes} > 1) { + # We can't split meshes if there's more than one material, because + # we can't group the resulting meshes by object afterwards + push @{$self->objects}, $object; + next; + } + + my $volume = $object->volumes->[0]; + foreach my $mesh ($volume->mesh->split_mesh) { + my $new_object = $self->add_object( + input_file => $object->input_file, + layer_height_ranges => $object->layer_height_ranges, + ); + $new_object->add_volume( + vertices => $mesh->vertices, + facets => $mesh->facets, + material_id => $volume->material_id, + ); + + # let's now align the new object to the origin and put its displacement + # (extents) in the instances info + my @extents = $mesh->extents; + $new_object->align_to_origin; + + # add one instance per original instance applying the displacement + $new_object->add_instance( + offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ], + rotation => $_->rotation, + ) for @{ $object->instances // [] }; + } + } +} + package Slic3r::Model::Region; use Moo; @@ -74,7 +260,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z); +use Slic3r::Geometry qw(X Y Z MIN move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -123,6 +309,30 @@ sub mesh { ); } +sub size { + my $self = shift; + return [ Slic3r::Geometry::size_3D($self->vertices) ]; +} + +sub extents { + my $self = shift; + return Slic3r::Geometry::bounding_box_3D($self->vertices); +} + +sub align_to_origin { + my $self = shift; + + # calculate the displacements needed to + # have lowest value for each axis at coordinate 0 + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); +} + +sub move { + my $self = shift; + @{$self->vertices} = move_points_3D([ @_ ], @{$self->vertices}); +} + sub scale { my $self = shift; my ($factor) = @_; @@ -134,6 +344,19 @@ sub scale { } } +sub rotate { + my $self = shift; + my ($deg) = @_; + return if $deg == 0; + + my $rad = Slic3r::Geometry::deg2rad($deg); + + # transform vertex coordinates + foreach my $vertex (@{$self->vertices}) { + @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); + } +} + sub materials_count { my $self = shift; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index b5482f7a07..7a380fa54f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -87,6 +87,8 @@ sub _build_fill_maker { return Slic3r::Fill->new(print => $self); } +# caller is responsible for supplying models whose objects don't collide +# and have explicit instance positions sub add_model { my $self = shift; my ($model) = @_; @@ -103,13 +105,18 @@ sub add_model { } } + # optimization: if avoid_crossing_perimeters is enabled, split + # this mesh into distinct objects so that we reduce the complexity + # of the graphs + $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; + foreach my $object (@{ $model->objects }) { + # extract meshes by material my @meshes = (); # by region_id - foreach my $volume (@{$object->volumes}) { - # should the object contain multiple volumes of the same material, merge them my $region_id = defined $volume->material_id ? $materials{$volume->material_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; @@ -119,41 +126,22 @@ sub add_model { next unless $mesh; $mesh->check_manifoldness; - if ($object->instances) { - # we ignore the per-instance rotation currently and only - # consider the first one - $mesh->rotate($object->instances->[0]->rotation); - } + # we ignore the per-instance rotation currently and only + # consider the first one + $mesh->rotate($object->instances->[0]->rotation); - $mesh->rotate($Slic3r::Config->rotate); - $mesh->scale($Slic3r::Config->scale / &Slic3r::SCALING_FACTOR); + $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - my @defined_meshes = grep defined $_, @meshes; - my $complete_mesh = @defined_meshes == 1 ? $defined_meshes[0] : Slic3r::TriangleMesh->merge(@defined_meshes); - # initialize print object - my $print_object = Slic3r::Print::Object->new( + push @{$self->objects}, Slic3r::Print::Object->new( print => $self, meshes => [ @meshes ], - size => [ $complete_mesh->size ], + copies => [ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ], + size => [ map scale $_, @{ $object->size } ], input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); - push @{$self->objects}, $print_object; - - # align object to origin - { - my @extents = $complete_mesh->extents; - foreach my $mesh (grep defined $_, @meshes) { - $mesh->move(map -$extents[$_][MIN], X,Y,Z); - } - } - - if ($object->instances) { - # replace the default [0,0] instance with the custom ones - $print_object->copies([ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ]); - } } } @@ -282,54 +270,12 @@ sub regions_count { return scalar @{$self->regions}; } -sub duplicate { - my $self = shift; - - if ($Slic3r::Config->duplicate_grid->[X] > 1 || $Slic3r::Config->duplicate_grid->[Y] > 1) { - if (@{$self->objects} > 1) { - die "Grid duplication is not supported with multiple objects\n"; - } - my $object = $self->objects->[0]; - - # generate offsets for copies - my $dist = scale $Slic3r::Config->duplicate_distance; - @{$self->objects->[0]->copies} = (); - for my $x_copy (1..$Slic3r::Config->duplicate_grid->[X]) { - for my $y_copy (1..$Slic3r::Config->duplicate_grid->[Y]) { - push @{$self->objects->[0]->copies}, [ - ($object->size->[X] + $dist) * ($x_copy-1), - ($object->size->[Y] + $dist) * ($y_copy-1), - ]; - } - } - } elsif ($Slic3r::Config->duplicate > 1) { - foreach my $object (@{$self->objects}) { - @{$object->copies} = map [0,0], 1..$Slic3r::Config->duplicate; - } - $self->arrange_objects; - } -} - -sub arrange_objects { - my $self = shift; - - my $total_parts = scalar map @{$_->copies}, @{$self->objects}; - my $partx = max(map $_->size->[X], @{$self->objects}); - my $party = max(map $_->size->[Y], @{$self->objects}); - - my @positions = Slic3r::Geometry::arrange - ($total_parts, $partx, $party, (map scale $_, @{$Slic3r::Config->bed_size}), scale $Slic3r::Config->min_object_distance, $self->config); - - @{$_->copies} = splice @positions, 0, scalar @{$_->copies} for @{$self->objects}; -} - sub bounding_box { my $self = shift; my @points = (); - foreach my $obj_idx (0 .. $#{$self->objects}) { - my $object = $self->objects->[$obj_idx]; - foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { + foreach my $object (@{$self->objects}) { + foreach my $copy (@{$object->copies}) { push @points, [ $copy->[X], $copy->[Y] ], [ $copy->[X] + $object->size->[X], $copy->[Y] ], diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 7b21db8745..39913d592e 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,7 +11,7 @@ has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'input_file' => (is => 'rw', required => 0); has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id has 'size' => (is => 'rw', required => 1); -has 'copies' => (is => 'rw', default => sub {[ [0,0] ]}, trigger => 1); +has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -76,6 +76,7 @@ sub BUILD { } } +# This should be probably moved in Print.pm at the point where we sort Layer objects sub _trigger_copies { my $self = shift; return unless @{$self->copies} > 1; @@ -166,6 +167,8 @@ sub slice { } }, ); + + $self->meshes->[$region_id] = undef; # free memory } die "Invalid input file\n" if !@{$self->layers}; diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index a79094854f..0ac2863951 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -9,7 +9,7 @@ use constant Y => 1; our $filltype = 'evenodd'; -sub factor {return 30; +sub factor { return &Slic3r::SCALING_FACTOR * 10; } diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index d6a10f374a..63ee7ac647 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -30,7 +30,9 @@ sub model { } my $model = Slic3r::Model->new; - $model->add_object(vertices => $vertices)->add_volume(facets => $facets); + my $object = $model->add_object(vertices => $vertices); + $object->add_volume(facets => $facets); + $object->add_instance(offset => [0,0]); return $model; } diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 9ffe542fd1..0968f7354d 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -10,9 +10,9 @@ has 'vertices' => (is => 'ro', required => 1); # id => [$x,$y,$z] has 'facets' => (is => 'ro', required => 1); # id => [ $v1_id, $v2_id, $v3_id ] # private -has 'edges' => (is => 'ro', default => sub { [] }); # id => [ $v1_id, $v2_id ] -has 'facets_edges' => (is => 'ro', default => sub { [] }); # id => [ $e1_id, $e2_id, $e3_id ] -has 'edges_facets' => (is => 'ro', default => sub { [] }); # id => [ $f1_id, $f2_id, (...) ] +has 'edges' => (is => 'rw'); # id => [ $v1_id, $v2_id ] +has 'facets_edges' => (is => 'rw'); # id => [ $e1_id, $e2_id, $e3_id ] +has 'edges_facets' => (is => 'rw'); # id => [ $f1_id, $f2_id, (...) ] use constant MIN => 0; use constant MAX => 1; @@ -29,13 +29,13 @@ use constant I_FACET_EDGE => 6; use constant FE_TOP => 0; use constant FE_BOTTOM => 1; -# always make sure this method is idempotent sub analyze { my $self = shift; - @{$self->edges} = (); - @{$self->facets_edges} = (); - @{$self->edges_facets} = (); + return if defined $self->edges; + $self->edges([]); + $self->facets_edges([]); + $self->edges_facets([]); my %table = (); # edge_coordinates => edge_id for (my $facet_id = 0; $facet_id <= $#{$self->facets}; $facet_id++) { diff --git a/slic3r.pl b/slic3r.pl index b3dec4969c..1d09441db3 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -91,13 +91,19 @@ if (@ARGV) { # slicing from command line $config->validate; while (my $input_file = shift @ARGV) { - my $print = Slic3r::Print->new(config => $config); - $print->add_model(Slic3r::Model->read_from_file($input_file)); + my $model; if ($opt{merge}) { - $print->add_model(Slic3r::Model->read_from_file($_)) for splice @ARGV, 0; + my @models = map Slic3r::Model->read_from_file($_), $input_file, (splice @ARGV, 0); + $model = Slic3r::Model->merge(@models); + } else { + $model = Slic3r::Model->read_from_file($input_file); } - $print->duplicate; - $print->arrange_objects if @{$print->objects} > 1; + $_->scale($config->scale) for @{$model->objects}; + $_->rotate($config->rotate) for @{$model->objects}; + $model->arrange_objects($config); + + my $print = Slic3r::Print->new(config => $config); + $print->add_model($model); $print->validate; my %params = ( output_file => $opt{output}, diff --git a/t/slice.t b/t/slice.t index cfa78a60c9..e47929f0fc 100644 --- a/t/slice.t +++ b/t/slice.t @@ -16,8 +16,6 @@ my @lines; my $z = 20; my @points = ([3, 4], [8, 5], [1, 9]); # XY coordinates of the facet vertices -my $mesh = Slic3r::TriangleMesh->new(facets => [], vertices => []); - # NOTE: # the first point of the intersection lines is replaced by -1 because TriangleMesh.pm # is saving memory and doesn't store point A anymore since it's not actually needed. @@ -104,21 +102,23 @@ my @upper = intersect(20, 20, 10); is $lower[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_BOTTOM, 'bottom edge on layer'; is $upper[0][Slic3r::TriangleMesh::I_FACET_EDGE], Slic3r::TriangleMesh::FE_TOP, 'upper edge on layer'; +my $mesh; + +sub intersect { + $mesh = Slic3r::TriangleMesh->new( + facets => [], + vertices => [], + ); + push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; + $mesh->analyze; + return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); +} + sub vertices { push @{$mesh->vertices}, map [ @{$points[$_]}, $_[$_] ], 0..2; [ ($#{$mesh->vertices}-2) .. $#{$mesh->vertices} ] } -sub add_facet { - push @{$mesh->facets}, [ [0,0,0], @{vertices(@_)} ]; - $mesh->analyze; -} - -sub intersect { - add_facet(@_); - return map Slic3r::TriangleMesh::unpack_line($_), $mesh->intersect_facet($#{$mesh->facets}, $z); -} - sub lines { my @lines = intersect(@_); #$_->a->[X] = sprintf('%.0f', $_->a->[X]) for @lines; From f599ed00c605ccd478323cea984ed39a93370f85 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 18 May 2013 16:57:44 +0200 Subject: [PATCH 11/78] Avoid closures, move planning code to _plan() and layer G-code generation to Slic3r::GCode::Layer --- MANIFEST | 1 + lib/Slic3r.pm | 1 + lib/Slic3r/GCode.pm | 61 ++++++------ lib/Slic3r/GCode/Layer.pm | 202 ++++++++++++++++++++++++++++++++++++++ lib/Slic3r/Print.pm | 191 +++-------------------------------- 5 files changed, 246 insertions(+), 210 deletions(-) create mode 100644 lib/Slic3r/GCode/Layer.pm diff --git a/MANIFEST b/MANIFEST index 22766599f7..a9180a6913 100644 --- a/MANIFEST +++ b/MANIFEST @@ -25,6 +25,7 @@ lib/Slic3r/Format/OBJ.pm lib/Slic3r/Format/STL.pm lib/Slic3r/GCode.pm lib/Slic3r/GCode/CoolingBuffer.pm +lib/Slic3r/GCode/Layer.pm lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/GCode/Reader.pm lib/Slic3r/GCode/SpiralVase.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9214022f8b..1d2849f1d4 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -46,6 +46,7 @@ use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode; use Slic3r::GCode::CoolingBuffer; +use Slic3r::GCode::Layer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 7cd3d0ef28..1180b76ff1 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -294,34 +294,6 @@ sub travel_to { $self->speed('travel'); $gcode .= $self->G0($point, undef, 0, $comment || ""); } else { - my $plan = sub { - my $mp = shift; - - my $gcode = ""; - my @travel = $mp->shortest_path($self->last_pos, $point)->lines; - - # if the path is not contained in a single island we need to retract - my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters; - if (!$need_retract) { - $need_retract = 1; - foreach my $slice (@{$self->layer->slices}) { - # discard the island if at any line is not enclosed in it - next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; - # okay, this island encloses the full travel path - $need_retract = 0; - last; - } - } - - # do the retract (the travel_to argument is broken) - $gcode .= $self->retract(travel_to => $point) if $need_retract; - - # append the actual path and return - $self->speed('travel'); - $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel; - return $gcode; - }; - if ($self->new_object) { $self->new_object(0); @@ -332,16 +304,45 @@ sub travel_to { # calculate path (external_mp uses G-code coordinates so we temporary need a null shift) $self->set_shift(0,0); - $gcode .= $plan->($self->external_mp); + $gcode .= $self->_plan($self->external_mp, $point, $comment); $self->set_shift(@shift); } else { - $gcode .= $plan->($self->layer_mp); + $gcode .= $self->_plan($self->layer_mp, $point, $comment); } } return $gcode; } +sub _plan { + my $self = shift; + my ($mp, $point, $comment) = @_; + + my $gcode = ""; + my @travel = $mp->shortest_path($self->last_pos, $point)->lines; + + # if the path is not contained in a single island we need to retract + my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters; + if (!$need_retract) { + $need_retract = 1; + foreach my $slice (@{$self->layer->slices}) { + # discard the island if at any line is not enclosed in it + next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; + # okay, this island encloses the full travel path + $need_retract = 0; + last; + } + } + + # do the retract (the travel_to argument is broken) + $gcode .= $self->retract(travel_to => $point) if $need_retract; + + # append the actual path and return + $self->speed('travel'); + $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel; + return $gcode; +} + sub retract { my $self = shift; my %params = @_; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm new file mode 100644 index 0000000000..4a6eb366dc --- /dev/null +++ b/lib/Slic3r/GCode/Layer.pm @@ -0,0 +1,202 @@ +package Slic3r::GCode::Layer; +use Moo; + +use Slic3r::Geometry qw(X Y unscale); + +has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]); +has 'gcodegen' => (is => 'ro', required => 1); +has 'shift' => (is => 'ro', required => 1); + +has 'spiralvase' => (is => 'lazy'); +has 'skirt_done' => (is => 'rw', default => sub {0}); # count of skirt layers done +has 'brim_done' => (is => 'rw'); +has 'second_layer_things_done' => (is => 'rw'); +has '_last_obj_copy' => (is => 'rw'); + +sub _build_spiralvase { + my $self = shift; + + return $Slic3r::Config->spiral_vase + ? Slic3r::GCode::SpiralVase->new + : undef; +} + +sub process_layer { + my $self = shift; + my ($layer, $object_copies) = @_; + my $gcode = ""; + + if (!$self->second_layer_things_done && $layer->id == 1) { + for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { + $gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) + if $self->print->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature; + } + $gcode .= $self->gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature) + if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature; + $self->second_layer_things_done(1); + } + + # set new layer, but don't move Z as support material contact areas may need an intermediate one + $gcode .= $self->gcodegen->change_layer($layer); + + # prepare callback to call as soon as a Z command is generated + $self->gcodegen->move_z_callback(sub { + $self->gcodegen->move_z_callback(undef); # circular ref or not? + return "" if !$Slic3r::Config->layer_gcode; + return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n"; + }); + + # extrude skirt + if ($self->skirt_done < $Slic3r::Config->skirt_height) { + $self->gcodegen->set_shift(@{$self->shift}); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder + $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + # skip skirt if we have a large brim + if ($layer->id < $Slic3r::Config->skirt_height) { + # distribute skirt loops across all extruders + for my $i (0 .. $#{$self->print->skirt}) { + # when printing layers > 0 ignore 'min_skirt_length' and + # just use the 'skirts' setting; also just use the current extruder + last if ($layer->id > 0) && ($i >= $Slic3r::Config->skirts); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ]) + if $layer->id == 0; + $gcode .= $self->gcodegen->extrude_loop($self->print->skirt->[$i], 'skirt'); + } + } + $self->skirt_done($self->skirt_done + 1); + $self->gcodegen->straight_once(1); + } + + # extrude brim + if (!$self->brim_done) { + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder + $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + $self->gcodegen->set_shift(@{$self->shift}); + $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; + $self->brim_done(1); + $self->gcodegen->straight_once(1); + } + + for my $copy (@$object_copies) { + $self->gcodegen->new_object(1) if ($self->_last_obj_copy // '') ne "$copy"; + $self->_last_obj_copy("$copy"); + + $self->gcodegen->set_shift(map $self->shift->[$_] + unscale $copy->[$_], X,Y); + + # extrude support material before other things because it might use a lower Z + # and also because we avoid travelling on other things when printing it + if ($self->print->has_support_material) { + $gcode .= $self->gcodegen->move_z($layer->support_material_contact_z) + if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths }); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); + if ($layer->support_contact_fills) { + $gcode .= $self->gcodegen->extrude_path($_, 'support material contact area') + for $layer->support_contact_fills->chained_path($self->gcodegen->last_pos); + } + + $gcode .= $self->gcodegen->move_z($layer->print_z); + if ($layer->support_fills) { + $gcode .= $self->gcodegen->extrude_path($_, 'support material') + for $layer->support_fills->chained_path($self->gcodegen->last_pos); + } + } + + # set actual Z - this will force a retraction + $gcode .= $self->gcodegen->move_z($layer->print_z); + + # tweak region ordering to save toolchanges + my @region_ids = 0 .. ($self->print->regions_count-1); + if ($self->gcodegen->multiple_extruders) { + my $last_extruder = $self->gcodegen->extruder; + my $best_region_id = first { $self->print->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids; + @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; + } + + foreach my $region_id (@region_ids) { + my $layerm = $layer->regions->[$region_id]; + my $region = $self->print->regions->[$region_id]; + + my @islands = (); + if ($Slic3r::Config->avoid_crossing_perimeters) { + push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices}; + PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { + my $p = $perimeter->unpack; + for my $i (0 .. $#{$layer->slices}-1) { + if ($layer->slices->[$i]->contour->encloses_point($p->first_point)) { + push @{ $islands[$i]{perimeters} }, $p; + next PERIMETER; + } + } + push @{ $islands[-1]{perimeters} }, $p; # optimization + } + FILL: foreach my $fill (@{$layerm->fills}) { + my $f = $fill->unpack; + for my $i (0 .. $#{$layer->slices}-1) { + if ($layer->slices->[$i]->contour->encloses_point($f->first_point)) { + push @{ $islands[$i]{fills} }, $f; + next FILL; + } + } + push @{ $islands[-1]{fills} }, $f; # optimization + } + } else { + push @islands, { + perimeters => $layerm->perimeters, + fills => $layerm->fills, + }; + } + + foreach my $island (@islands) { + # give priority to infill if we were already using its extruder and it wouldn't + # be good for perimeters + if ($Slic3r::Config->infill_first + || ($self->gcodegen->multiple_extruders && $region->extruders->{infill} eq $self->gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) { + $gcode .= $self->_extrude_infill($island, $region); + $gcode .= $self->_extrude_perimeters($island, $region); + } else { + $gcode .= $self->_extrude_perimeters($island, $region); + $gcode .= $self->_extrude_infill($island, $region); + } + } + } + } + + # apply spiral vase post-processing if this layer contains suitable geometry + $gcode = $self->spiralvase->process_layer($gcode, $layer) + if defined $self->spiralvase + && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) + && ($layer->id >= $Slic3r::Config->skirt_height) + && ($layer->id >= $Slic3r::Config->bottom_solid_layers); + + return $gcode; +} + +sub _extrude_perimeters { + my $self = shift; + my ($island, $region) = @_; + + return "" if !@{ $island->{perimeters} }; + return $self->gcodegen->set_extruder($region->extruders->{perimeter}) + . $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; +} + +sub _extrude_infill { + my $self = shift; + my ($island, $region) = @_; + + return "" if !@{ $island->{fills} }; + + my $gcode = ""; + $gcode .= $self->gcodegen->set_extruder($region->extruders->{infill}); + for my $fill (@{ $island->{fills} }) { + if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { + $gcode .= $self->gcodegen->extrude($_, 'fill') + for $fill->chained_path($self->gcodegen->last_pos); + } else { + $gcode .= $self->gcodegen->extrude($fill, 'fill') ; + } + } + return $gcode; +} + +1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 7a380fa54f..c5e341cb52 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -759,183 +759,12 @@ sub write_gcode { )); } - # prepare the SpiralVase processor if it's possible - my $spiralvase = $Slic3r::Config->spiral_vase - ? Slic3r::GCode::SpiralVase->new - : undef; - - # prepare the logic to print one layer - my $skirt_done = 0; # count of skirt layers done - my $brim_done = 0; - my $second_layer_things_done = 0; - my $last_obj_copy = ""; - my $extrude_layer = sub { - my ($layer, $object_copies) = @_; - my $gcode = ""; - - if (!$second_layer_things_done && $layer->id == 1) { - for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { - $gcode .= $gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) - if $self->extruders->[$t]->temperature && $self->extruders->[$t]->temperature != $self->extruders->[$t]->first_layer_temperature; - } - $gcode .= $gcodegen->set_bed_temperature($Slic3r::Config->bed_temperature) - if $Slic3r::Config->bed_temperature && $Slic3r::Config->bed_temperature != $Slic3r::Config->first_layer_bed_temperature; - $second_layer_things_done = 1; - } - - # set new layer, but don't move Z as support material contact areas may need an intermediate one - $gcode .= $gcodegen->change_layer($layer); - - # prepare callback to call as soon as a Z command is generated - $gcodegen->move_z_callback(sub { - $gcodegen->move_z_callback(undef); # circular ref or not? - return "" if !$Slic3r::Config->layer_gcode; - return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode) . "\n"; - }); - - # extrude skirt - if ($skirt_done < $Slic3r::Config->skirt_height) { - $gcodegen->set_shift(@shift); - $gcode .= $gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder - $gcode .= $gcodegen->move_z($gcodegen->layer->print_z); - # skip skirt if we have a large brim - if ($layer->id < $Slic3r::Config->skirt_height) { - # distribute skirt loops across all extruders - for my $i (0 .. $#{$self->skirt}) { - # when printing layers > 0 ignore 'min_skirt_length' and - # just use the 'skirts' setting; also just use the current extruder - last if ($layer->id > 0) && ($i >= $Slic3r::Config->skirts); - $gcode .= $gcodegen->set_extruder($self->extruders->[ ($i/@{$self->extruders}) % @{$self->extruders} ]) - if $layer->id == 0; - $gcode .= $gcodegen->extrude_loop($self->skirt->[$i], 'skirt'); - } - } - $skirt_done++; - $gcodegen->straight_once(1); - } - - # extrude brim - if (!$brim_done) { - $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder - $gcode .= $gcodegen->move_z($gcodegen->layer->print_z); - $gcodegen->set_shift(@shift); - $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim}; - $brim_done = 1; - $gcodegen->straight_once(1); - } - - for my $copy (@$object_copies) { - $gcodegen->new_object(1) if $last_obj_copy && $last_obj_copy ne "$copy"; - $last_obj_copy = "$copy"; - - $gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y); - - # extrude support material before other things because it might use a lower Z - # and also because we avoid travelling on other things when printing it - if ($self->has_support_material) { - $gcode .= $gcodegen->move_z($layer->support_material_contact_z) - if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths }); - $gcode .= $gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); - if ($layer->support_contact_fills) { - $gcode .= $gcodegen->extrude_path($_, 'support material contact area') - for $layer->support_contact_fills->chained_path($gcodegen->last_pos); - } - - $gcode .= $gcodegen->move_z($layer->print_z); - if ($layer->support_fills) { - $gcode .= $gcodegen->extrude_path($_, 'support material') - for $layer->support_fills->chained_path($gcodegen->last_pos); - } - } - - # set actual Z - this will force a retraction - $gcode .= $gcodegen->move_z($layer->print_z); - - # tweak region ordering to save toolchanges - my @region_ids = 0 .. ($self->regions_count-1); - if ($gcodegen->multiple_extruders) { - my $last_extruder = $gcodegen->extruder; - my $best_region_id = first { $self->regions->[$_]->extruders->{perimeter} eq $last_extruder } @region_ids; - @region_ids = ($best_region_id, grep $_ != $best_region_id, @region_ids) if $best_region_id; - } - - foreach my $region_id (@region_ids) { - my $layerm = $layer->regions->[$region_id]; - my $region = $self->regions->[$region_id]; - - my @islands = (); - if ($Slic3r::Config->avoid_crossing_perimeters) { - push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices}; - PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { - my $p = $perimeter->unpack; - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->encloses_point($p->first_point)) { - push @{ $islands[$i]{perimeters} }, $p; - next PERIMETER; - } - } - push @{ $islands[-1]{perimeters} }, $p; # optimization - } - FILL: foreach my $fill (@{$layerm->fills}) { - my $f = $fill->unpack; - for my $i (0 .. $#{$layer->slices}-1) { - if ($layer->slices->[$i]->contour->encloses_point($f->first_point)) { - push @{ $islands[$i]{fills} }, $f; - next FILL; - } - } - push @{ $islands[-1]{fills} }, $f; # optimization - } - } else { - push @islands, { - perimeters => $layerm->perimeters, - fills => $layerm->fills, - }; - } - - foreach my $island (@islands) { - my $extrude_perimeters = sub { - return if !@{ $island->{perimeters} }; - $gcode .= $gcodegen->set_extruder($region->extruders->{perimeter}); - $gcode .= $gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; - }; - - my $extrude_fills = sub { - return if !@{ $island->{fills} }; - $gcode .= $gcodegen->set_extruder($region->extruders->{infill}); - for my $fill (@{ $island->{fills} }) { - if ($fill->isa('Slic3r::ExtrusionPath::Collection')) { - $gcode .= $gcodegen->extrude($_, 'fill') - for $fill->chained_path($gcodegen->last_pos); - } else { - $gcode .= $gcodegen->extrude($fill, 'fill') ; - } - } - }; - - # give priority to infill if we were already using its extruder and it wouldn't - # be good for perimeters - if ($Slic3r::Config->infill_first - || ($gcodegen->multiple_extruders && $region->extruders->{infill} eq $gcodegen->extruder) && $region->extruders->{infill} ne $region->extruders->{perimeter}) { - $extrude_fills->(); - $extrude_perimeters->(); - } else { - $extrude_perimeters->(); - $extrude_fills->(); - } - } - } - } - - # apply spiral vase post-processing if this layer contains suitable geometry - $gcode = $spiralvase->process_layer($gcode, $layer) - if defined $spiralvase - && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) - && ($layer->id >= $Slic3r::Config->skirt_height) - && ($layer->id >= $Slic3r::Config->bottom_solid_layers); - - return $gcode; - }; + # prepare the layer processor + my $layer_gcode = Slic3r::GCode::Layer->new( + print => $self, + gcodegen => $gcodegen, + shift => \@shift, + ); # do all objects for each layer if ($Slic3r::Config->complete_objects) { @@ -970,7 +799,7 @@ sub write_gcode { if $Slic3r::Config->first_layer_bed_temperature; $print_first_layer_temperature->(); } - print $fh $buffer->append($extrude_layer->($layer, [$copy]), $layer); + print $fh $buffer->append($layer_gcode->process_layer($layer, [$copy]), $layer); } print $fh $buffer->flush; $finished_objects++; @@ -981,8 +810,10 @@ sub write_gcode { config => $Slic3r::Config, gcodegen => $gcodegen, ); - print $fh $buffer->append($extrude_layer->($_, $_->object->copies), $_) - for sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; + my @layers = sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; + foreach my $layer (@layers) { + print $fh $buffer->append($layer_gcode->process_layer($layer, $layer->object->copies), $layer); + } print $fh $buffer->flush; } From 7180e7cb30938e996f86b4329f77bf5c1a8ef1b0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 10:35:11 +0200 Subject: [PATCH 12/78] Speed optimization in avoid_crossing_perimeters --- lib/Slic3r/GCode/MotionPlanner.pm | 6 ++++-- lib/Slic3r/Polygon.pm | 6 ------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 6eff6c3bc1..026485357b 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -75,6 +75,7 @@ sub BUILD { { my @outer = (map @$_, @{$self->_outer}); + my @outer_ex = map [$_], @outer; # as ExPolygons # lines of outer polygons connect visible points for my $i (0 .. $#outer) { @@ -91,7 +92,7 @@ sub BUILD { for my $m (0 .. $#{$outer[$i]}) { for my $n (0 .. $#{$outer[$j]}) { my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]); - if (!first { $_->intersects_line($line) } @outer) { + if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@outer_ex, [$line])}) { # this line does not cross any polygon my $dist = $line->length; $edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist; @@ -106,12 +107,13 @@ sub BUILD { # lines connecting inner polygons contours are visible but discouraged if (!$self->no_internal) { my @inner = (map $_->contour, map @$_, @{$self->_inner}); + my @inner_ex = map [$_], @inner; # as ExPolygons for my $i (0 .. $#inner) { for my $j (($i+1) .. $#inner) { for my $m (0 .. $#{$inner[$i]}) { for my $n (0 .. $#{$inner[$j]}) { my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]); - if (!first { $_->intersects_line($line) } @inner) { + if (!@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection(\@inner_ex, [$line])}) { # this line does not cross any polygon my $dist = $line->length * CROSSING_FACTOR; $edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist; diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index f8df6915a6..6e460f40b9 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -172,10 +172,4 @@ sub split_at_first_point { return $self->split_at_index(0); } -sub intersects_line { - my $self = shift; - my ($line) = @_; - return @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$self], [$line])} > 0; -} - 1; \ No newline at end of file From b5b0c2cc8944375d9361086236d8f332517a4dea Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 10:47:00 +0200 Subject: [PATCH 13/78] Bad copy and paste in commit from yesterday causing loss of perimeters. #1178 --- lib/Slic3r/GCode/Layer.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 4a6eb366dc..1cb0b90bab 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -176,8 +176,11 @@ sub _extrude_perimeters { my ($island, $region) = @_; return "" if !@{ $island->{perimeters} }; - return $self->gcodegen->set_extruder($region->extruders->{perimeter}) - . $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; + + my $gcode = ""; + $gcode .= $self->gcodegen->set_extruder($region->extruders->{perimeter}); + $gcode .= $self->gcodegen->extrude($_, 'perimeter') for @{ $island->{perimeters} }; + return $gcode; } sub _extrude_infill { From 627debf2843d28465d2a7a61225b5d95839af08f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 11:25:18 +0200 Subject: [PATCH 14/78] Scale bounding_box by sqrt(2) in honeycomb too to ensure it will cover the object even after rotation, like we already did for Rectilinear --- lib/Slic3r/Fill/Honeycomb.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/Fill/Honeycomb.pm b/lib/Slic3r/Fill/Honeycomb.pm index 1985ddb993..4a44b445c8 100644 --- a/lib/Slic3r/Fill/Honeycomb.pm +++ b/lib/Slic3r/Fill/Honeycomb.pm @@ -38,10 +38,12 @@ sub fill_surface { # adjust actual bounding box to the nearest multiple of our hex pattern # and align it so that it matches across layers + my $bounding_box = [ @{$self->bounding_box} ]; # clone $bounding_box->[$_] = 0 for X1, Y1; { my $bb_polygon = Slic3r::Polygon->new_from_bounding_box($bounding_box); + $bb_polygon->scale(sqrt 2); $bb_polygon->rotate($rotate_vector->[0][0], $hex_center); $bounding_box = [ Slic3r::Geometry::bounding_box($bb_polygon) ]; # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one From bbb190dc68e06f8666403e6df54fac69f55de81c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 11:35:41 +0200 Subject: [PATCH 15/78] Generate infill using each object's bounding_box instead of full print. #1177 --- lib/Slic3r/Fill.pm | 4 ++-- lib/Slic3r/Layer/Region.pm | 2 +- lib/Slic3r/Print.pm | 12 +++--------- lib/Slic3r/Print/Object.pm | 17 +++++++++++++++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 68b677c9fc..ecd0c024d2 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -17,7 +17,7 @@ use Slic3r::Geometry::Clipper qw(union_ex diff diff_ex intersection_ex offset); use Slic3r::Surface ':types'; -has 'print' => (is => 'ro', required => 1, weak_ref => 1); +has 'object' => (is => 'ro', required => 1, weak_ref => 1); has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( @@ -40,7 +40,7 @@ sub filler { } $self->fillers->{$filler} ||= $FillTypes{$filler}->new( - bounding_box => [ $self->print->bounding_box ], + bounding_box => [ $self->object->bounding_box ], ); return $self->fillers->{$filler}; } diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 65ee99ae6c..1e32aff94c 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -301,7 +301,7 @@ sub _fill_gaps { return unless $Slic3r::Config->gap_fill_speed > 0 && $Slic3r::Config->fill_density > 0 && @$gaps; - my $filler = $self->layer->object->print->fill_maker->filler('rectilinear'); + my $filler = $self->layer->object->fill_maker->filler('rectilinear'); $filler->layer_id($self->layer->id); # we should probably use this code to handle thin walls and remove that logic from diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c5e341cb52..9acbe2cf34 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -21,7 +21,6 @@ has 'regions' => (is => 'rw', default => sub {[]}); has 'support_material_flow' => (is => 'rw'); has 'first_layer_support_material_flow' => (is => 'rw'); has 'has_support_material' => (is => 'lazy'); -has 'fill_maker' => (is => 'lazy'); # ordered collection of extrusion paths to build skirt loops has 'skirt' => ( @@ -82,11 +81,6 @@ sub _build_has_support_material { || $self->config->support_material_enforce_layers > 0; } -sub _build_fill_maker { - my $self = shift; - return Slic3r::Fill->new(print => $self); -} - # caller is responsible for supplying models whose objects don't collide # and have explicit instance positions sub add_model { @@ -362,7 +356,6 @@ sub export_gcode { # this will generate extrusion paths for each layer $status_cb->(80, "Infilling layers"); { - my $fill_maker = $self->fill_maker; Slic3r::parallelize( items => sub { my @items = (); # [obj_idx, layer_id] @@ -379,10 +372,11 @@ sub export_gcode { my $fills = {}; while (defined (my $obj_layer = $q->dequeue)) { my ($obj_idx, $layer_id, $region_id) = @$obj_layer; + my $object = $self->objects->[$obj_idx]; $fills->{$obj_idx} ||= {}; $fills->{$obj_idx}{$layer_id} ||= {}; $fills->{$obj_idx}{$layer_id}{$region_id} = [ - $fill_maker->make_fill($self->objects->[$obj_idx]->layers->[$layer_id]->regions->[$region_id]), + $object->fill_maker->make_fill($object->layers->[$layer_id]->regions->[$region_id]), ]; } return $fills; @@ -401,7 +395,7 @@ sub export_gcode { }, no_threads_cb => sub { foreach my $layerm (map @{$_->regions}, map @{$_->layers}, @{$self->objects}) { - $layerm->fills([ $fill_maker->make_fill($layerm) ]); + $layerm->fills([ $layerm->layer->object->fill_maker->make_fill($layerm) ]); } }, ); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 39913d592e..816b986598 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -14,6 +14,7 @@ has 'size' => (is => 'rw', required => 1); has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] +has 'fill_maker' => (is => 'lazy'); sub BUILD { my $self = shift; @@ -76,6 +77,11 @@ sub BUILD { } } +sub _build_fill_maker { + my $self = shift; + return Slic3r::Fill->new(object => $self); +} + # This should be probably moved in Print.pm at the point where we sort Layer objects sub _trigger_copies { my $self = shift; @@ -127,6 +133,13 @@ sub get_layer_range { return ($min_layer, $max_layer); } +sub bounding_box { + my $self = shift; + + # since the object is aligned to origin, bounding box coincides with size + return Slic3r::Geometry::bounding_box([ [0,0], $self->size ]); +} + sub slice { my $self = shift; my %params = @_; @@ -931,7 +944,7 @@ sub generate_support_material { push @angles, $angles[0] + 90; } - my $filler = $self->print->fill_maker->filler($pattern); + my $filler = $self->fill_maker->filler($pattern); my $make_pattern = sub { my ($expolygon, $density) = @_; @@ -1016,7 +1029,7 @@ sub generate_support_material { # make a solid base on bottom layer if ($layer_id == 0) { - my $filler = $self->print->fill_maker->filler('rectilinear'); + my $filler = $self->fill_maker->filler('rectilinear'); $filler->angle($Slic3r::Config->support_material_angle + 90); foreach my $expolygon (@$islands) { my @paths = $filler->fill_surface( From 48e37f97b48839fb3f071920e5bccbeb22a61936 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 15:33:54 +0200 Subject: [PATCH 16/78] Fixed bad copy and paste causing multiple extruders to throw an error --- lib/Slic3r/GCode/Layer.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 1cb0b90bab..bb74dcd29f 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -1,6 +1,7 @@ package Slic3r::GCode::Layer; use Moo; +use List::Util qw(first); use Slic3r::Geometry qw(X Y unscale); has 'print' => (is => 'ro', required => 1, handles => [qw(extruders)]); From 5494f4f38558129283cbec8a2ba421aff3ec07b2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 17:34:33 +0200 Subject: [PATCH 17/78] Free @_ before spawning a new thread (known old Perl bug) as an attempt to fix the unref scalars error --- lib/Slic3r.pm | 1 + lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater.pm | 2 ++ 3 files changed, 4 insertions(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1d2849f1d4..bd5215c4c2 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -84,6 +84,7 @@ sub parallelize { $q->enqueue(@items, (map undef, 1..$Config->threads)); my $thread_cb = sub { $params{thread_cb}->($q) }; + @_ = (); foreach my $th (map threads->create($thread_cb), 1..$Config->threads) { $params{collect_cb}->($th->join); } diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 0f9057ed7c..a799a570c5 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -288,6 +288,7 @@ sub check_version { my %p = @_; Slic3r::debugf "Checking for updates...\n"; + @_ = (); threads->create(sub { my $ua = LWP::UserAgent->new; $ua->timeout(10); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4f63271d69..1144e5072f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -545,6 +545,7 @@ sub export_gcode { $self->statusbar->StartBusy; if ($Slic3r::have_threads) { + @_ = (); $self->{export_thread} = threads->create(sub { $self->export_gcode2( $print, @@ -739,6 +740,7 @@ sub make_thumbnail { } }; + @_ = (); $Slic3r::have_threads ? threads->create($cb)->detach : $cb->(); } From f13d4e4e660f4a6fdbad8df089b8ac0fc15e2622 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 19 May 2013 18:03:18 +0200 Subject: [PATCH 18/78] Restore skirt preview and extrude clearance after recent usage of int_offset() --- lib/Slic3r/GUI/Plater.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 1144e5072f..933dbcb20c 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -877,7 +877,7 @@ sub repaint { # if sequential printing is enabled and we have more than one object if ($parent->{config}->complete_objects && (map @{$_->instances}, @{$parent->{objects}}) > 1) { my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, @{$parent->{object_previews}->[-1][2]->expolygons} ])); - my ($clearance) = offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 1, JT_ROUND); + my ($clearance) = @{offset([$convex_hull], $parent->{config}->extruder_clearance_radius / 2 * $parent->{scaling_factor}, 100, JT_ROUND)}; $dc->SetPen($parent->{clearance_pen}); $dc->SetBrush($parent->{transparent_brush}); $dc->DrawPolygon($parent->_y($clearance), 0, 0); @@ -888,7 +888,7 @@ sub repaint { # draw skirt if (@{$parent->{object_previews}} && $parent->{config}->skirts) { my $convex_hull = Slic3r::Polygon->new(convex_hull([ map @{$_->contour}, map @{$_->[2]->expolygons}, @{$parent->{object_previews}} ])); - ($convex_hull) = offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 1, JT_ROUND); + ($convex_hull) = @{offset([$convex_hull], $parent->{config}->skirt_distance * $parent->{scaling_factor}, 100, JT_ROUND)}; $dc->SetPen($parent->{skirt_pen}); $dc->SetBrush($parent->{transparent_brush}); $dc->DrawPolygon($parent->_y($convex_hull), 0, 0) if $convex_hull; From c43ef450646bf03dee96419abb08599f6651e309 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 May 2013 09:56:55 +0200 Subject: [PATCH 19/78] Require the newest Math::Clipper and Boost::Geometry::Utils to avoid the huge memory leaks present in previous versions --- Build.PL | 4 ++-- lib/Slic3r.pm | 2 +- lib/Slic3r/Geometry/Clipper.pm | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Build.PL b/Build.PL index d7d4d8e8ab..522cf2284f 100644 --- a/Build.PL +++ b/Build.PL @@ -7,12 +7,12 @@ my $build = Module::Build->new( dist_version => '0.1', license => 'perl', requires => { - 'Boost::Geometry::Utils' => '0.08', + 'Boost::Geometry::Utils' => '0.12', 'Encode::Locale' => '0', 'File::Basename' => '0', 'File::Spec' => '0', 'Getopt::Long' => '0', - 'Math::Clipper' => '1.21', + 'Math::Clipper' => '1.22', 'Math::ConvexHull::MonotoneChain' => '0.01', 'Math::Geometry::Voronoi' => '1.3', 'Math::PlanePath' => '53', diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index bd5215c4c2..1338ce0669 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -29,7 +29,7 @@ our $var = "$FindBin::Bin/var"; use Encode; use Encode::Locale; -use Boost::Geometry::Utils 0.08; +use Boost::Geometry::Utils 0.12; use Moo 0.091009; use Slic3r::Config; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 498ccbcf78..e15f31a5a8 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -8,7 +8,7 @@ our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt); -use Math::Clipper 1.21 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); +use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); our $clipper = Math::Clipper->new; From 71d6f428ca3d1ceda95bb100fbe07338458aff09 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 May 2013 10:57:27 +0200 Subject: [PATCH 20/78] Apparent fix for "Attempt to free unreferenced scalar" --- lib/Slic3r/GUI/Plater.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 933dbcb20c..e70414a3c5 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -544,6 +544,11 @@ sub export_gcode { } $self->statusbar->StartBusy; + + # 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); + if ($Slic3r::have_threads) { @_ = (); $self->{export_thread} = threads->create(sub { From 8f5a9589485fff618921685e7d70da67826587bd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 20 May 2013 11:02:12 +0200 Subject: [PATCH 21/78] Rename do_slice() to quick_slice() --- lib/Slic3r/GUI.pm | 8 ++++---- lib/Slic3r/GUI/SkeinPanel.pm | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index a799a570c5..b08ae251d6 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -118,12 +118,12 @@ sub OnInit { $fileMenu->Append(wxID_EXIT, "&Quit", 'Quit Slic3r'); EVT_MENU($frame, MI_LOAD_CONF, sub { $self->{skeinpanel}->load_config_file }); EVT_MENU($frame, MI_EXPORT_CONF, sub { $self->{skeinpanel}->export_config }); - EVT_MENU($frame, MI_QUICK_SLICE, sub { $self->{skeinpanel}->do_slice; + EVT_MENU($frame, MI_QUICK_SLICE, sub { $self->{skeinpanel}->quick_slice; $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); - EVT_MENU($frame, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->do_slice(reslice => 1) }); - EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->do_slice(save_as => 1); + EVT_MENU($frame, MI_REPEAT_QUICK, sub { $self->{skeinpanel}->quick_slice(reslice => 1) }); + EVT_MENU($frame, MI_QUICK_SAVE_AS, sub { $self->{skeinpanel}->quick_slice(save_as => 1); $repeat->Enable(defined $Slic3r::GUI::SkeinPanel::last_input_file) }); - EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->do_slice(save_as => 1, export_svg => 1) }); + EVT_MENU($frame, MI_SLICE_SVG, sub { $self->{skeinpanel}->quick_slice(save_as => 1, export_svg => 1) }); EVT_MENU($frame, MI_COMBINE_STLS, sub { $self->{skeinpanel}->combine_stls }); EVT_MENU($frame, wxID_PREFERENCES, sub { Slic3r::GUI::Preferences->new($frame)->ShowModal }); EVT_MENU($frame, wxID_EXIT, sub {$_[0]->Close(0)}); diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 5defd274cf..6f30da477c 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -73,7 +73,7 @@ sub new { return $self; } -sub do_slice { +sub quick_slice { my $self = shift; my %params = @_; From 7baaf6bf5d3cdf68a877a16d5f27e76f0fbc6e35 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 30 May 2013 20:06:05 +0200 Subject: [PATCH 22/78] Fixed regression causing skirt_height to he honored incorrectly when printing more than one object. Includes regression test #1200 --- MANIFEST | 1 + lib/Slic3r/GCode/Layer.pm | 10 ++++----- lib/Slic3r/GCode/Reader.pm | 2 +- lib/Slic3r/Test.pm | 4 +++- t/skirt_brim.t | 46 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 t/skirt_brim.t diff --git a/MANIFEST b/MANIFEST index a9180a6913..45d52d6e61 100644 --- a/MANIFEST +++ b/MANIFEST @@ -78,6 +78,7 @@ t/retraction.t t/serialize.t t/shells.t t/slice.t +t/skirt_brim.t t/support.t t/vibrationlimit.t utils/amf-to-stl.pl diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index bb74dcd29f..d1716c4042 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -9,7 +9,7 @@ has 'gcodegen' => (is => 'ro', required => 1); has 'shift' => (is => 'ro', required => 1); has 'spiralvase' => (is => 'lazy'); -has 'skirt_done' => (is => 'rw', default => sub {0}); # count of skirt layers done +has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 has 'brim_done' => (is => 'rw'); has 'second_layer_things_done' => (is => 'rw'); has '_last_obj_copy' => (is => 'rw'); @@ -48,10 +48,10 @@ sub process_layer { }); # extrude skirt - if ($self->skirt_done < $Slic3r::Config->skirt_height) { + if ((values %{$self->skirt_done}) < $Slic3r::Config->skirt_height && !$self->skirt_done->{$layer->print_z}) { $self->gcodegen->set_shift(@{$self->shift}); $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder - $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + $gcode .= $self->gcodegen->move_z($layer->print_z); # skip skirt if we have a large brim if ($layer->id < $Slic3r::Config->skirt_height) { # distribute skirt loops across all extruders @@ -64,14 +64,14 @@ sub process_layer { $gcode .= $self->gcodegen->extrude_loop($self->print->skirt->[$i], 'skirt'); } } - $self->skirt_done($self->skirt_done + 1); + $self->skirt_done->{$layer->print_z} = 1; $self->gcodegen->straight_once(1); } # extrude brim if (!$self->brim_done) { $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder - $gcode .= $self->gcodegen->move_z($self->gcodegen->layer->print_z); + $gcode .= $self->gcodegen->move_z($layer->print_z); $self->gcodegen->set_shift(@{$self->shift}); $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; $self->brim_done(1); diff --git a/lib/Slic3r/GCode/Reader.pm b/lib/Slic3r/GCode/Reader.pm index 235a0ad741..20a1313b07 100644 --- a/lib/Slic3r/GCode/Reader.pm +++ b/lib/Slic3r/GCode/Reader.pm @@ -26,7 +26,7 @@ sub parse { my ($command, @args) = split /\s+/, $line; my %args = map { /([A-Z])(.*)/; ($1 => $2) } @args; - # check retraction + # check motion if ($command =~ /^G[01]$/) { foreach my $axis (@AXES) { if (exists $args{$axis}) { diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 63ee7ac647..46443cc6af 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -44,7 +44,9 @@ sub init_print { $config->set('gcode_comments', 1) if $ENV{SLIC3R_TESTS_GCODE}; my $print = Slic3r::Print->new(config => $config); - $print->add_model(model($model_name)); + + $model_name = [$model_name] if ref($model_name) ne 'ARRAY'; + $print->add_model(model($_)) for @$model_name; $print->validate; return $print; diff --git a/t/skirt_brim.t b/t/skirt_brim.t new file mode 100644 index 0000000000..c78bf41e0a --- /dev/null +++ b/t/skirt_brim.t @@ -0,0 +1,46 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(first); +use Slic3r; +use Slic3r::Test; + +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 1); + $config->set('skirt_height', 2); + $config->set('perimeters', 0); + $config->set('perimeter_speed', 99); + $config->set('cooling', 0); # to prevent speeds to be altered + $config->set('first_layer_speed', '100%'); # to prevent speeds to be altered + + my $test = sub { + my ($conf) = @_; + $conf ||= $config; + + my $print = Slic3r::Test::init_print(['20mm_cube','20mm_cube'], config => $config); + + my %layers_with_skirt = (); # Z => $count + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + if (defined $self->Z) { + $layers_with_skirt{$self->Z} //= 0; + $layers_with_skirt{$self->Z} = 1 + if $info->{extruding} && ($args->{F} // $self->F) == $config->perimeter_speed*60; + } + }); + fail "wrong number of layers with skirt" + unless (grep $_, values %layers_with_skirt) == $config->skirt_height; + }; + + ok $test->(), "skirt_height is honored when printing multiple objects too"; +} + +__END__ From da7649698809a1e5afc5460fdc022897ad1bb152 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 11:19:36 +0200 Subject: [PATCH 23/78] Store config in Slic3r::GCode object --- lib/Slic3r/GCode.pm | 133 ++++++++++++++++++++++---------------------- lib/Slic3r/Print.pm | 1 + t/gcode.t | 6 +- 3 files changed, 72 insertions(+), 68 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 1180b76ff1..4519d933a9 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -6,6 +6,7 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); +has 'config' => (is => 'ro', required => 1); has 'multiple_extruders' => (is => 'ro', default => sub {0} ); has 'layer_count' => (is => 'ro', required => 1 ); has 'layer' => (is => 'rw'); @@ -15,6 +16,7 @@ has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); has 'speed' => (is => 'rw'); +has 'speeds' => (is => 'lazy'); # mm/min has 'external_mp' => (is => 'rw'); has 'layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); @@ -35,15 +37,14 @@ has 'dec' => (is => 'ro', default => sub { 3 } ); has 'last_dir' => (is => 'ro', default => sub { [0,0] }); has 'dir_time' => (is => 'ro', default => sub { [0,0] }); -# calculate speeds (mm/min) -has 'speeds' => ( - is => 'ro', - default => sub {+{ - map { $_ => 60 * $Slic3r::Config->get_value("${_}_speed") } +sub _build_speeds { + my $self = shift; + return { + map { $_ => 60 * $self->config->get_value("${_}_speed") } qw(travel perimeter small_perimeter external_perimeter infill solid_infill top_solid_infill support_material bridge gap_fill retract), - }}, -); + }; +} # assign speeds to roles my %role_speeds = ( @@ -81,17 +82,17 @@ sub change_layer { my ($layer) = @_; $self->layer($layer); - if ($Slic3r::Config->avoid_crossing_perimeters) { + if ($self->config->avoid_crossing_perimeters) { $self->layer_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1), )); } my $gcode = ""; - if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { $gcode .= sprintf "M73 P%s%s\n", int(99 * ($layer->id / ($self->layer_count - 1))), - ($Slic3r::Config->gcode_comments ? ' ; update progress' : ''); + ($self->config->gcode_comments ? ' ; update progress' : ''); } return $gcode; } @@ -102,7 +103,7 @@ sub move_z { my ($z, $comment) = @_; $z *= &Slic3r::SCALING_FACTOR; - $z += $Slic3r::Config->z_offset; + $z += $self->config->z_offset; my $gcode = ""; my $current_z = $self->z; @@ -136,9 +137,9 @@ sub extrude_loop { # find the point of the loop that is closest to the current extruder position # or randomize if requested my $last_pos = $self->last_pos; - if ($Slic3r::Config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { - $last_pos = Slic3r::Point->new(scale $Slic3r::Config->print_center->[X], scale $Slic3r::Config->bed_size->[Y]); - $last_pos->rotate(rand(2*PI), $Slic3r::Config->print_center); + if ($self->config->randomize_start && $loop->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER) { + $last_pos = Slic3r::Point->new(scale $self->config->print_center->[X], scale $self->config->bed_size->[Y]); + $last_pos->rotate(rand(2*PI), $self->config->print_center); } my $start_index = $loop->nearest_point_index_to($last_pos); @@ -156,7 +157,7 @@ sub extrude_loop { $self->wipe_path($extrusion_path->polyline); # make a little move inwards before leaving loop - if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $Slic3r::Config->perimeters > 1) { + if ($loop->role == EXTR_ROLE_EXTERNAL_PERIMETER && $self->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); @@ -187,7 +188,7 @@ sub extrude_path { $path->simplify(&Slic3r::SCALED_RESOLUTION); # detect arcs - if ($Slic3r::Config->gcode_arcs && !$recursive) { + if ($self->config->gcode_arcs && !$recursive) { my $gcode = ""; foreach my $arc_path ($path->detect_arcs) { $gcode .= $self->extrude_path($arc_path, $description, 1); @@ -204,12 +205,12 @@ sub extrude_path { # adjust acceleration my $acceleration; - if ($Slic3r::Config->perimeter_acceleration && $path->is_perimeter) { - $acceleration = $Slic3r::Config->perimeter_acceleration; - } elsif ($Slic3r::Config->infill_acceleration && $path->is_fill) { - $acceleration = $Slic3r::Config->infill_acceleration; - } elsif ($Slic3r::Config->infill_acceleration && ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE)) { - $acceleration = $Slic3r::Config->bridge_acceleration; + if ($self->config->perimeter_acceleration && $path->is_perimeter) { + $acceleration = $self->config->perimeter_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_fill) { + $acceleration = $self->config->infill_acceleration; + } elsif ($self->config->infill_acceleration && ($path->role == EXTR_ROLE_BRIDGE || $path->role == EXTR_ROLE_INTERNALBRIDGE)) { + $acceleration = $self->config->bridge_acceleration; } $gcode .= $self->set_acceleration($acceleration) if $acceleration; @@ -251,19 +252,19 @@ sub extrude_path { if $self->extruder->wipe; } - if ($Slic3r::Config->cooling) { + if ($self->config->cooling) { my $path_time = $path_length / $self->speeds->{$self->last_speed} * 60; if ($self->layer->id == 0) { - $path_time = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ + $path_time = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ ? $path_time / ($1/100) - : $path_length / $Slic3r::Config->first_layer_speed * 60; + : $path_length / $self->config->first_layer_speed * 60; } $self->elapsed_time($self->elapsed_time + $path_time); } # reset acceleration - $gcode .= $self->set_acceleration($Slic3r::Config->default_acceleration) - if $acceleration && $Slic3r::Config->default_acceleration; + $gcode .= $self->set_acceleration($self->config->default_acceleration) + if $acceleration && $self->config->default_acceleration; return $gcode; } @@ -282,13 +283,13 @@ sub travel_to { $travel->translate(-$self->shift_x, -$self->shift_y); if ($travel->length < scale $self->extruder->retract_before_travel - || ($Slic3r::Config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) + || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) ) { $self->straight_once(0); $self->speed('travel'); $gcode .= $self->G0($point, undef, 0, $comment || ""); - } elsif (!$Slic3r::Config->avoid_crossing_perimeters || $self->straight_once) { + } elsif (!$self->config->avoid_crossing_perimeters || $self->straight_once) { $self->straight_once(0); $gcode .= $self->retract(travel_to => $point); $self->speed('travel'); @@ -322,7 +323,7 @@ sub _plan { my @travel = $mp->shortest_path($self->last_pos, $point)->lines; # if the path is not contained in a single island we need to retract - my $need_retract = !$Slic3r::Config->only_retract_when_crossing_perimeters; + my $need_retract = !$self->config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; foreach my $slice (@{$self->layer->slices}) { @@ -370,7 +371,7 @@ sub retract { ? undef : [undef, $self->z + $self->extruder->retract_lift, 0, 'lift plate during travel']; - if (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && $params{travel_to}) { + if (($self->config->g0 || $self->config->gcode_flavor eq 'mach3') && $params{travel_to}) { $self->speed('travel'); if ($lift) { # combine lift and retract @@ -381,7 +382,7 @@ sub retract { my $travel = [$params{travel_to}, undef, $retract->[2], "travel and $comment"]; $gcode .= $self->G0(@$travel); } - } elsif (($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3') && defined $params{move_z}) { + } elsif (($self->config->g0 || $self->config->gcode_flavor eq 'mach3') && defined $params{move_z}) { # combine Z change and retraction $self->speed('travel'); my $travel = [undef, $params{move_z}, $retract->[2], "change layer and $comment"]; @@ -417,7 +418,7 @@ sub retract { # reset extrusion distance during retracts # this makes sure we leave sufficient precision in the firmware - $gcode .= $self->reset_e if $Slic3r::Config->gcode_flavor !~ /^(?:mach3|makerbot)$/; + $gcode .= $self->reset_e if $self->config->gcode_flavor !~ /^(?:mach3|makerbot)$/; return $gcode; } @@ -448,8 +449,8 @@ sub reset_e { my $self = shift; $self->extrusion_distance(0); - return sprintf "G92 %s0%s\n", $Slic3r::Config->extrusion_axis, ($Slic3r::Config->gcode_comments ? ' ; reset extrusion distance' : '') - if $Slic3r::Config->extrusion_axis && !$Slic3r::Config->use_relative_e_distances; + return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') + if $self->config->extrusion_axis && !$self->config->use_relative_e_distances; } sub set_acceleration { @@ -458,12 +459,12 @@ sub set_acceleration { return "" if !$acceleration; return sprintf "M204 S%s%s\n", - $acceleration, ($Slic3r::Config->gcode_comments ? ' ; adjust acceleration' : ''); + $acceleration, ($self->config->gcode_comments ? ' ; adjust acceleration' : ''); } sub G0 { my $self = shift; - return $self->G1(@_) if !($Slic3r::Config->g0 || $Slic3r::Config->gcode_flavor eq 'mach3'); + return $self->G1(@_) if !($self->config->g0 || $self->config->gcode_flavor eq 'mach3'); return $self->_G0_G1("G0", @_); } @@ -533,9 +534,9 @@ sub _Gx { ? ($self->extruder->retract_speed_mm_min) : $self->speeds->{$self->speed} // $self->speed; if ($e && $self->layer && $self->layer->id == 0 && $comment !~ /retract/) { - $F = $Slic3r::Config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ + $F = $self->config->first_layer_speed =~ /^(\d+(?:\.\d+)?)%$/ ? ($F * $1/100) - : $Slic3r::Config->first_layer_speed * 60; + : $self->config->first_layer_speed * 60; } $self->last_speed($self->speed); $self->last_f($F); @@ -543,14 +544,14 @@ sub _Gx { $gcode .= sprintf " F%.${dec}f", $F if defined $F; # output extrusion distance - if ($e && $Slic3r::Config->extrusion_axis) { - $self->extrusion_distance(0) if $Slic3r::Config->use_relative_e_distances; + if ($e && $self->config->extrusion_axis) { + $self->extrusion_distance(0) if $self->config->use_relative_e_distances; $self->extrusion_distance($self->extrusion_distance + $e); $self->total_extrusion_length($self->total_extrusion_length + $e); - $gcode .= sprintf " %s%.5f", $Slic3r::Config->extrusion_axis, $self->extrusion_distance; + $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extrusion_distance; } - $gcode .= sprintf " ; %s", $comment if $comment && $Slic3r::Config->gcode_comments; + $gcode .= sprintf " ; %s", $comment if $comment && $self->config->gcode_comments; if ($append_bridge_off) { $gcode .= "\n;_BRIDGE_FAN_END"; } @@ -575,8 +576,8 @@ sub set_extruder { $gcode .= $self->retract(toolchange => 1) if defined $self->extruder; # append custom toolchange G-code - if (defined $self->extruder && $Slic3r::Config->toolchange_gcode) { - $gcode .= sprintf "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->toolchange_gcode, { + if (defined $self->extruder && $self->config->toolchange_gcode) { + $gcode .= sprintf "%s\n", $self->config->replace_options($self->config->toolchange_gcode, { previous_extruder => $self->extruder->id, next_extruder => $extruder->id, }); @@ -585,11 +586,11 @@ sub set_extruder { # set the new extruder $self->extruder($extruder); my $toolchange_gcode = sprintf "%s%d%s\n", - ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'), + ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'), $extruder->id, - ($Slic3r::Config->gcode_comments ? ' ; change extruder' : ''); + ($self->config->gcode_comments ? ' ; change extruder' : ''); - if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { $gcode .= $self->reset_e; $gcode .= $toolchange_gcode; } else { @@ -607,18 +608,18 @@ sub set_fan { if ($self->last_fan_speed != $speed || $dont_save) { $self->last_fan_speed($speed) if !$dont_save; if ($speed == 0) { - my $code = $Slic3r::Config->gcode_flavor eq 'teacup' + my $code = $self->config->gcode_flavor eq 'teacup' ? 'M106 S0' - : $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ + : $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M127' : 'M107'; - return sprintf "$code%s\n", ($Slic3r::Config->gcode_comments ? ' ; disable fan' : ''); + return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : ''); } else { - if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { - return sprintf "M126%s\n", ($Slic3r::Config->gcode_comments ? ' ; enable fan' : ''); + if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : ''); } else { - return sprintf "M106 %s%d%s\n", ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), - (255 * $speed / 100), ($Slic3r::Config->gcode_comments ? ' ; enable fan' : ''); + return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), + (255 * $speed / 100), ($self->config->gcode_comments ? ' ; enable fan' : ''); } } } @@ -629,17 +630,17 @@ sub set_temperature { my $self = shift; my ($temperature, $wait, $tool) = @_; - return "" if $wait && $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/; + return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/; - my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') + my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", - ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && ($self->multiple_extruders || $Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : ""; + ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, + (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" - if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; + if $self->config->gcode_flavor eq 'teacup' && $wait; return $gcode; } @@ -648,14 +649,14 @@ sub set_bed_temperature { my $self = shift; my ($temperature, $wait) = @_; - my ($code, $comment) = ($wait && $Slic3r::Config->gcode_flavor ne 'teacup') - ? (($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') + my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') + ? (($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); my $gcode = sprintf "$code %s%d ; $comment\n", - ($Slic3r::Config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; + ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; $gcode .= "M116 ; wait for bed temperature to be reached\n" - if $Slic3r::Config->gcode_flavor eq 'teacup' && $wait; + if $self->config->gcode_flavor eq 'teacup' && $wait; return $gcode; } @@ -665,8 +666,8 @@ sub _limit_frequency { my $self = shift; my ($point) = @_; - return '' if $Slic3r::Config->vibration_limit == 0; - my $min_time = 1 / ($Slic3r::Config->vibration_limit * 60); # in minutes + return '' if $self->config->vibration_limit == 0; + my $min_time = 1 / ($self->config->vibration_limit * 60); # in minutes # calculate the move vector and move direction my $vector = Slic3r::Line->new($self->last_pos, $point)->vector; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 9acbe2cf34..04fc20ed81 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -688,6 +688,7 @@ sub write_gcode { # set up our extruder object my $gcodegen = Slic3r::GCode->new( + config => $self->config, multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); diff --git a/t/gcode.t b/t/gcode.t index c41307d4fc..61c940faf2 100644 --- a/t/gcode.t +++ b/t/gcode.t @@ -11,8 +11,10 @@ use Slic3r; use Slic3r::Geometry qw(scale); { - local $Slic3r::Config = Slic3r::Config->new_from_defaults; - my $gcodegen = Slic3r::GCode->new(layer_count => 1); + my $gcodegen = Slic3r::GCode->new( + config => Slic3r::Config->new_from_defaults, + layer_count => 1, + ); $gcodegen->set_shift(10, 10); is_deeply $gcodegen->last_pos, [scale -10, scale -10], 'last_pos is shifted correctly'; } From 5c7dd2cf78310b2511aa21a29329f24ca6d91026 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 12:18:33 +0200 Subject: [PATCH 24/78] Some fixes to the cooling logic and new test suite for cooling --- MANIFEST | 1 + lib/Slic3r/GCode/CoolingBuffer.pm | 13 +++-- lib/Slic3r/Print.pm | 14 ++++- t/cooling.t | 89 +++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 t/cooling.t diff --git a/MANIFEST b/MANIFEST index 45d52d6e61..c319935b98 100644 --- a/MANIFEST +++ b/MANIFEST @@ -66,6 +66,7 @@ t/clean_polylines.t t/clipper.t t/collinear.t t/combineinfill.t +t/cooling.t t/custom_gcode.t t/dynamic.t t/fill.t diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index a04554f58c..a4f44fe260 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -6,7 +6,7 @@ has 'gcodegen' => (is => 'ro', required => 1); has 'gcode' => (is => 'rw', default => sub {""}); has 'elapsed_time' => (is => 'rw', default => sub {0}); has 'layer_id' => (is => 'rw'); -has 'last_z' => (is => 'rw'); +has 'last_z' => (is => 'rw', default => sub { {} }); # obj_id => z (basically a 'last seen' table) has 'min_print_speed' => (is => 'lazy'); sub _build_min_print_speed { @@ -16,15 +16,17 @@ sub _build_min_print_speed { sub append { my $self = shift; - my ($gcode, $layer) = @_; + my ($gcode, $obj_id, $layer_id, $print_z) = @_; + + # TODO: differentiate $obj_id between normal layers and support layers my $return = ""; - if (defined $self->last_z && $self->last_z != $layer->print_z) { + if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) { $return = $self->flush; } - $self->layer_id($layer->id); - $self->last_z($layer->print_z); + $self->layer_id($layer_id); + $self->last_z->{$obj_id} = $print_z; $self->gcode($self->gcode . $gcode); $self->elapsed_time($self->elapsed_time + $self->gcodegen->elapsed_time); $self->gcodegen->elapsed_time(0); @@ -39,6 +41,7 @@ sub flush { my $elapsed = $self->elapsed_time; $self->gcode(""); $self->elapsed_time(0); + $self->last_z({}); # reset the whole table otherwise we would compute overlapping times my $fan_speed = $self->config->fan_always_on ? $self->config->min_fan_speed : 0; my $speed_factor = 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 04fc20ed81..0e4182da50 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -794,7 +794,12 @@ sub write_gcode { if $Slic3r::Config->first_layer_bed_temperature; $print_first_layer_temperature->(); } - print $fh $buffer->append($layer_gcode->process_layer($layer, [$copy]), $layer); + print $fh $buffer->append( + $layer_gcode->process_layer($layer, [$copy]), + $layer->object."", + $layer->id, + $layer->print_z, + ); } print $fh $buffer->flush; $finished_objects++; @@ -807,7 +812,12 @@ sub write_gcode { ); my @layers = sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; foreach my $layer (@layers) { - print $fh $buffer->append($layer_gcode->process_layer($layer, $layer->object->copies), $layer); + print $fh $buffer->append( + $layer_gcode->process_layer($layer, $layer->object->copies), + $layer->object."", + $layer->id, + $layer->print_z, + ); } print $fh $buffer->flush; } diff --git a/t/cooling.t b/t/cooling.t new file mode 100644 index 0000000000..27cd59b7a3 --- /dev/null +++ b/t/cooling.t @@ -0,0 +1,89 @@ +use Test::More; +use strict; +use warnings; + +plan tests => 8; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; + +sub buffer { + my $config = shift || Slic3r::Config->new_from_defaults; + my $buffer = Slic3r::GCode::CoolingBuffer->new( + config => $config, + gcodegen => Slic3r::GCode->new(config => $config, layer_count => 10), + ); + return $buffer; +} + +my $config = Slic3r::Config->new_from_defaults; +$config->set('disable_fan_first_layers', 0); + +{ + my $buffer = buffer($config); + $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time + 1); + my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; + like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; +} + +{ + my $buffer = buffer($config); + $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); + my $gcode = $buffer->append("G1 X50 F2500\nG1 X100 E1 F3000\nG1 E4 F400", 0, 0, 0.4) . $buffer->flush; + unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold'; + like $gcode, qr/F2500/, 'speed is not altered for travel moves'; + like $gcode, qr/F400/, 'speed is not altered for extruder-only moves'; +} + +{ + my $buffer = buffer($config); + $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time + 1); + my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; + unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; +} + +{ + my $buffer = buffer($config); + my $gcode = ""; + for my $obj_id (0 .. 1) { + # use an elapsed time which is < the slowdown threshold but greater than it when summed twice + $buffer->gcodegen->elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4); + } + $gcode .= $buffer->flush; + like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at same Z'; +} + +{ + my $buffer = buffer($config); + my $gcode = ""; + for my $layer_id (0 .. 1) { + for my $obj_id (0 .. 1) { + # use an elapsed time which is < the threshold but greater than it when summed twice + $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time - 1); + $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights + } + } + $gcode .= $buffer->flush; + unlike $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z'; +} + +{ + my $buffer = buffer($config); + my $gcode = ""; + for my $layer_id (0 .. 1) { + for my $obj_id (0 .. 1) { + # use an elapsed time which is < the threshold even when summed twice + $buffer->gcodegen->elapsed_time($buffer->config->fan_below_layer_time/2 - 1); + $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights + } + } + $gcode .= $buffer->flush; + like $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z'; +} + +__END__ From 5f06cea821d1cdcff77dfc5912a683dcc55b18d2 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 12:45:18 +0200 Subject: [PATCH 25/78] Prevent --extrusion-multiplier <= 0. #1183 --- lib/Slic3r/Config.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 3fc8c28355..257b90b458 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -1328,6 +1328,10 @@ sub validate { if $self->extruder_clearance_radius <= 0; die "Invalid value for --extruder-clearance-height\n" if $self->extruder_clearance_height <= 0; + + # --extrusion-multiplier + die "Invalid value for --extrusion-multiplier\n" + if defined first { $_ <= 0 } @{$self->extrusion_multiplier}; } sub replace_options { From 917915d68e5932df327a797e47a7c8a6fcfe9ca4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 14:23:42 +0200 Subject: [PATCH 26/78] Bugfix: superfluous extra perimeters were generated. #1170 --- lib/Slic3r/Print/Object.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 816b986598..b166a0e319 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -313,6 +313,7 @@ sub make_perimeters { [ offset([ map @$_, @$diff ], -$perimeter_spacing) ], +$perimeter_spacing ) ], + 1, ); next if !@$diff; # diff contains the collapsed area From c62b49d1af7a6cbecfa3f97f28b388fadab40fe0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 14:30:07 +0200 Subject: [PATCH 27/78] Optimization of extra perimeters detection --- lib/Slic3r/Geometry/Clipper.pm | 15 ++++++++++++++- lib/Slic3r/Print/Object.pm | 22 ++++++++-------------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index e15f31a5a8..276a300b8d 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -6,7 +6,8 @@ require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND - JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt); + JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt + intersection); use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -118,6 +119,18 @@ sub intersection_ex { ]; } +sub intersection { + my ($subject, $clip, $jointype, $safety_offset) = @_; + $jointype = PFT_NONZERO unless defined $jointype; + $clipper->clear; + $clipper->add_subject_polygons($subject); + $clipper->add_clip_polygons($safety_offset ? safety_offset($clip) : $clip); + return [ + map Slic3r::Polygon->new($_), + @{ $clipper->execute(CT_INTERSECTION, $jointype, $jointype) }, + ]; +} + sub xor_ex { my ($subject, $clip, $jointype) = @_; $jointype = PFT_NONZERO unless defined $jointype; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index b166a0e319..11339955a6 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -4,7 +4,8 @@ use Moo; use List::Util qw(min sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path_points); -use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex); +use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex + offset2 diff intersection); use Slic3r::Surface ':types'; has 'print' => (is => 'ro', weak_ref => 1, required => 1); @@ -300,19 +301,16 @@ sub make_perimeters { my $overlap = $perimeter_spacing; # one perimeter - my $diff = diff_ex( + my $diff = diff( [ offset([ map @{$_->expolygon}, @{$layerm->slices} ], -($Slic3r::Config->perimeters * $perimeter_spacing)) ], [ offset([ map @{$_->expolygon}, @{$upper_layerm->slices} ], -$overlap) ], ); next if !@$diff; # if we need more perimeters, $diff should contain a narrow region that we can collapse - $diff = diff_ex( - [ map @$_, @$diff ], - [ offset( - [ offset([ map @$_, @$diff ], -$perimeter_spacing) ], - +$perimeter_spacing - ) ], + $diff = diff( + $diff, + [ offset2($diff, -$perimeter_spacing, +$perimeter_spacing) ], 1, ); next if !@$diff; @@ -324,18 +322,14 @@ sub make_perimeters { # compute polygons representing the thickness of the hypotetical new internal perimeter # of our slice $extra_perimeters++; - my $hypothetical_perimeter = diff_ex( + 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))) ], ); last CYCLE if !@$hypothetical_perimeter; # no extra perimeter is possible # only add the perimeter if there's an intersection with the collapsed area - my $intersection = intersection_ex( - [ map @$_, @$diff ], - [ map @$_, @$hypothetical_perimeter ], - ); - last CYCLE if !@$intersection; + last CYCLE if !@{ intersection($diff, $hypothetical_perimeter) }; Slic3r::debugf " adding one more perimeter at layer %d\n", $layer_id; $slice->extra_perimeters($extra_perimeters); } From 49531f6f7834d5b00a27fd91cfeccc57286e87cd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 31 May 2013 19:41:31 +0200 Subject: [PATCH 28/78] Fixed regression causing rotation from plater to also translate the resulting G-code. #1191 --- lib/Slic3r/GUI/Plater.pm | 3 +-- lib/Slic3r/Model.pm | 2 +- lib/Slic3r/Print.pm | 2 +- lib/Slic3r/TriangleMesh.pm | 4 ++-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index e70414a3c5..1e229a78b5 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -718,7 +718,7 @@ sub make_model { } $new_model_object->scale($plater_object->scale); $new_model_object->add_instance( - rotation => $plater_object->rotate, + rotation => $plater_object->rotate, # around center point offset => [ @$_ ], ) for @{$plater_object->instances}; $new_model_object->align_to_origin; @@ -1128,7 +1128,6 @@ sub instances_count { sub make_thumbnail { my $self = shift; - my @points = map [ @$_[X,Y] ], @{$self->model_object->mesh->vertices}; my $mesh = $self->model_object->mesh; my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index cb740ab741..7082472656 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -390,7 +390,7 @@ package Slic3r::Model::Instance; use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); -has 'rotation' => (is => 'rw', default => sub { 0 }); +has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point has 'offset' => (is => 'rw'); 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 0e4182da50..ae29924843 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -122,7 +122,7 @@ sub add_model { # we ignore the per-instance rotation currently and only # consider the first one - $mesh->rotate($object->instances->[0]->rotation); + $mesh->rotate($object->instances->[0]->rotation, $mesh->center); $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 0968f7354d..f6146d5405 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -324,14 +324,14 @@ sub make_loops { sub rotate { my $self = shift; - my ($deg) = @_; + my ($deg, $center) = @_; return if $deg == 0; my $rad = Slic3r::Geometry::deg2rad($deg); # transform vertex coordinates foreach my $vertex (@{$self->vertices}) { - @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, undef, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); + @$vertex = (@{ +(Slic3r::Geometry::rotate_points($rad, $center, [ $vertex->[X], $vertex->[Y] ]))[0] }, $vertex->[Z]); } } From d801876ee93de5dd1bea2c311a7d7cf3497eda74 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 1 Jun 2013 18:54:07 +0200 Subject: [PATCH 29/78] Revert "Bugfix: configuration wizard crash 2 #1077" This reverts commit 55c413627f8545fb5d2b75efb157d30689985d53. --- lib/Slic3r/GUI/SkeinPanel.pm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 7e61cd4a09..6f30da477c 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -252,9 +252,7 @@ sub load_config { my ($config) = @_; foreach my $tab (values %{$self->{options_tabs}}) { - if ($self->{mode} eq 'expert') { - $tab->set_value($_, $config->$_) for keys %$config; - } + $tab->set_value($_, $config->$_) for keys %$config; } } From 30ce7dc745039301913456c7a2619828b2f65e10 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 1 Jun 2013 18:56:23 +0200 Subject: [PATCH 30/78] Bugfix: wizard crashing in Simple Mode. #1077 #1122 --- lib/Slic3r/GUI/SimpleTab.pm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/Slic3r/GUI/SimpleTab.pm b/lib/Slic3r/GUI/SimpleTab.pm index 0463a3d4e6..c1fee5439e 100644 --- a/lib/Slic3r/GUI/SimpleTab.pm +++ b/lib/Slic3r/GUI/SimpleTab.pm @@ -78,6 +78,17 @@ sub load_config { } } +sub set_value { + my $self = shift; + my ($opt_key, $value) = @_; + + my $changed = 0; + foreach my $optgroup (@{$self->{optgroups}}) { + $changed = 1 if $optgroup->set_value($opt_key, $value); + } + return $changed; +} + sub is_dirty { 0 } sub config { $_[0]->{config}->clone } From da36df65a44d340983fa01fc25ad370c2f85cd56 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 11:15:57 +0200 Subject: [PATCH 31/78] Warning removed when exporting config --- lib/Slic3r/Config.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 257b90b458..41b5fc4f78 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -9,6 +9,7 @@ use List::Util qw(first); our @Ignore = qw(duplicate_x duplicate_y multiply_x multiply_y support_material_tool acceleration); my $serialize_comma = sub { join ',', @{$_[0]} }; +my $serialize_comma_bool = sub { join ',', map $_ // 0, @{$_[0]} }; my $deserialize_comma = sub { [ split /,/, $_[0] ] }; our $Options = { @@ -797,7 +798,7 @@ END tooltip => 'This flag enforces a retraction whenever a Z move is done.', cli => 'retract-layer-change!', type => 'bool', - serialize => $serialize_comma, + serialize => $serialize_comma_bool, deserialize => $deserialize_comma, default => [1], }, @@ -806,7 +807,7 @@ END tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.', cli => 'wipe!', type => 'bool', - serialize => $serialize_comma, + serialize => $serialize_comma_bool, deserialize => $deserialize_comma, default => [0], }, From 655d528d929af4daacd63bb7796c623e3347bfba Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 16:56:08 +0200 Subject: [PATCH 32/78] Fixed recent regression causing a spike when avoid_crossing_perimeters was used on split objects --- lib/Slic3r/GUI/Plater.pm | 4 ++-- lib/Slic3r/Model.pm | 35 +++++++++++++++++++++++++++-------- lib/Slic3r/Print.pm | 19 ++++++++++++++----- lib/Slic3r/TriangleMesh.pm | 9 +++++++-- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 1e229a78b5..0213f6c455 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -719,11 +719,11 @@ sub make_model { $new_model_object->scale($plater_object->scale); $new_model_object->add_instance( rotation => $plater_object->rotate, # around center point - offset => [ @$_ ], + offset => Slic3r::Point->new($_), ) for @{$plater_object->instances}; - $new_model_object->align_to_origin; } + $model->align_to_origin; return $model; } diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 7082472656..b7ff6f7aec 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -164,14 +164,19 @@ sub vertices { return [ map @{$_->vertices}, @{$self->objects} ]; } +sub used_vertices { + my $self = shift; + return [ map @{$_->used_vertices}, @{$self->objects} ]; +} + sub size { my $self = shift; - return [ Slic3r::Geometry::size_3D($self->vertices) ]; + return [ Slic3r::Geometry::size_3D($self->used_vertices) ]; } sub extents { my $self = shift; - return Slic3r::Geometry::bounding_box_3D($self->vertices); + return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } sub align_to_origin { @@ -179,8 +184,17 @@ sub align_to_origin { # calculate the displacements needed to # have lowest value for each axis at coordinate 0 - my @extents = $self->extents; - $self->move(map -$extents[$_][MIN], X,Y,Z); + { + my @extents = $self->extents; + $self->move(map -$extents[$_][MIN], X,Y,Z); + } + + # align all instances to 0,0 as well + { + my @instances = map @{$_->instances}, @{$self->objects}; + my @extents = Slic3r::Geometry::bounding_box_3D([ map $_->offset, @instances ]); + $_->offset->translate(-$extents[X][MIN], -$extents[Y][MIN]) for @instances; + } } sub move { @@ -260,7 +274,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z MIN move_points_3D); +use Slic3r::Geometry qw(X Y Z MIN move_points move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -309,14 +323,19 @@ sub mesh { ); } +sub used_vertices { + my $self = shift; + return [ map $self->vertices->[$_], map @$_, map @{$_->facets}, @{$self->volumes} ]; +} + sub size { my $self = shift; - return [ Slic3r::Geometry::size_3D($self->vertices) ]; + return [ Slic3r::Geometry::size_3D($self->used_vertices) ]; } sub extents { my $self = shift; - return Slic3r::Geometry::bounding_box_3D($self->vertices); + return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } sub align_to_origin { @@ -391,6 +410,6 @@ use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point -has 'offset' => (is => 'rw'); +has 'offset' => (is => 'rw'); # must be Slic3r::Point object 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index ae29924843..84a208ea02 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,7 +6,7 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN PI scale unscale move_points nearest_point); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points nearest_point); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); @@ -116,8 +116,7 @@ sub add_model { : $mesh; } - foreach my $mesh (@meshes) { - next unless $mesh; + foreach my $mesh (grep $_, @meshes) { $mesh->check_manifoldness; # we ignore the per-instance rotation currently and only @@ -127,12 +126,22 @@ sub add_model { $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } + # align the object to origin; not sure this is required by the toolpath generation + # algorithms, but it's good practice to avoid negative coordinates; it probably + # provides also some better performance in infill generation + my @extents = Slic3r::Geometry::bounding_box_3D([ map @{$_->used_vertices}, grep $_, @meshes ]); + foreach my $mesh (grep $_, @meshes) { + $mesh->move(map -$extents[$_][MIN], X,Y,Z); + } + # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( print => $self, meshes => [ @meshes ], - copies => [ map [ scale $_->offset->[X], scale $_->offset->[Y] ], @{$object->instances} ], - size => [ map scale $_, @{ $object->size } ], + copies => [ + map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}, + ], + size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ], input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index f6146d5405..3506d893e8 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -398,14 +398,19 @@ sub duplicate { $self->BUILD; } +sub used_vertices { + my $self = shift; + return [ map $self->vertices->[$_], map @$_, @{$self->facets} ]; +} + sub extents { my $self = shift; - return Slic3r::Geometry::bounding_box_3D($self->vertices); + return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } sub size { my $self = shift; - return Slic3r::Geometry::size_3D($self->vertices); + return Slic3r::Geometry::size_3D($self->used_vertices); } sub slice_facet { From 28a01e7c5e170aa50dcb01d420495d8ff9ddd5a7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 16:58:23 +0200 Subject: [PATCH 33/78] Fix past participle --- lib/Slic3r/GUI/Plater.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 0213f6c455..0dbca23b0e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -476,7 +476,7 @@ sub split_object { my $model_object = $current_object->get_model_object; if (@{$model_object->volumes} > 1) { - Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it contains more than one volume/material."); + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it contains more than one volume/material."); return; } @@ -485,7 +485,7 @@ sub split_object { my @new_meshes = $mesh->split_mesh; if (@new_meshes == 1) { - Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be splitted because it already contains a single part."); + Slic3r::GUI::warning_catcher($self)->("The selected object couldn't be split because it already contains a single part."); return; } From 086ec4af9c4925c7bf5cac9f91b84fd46938ce11 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:32:53 +0200 Subject: [PATCH 34/78] Fixed yet one more regression in Quick Slice caused by recent Model refactoring. #1208 --- lib/Slic3r/Print.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 84a208ea02..642891419e 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -121,7 +121,8 @@ sub add_model { # we ignore the per-instance rotation currently and only # consider the first one - $mesh->rotate($object->instances->[0]->rotation, $mesh->center); + $mesh->rotate($object->instances->[0]->rotation, $mesh->center) + if @{ $object->instances // [] }; $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } @@ -139,7 +140,9 @@ sub add_model { print => $self, meshes => [ @meshes ], copies => [ - map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}, + $object->instances + ? (map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}) + : [0,0], ], size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ], input_file => $object->input_file, From 8a01cb9e96f2c3552c466956b739d2cd5b46176a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:44:59 +0200 Subject: [PATCH 35/78] Generate a better error when input file is too thin and no layers could be generated. #1127 --- lib/Slic3r/Print/Object.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 11339955a6..6e80673827 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -184,13 +184,13 @@ sub slice { $self->meshes->[$region_id] = undef; # free memory } - die "Invalid input file\n" if !@{$self->layers}; # free memory $self->meshes(undef); # remove last layer(s) if empty - pop @{$self->layers} while !map @{$_->lines}, @{$self->layers->[-1]->regions}; + pop @{$self->layers} while @{$self->layers} && (!map @{$_->lines}, @{$self->layers->[-1]->regions}); + die "Invalid or too thin input file: no layers could be generated\n" if !@{$self->layers}; foreach my $layer (@{ $self->layers }) { # make sure all layers contain layer region objects for all regions From 6bb2e593a7dadd2871ca100dd31c8eb072bab030 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:49:21 +0200 Subject: [PATCH 36/78] Avoid potential fatal error when processing models with empty layers. #1127 --- lib/Slic3r/GCode/Layer.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index d1716c4042..5d398f463d 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -119,7 +119,8 @@ sub process_layer { my @islands = (); if ($Slic3r::Config->avoid_crossing_perimeters) { - push @islands, map +{ perimeters => [], fills => [] }, @{$layer->slices}; + push @islands, { perimeters => [], fills => [] } + for 1 .. (@{$layer->slices} || 1); # make sure we have at least one island hash to avoid failure of the -1 subscript below PERIMETER: foreach my $perimeter (@{$layerm->perimeters}) { my $p = $perimeter->unpack; for my $i (0 .. $#{$layer->slices}-1) { From 895e0bbfcd22bb364bcadc062f86eb9d1ed18a49 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 19:58:29 +0200 Subject: [PATCH 37/78] Include M82/M83 for Teacup too. #1206 --- lib/Slic3r/Print.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 642891419e..c3fbb7b340 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -725,7 +725,7 @@ sub write_gcode { print $fh "G90 ; use absolute coordinates\n"; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; - if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|makerbot|sailfish)$/) { + if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup|makerbot|sailfish)$/) { if ($Slic3r::Config->use_relative_e_distances) { print $fh "M83 ; use relative distances for extrusion\n"; } else { From 8c74d2f41eb12bf098245dfe2769b1ac73bada70 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 2 Jun 2013 20:03:22 +0200 Subject: [PATCH 38/78] New --autosave option for better toolchain integration. #837 --- README.markdown | 1 + lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/SkeinPanel.pm | 9 ++++++--- slic3r.pl | 3 +++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 01c43d22da..0a4e2b95df 100644 --- a/README.markdown +++ b/README.markdown @@ -93,6 +93,7 @@ The author of the Silk icon set is Mark James. GUI options: --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) + --autosave Automatically export current configuration to the specified file Output options: --output-filename-format diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index b08ae251d6..fd46cc5ebb 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -45,6 +45,7 @@ use constant MI_DOCUMENTATION => &Wx::NewId; our $datadir; our $no_plater; our $mode; +our $autosave; our $Settings = { _ => { diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 6f30da477c..6c4842eabf 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -49,9 +49,12 @@ sub new { $self->{tabpanel}, on_value_change => sub { $self->{plater}->on_config_change(@_) if $self->{plater}; # propagate config change events to the plater - if ($self->{mode} eq 'simple' && $init) { # don't save while loading for the first time - # save config - $self->config->save("$Slic3r::GUI::datadir/simple.ini"); + if ($init) { # don't save while loading for the first time + if ($self->{mode} eq 'simple') { + # save config + $self->config->save("$Slic3r::GUI::datadir/simple.ini"); + } + $self->config->save($Slic3r::GUI::autosave) if $Slic3r::GUI::autosave; } }, on_presets_changed => sub { diff --git a/slic3r.pl b/slic3r.pl index 1d09441db3..e792548ef5 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -26,6 +26,7 @@ my %cli_options = (); 'save=s' => \$opt{save}, 'load=s@' => \$opt{load}, + 'autosave=s' => \$opt{autosave}, 'ignore-nonexistent-config' => \$opt{ignore_nonexistent_config}, 'no-plater' => \$opt{no_plater}, 'gui-mode=s' => \$opt{gui_mode}, @@ -78,6 +79,7 @@ if (!@ARGV && !$opt{save} && eval "require Slic3r::GUI; 1") { $Slic3r::GUI::datadir = $opt{datadir}; $Slic3r::GUI::no_plater = $opt{no_plater}; $Slic3r::GUI::mode = $opt{gui_mode}; + $Slic3r::GUI::autosave = $opt{autosave}; } $gui = Slic3r::GUI->new; $gui->{skeinpanel}->load_config_file($_) for @{$opt{load}}; @@ -152,6 +154,7 @@ $j GUI options: --no-plater Disable the plater tab --gui-mode Overrides the configured mode (simple/expert) + --autosave Automatically export current configuration to the specified file Output options: --output-filename-format From 7134df4638e6fee0ba1ae1f480c9d1c0574a0502 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 11:39:23 +0200 Subject: [PATCH 39/78] Only skip objects with no layers without stopping the whole job --- lib/Slic3r/Print.pm | 7 +++++++ lib/Slic3r/Print/Object.pm | 4 ---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c3fbb7b340..0d5830614a 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -322,6 +322,13 @@ sub export_gcode { $status_cb->(10, "Processing triangulated mesh"); $_->slice for @{$self->objects}; + # remove empty layers and abort if there are no more + # as some algorithms assume all objects have at least one layer + # note: this will change object indexes + @{$self->objects} = grep @{$_->layers}, @{$self->objects}; + die "No layers were detected. You might want to repair your STL file(s) or check their size and retry.\n" + if !@{$self->objects}; + if ($Slic3r::Config->resolution) { $status_cb->(15, "Simplifying input"); $self->_simplify_slices(scale $Slic3r::Config->resolution); diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 6e80673827..dc5428dc94 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -190,7 +190,6 @@ sub slice { # remove last layer(s) if empty pop @{$self->layers} while @{$self->layers} && (!map @{$_->lines}, @{$self->layers->[-1]->regions}); - die "Invalid or too thin input file: no layers could be generated\n" if !@{$self->layers}; foreach my $layer (@{ $self->layers }) { # make sure all layers contain layer region objects for all regions @@ -277,9 +276,6 @@ sub slice { $self->layers->[$i]->id($i); } } - - warn "No layers were detected. You might want to repair your STL file and retry.\n" - if !@{$self->layers}; } sub make_perimeters { From 81bae56e928a075642d598059d9ab326804ef9b8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 12:25:32 +0200 Subject: [PATCH 40/78] Limit only_retract_when_crossing_perimeters to travel moves that are completely enclosed in the upper layer's slices so that we avoid visible traces on top layers. #1091 --- lib/Slic3r/GCode.pm | 4 ++-- lib/Slic3r/Layer.pm | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 4519d933a9..da68552102 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -283,7 +283,7 @@ sub travel_to { $travel->translate(-$self->shift_x, -$self->shift_y); if ($travel->length < scale $self->extruder->retract_before_travel - || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices}) + || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices}) || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) ) { $self->straight_once(0); @@ -326,7 +326,7 @@ sub _plan { my $need_retract = !$self->config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; - foreach my $slice (@{$self->layer->slices}) { + foreach my $slice (@{$self->layer->upper_layer_slices}) { # discard the island if at any line is not enclosed in it next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; # okay, this island encloses the full travel path diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 074baef554..ac6d8d290a 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -54,6 +54,12 @@ sub support_material_contact_z { return $self->print_z - ($self->height - $self->support_material_contact_height) / &Slic3r::SCALING_FACTOR; } +sub upper_layer_slices { + my $self = shift; + + my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return []; + return $upper_layer->slices; + sub region { my $self = shift; my ($region_id) = @_; From 48d5d6de586c7c96ccc2046c84efadbffe552401 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 12:29:45 +0200 Subject: [PATCH 41/78] Typo --- lib/Slic3r/Layer.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index ac6d8d290a..9803ce2b53 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -59,6 +59,7 @@ sub upper_layer_slices { my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return []; return $upper_layer->slices; +} sub region { my $self = shift; From 71608e799ebebf0f9c1efd9174ac9466dda245dc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 15:27:58 +0200 Subject: [PATCH 42/78] Keep per-extruder E absolute value for Makerbot flavour to avoid any G92 E0. #950 --- lib/Slic3r/Extruder.pm | 1 + lib/Slic3r/GCode.pm | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 526ac5048a..925c44fc4f 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -14,6 +14,7 @@ has 'id' => (is => 'rw', required => 1); has $_ => (is => 'ro', required => 1) for @{&OPTIONS}; has 'bridge_flow' => (is => 'lazy'); +has 'e' => (is => 'rw', default => sub {0} ); has 'retracted' => (is => 'rw', default => sub {0} ); has 'restart_extra' => (is => 'rw', default => sub {0} ); has 'e_per_mm3' => (is => 'lazy'); diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index da68552102..409609d483 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -22,7 +22,6 @@ has 'layer_mp' => (is => 'rw'); has 'new_object' => (is => 'rw', default => sub {0}); has 'straight_once' => (is => 'rw', default => sub {1}); has 'extruder' => (is => 'rw'); -has 'extrusion_distance' => (is => 'rw', default => sub {0} ); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds has 'total_extrusion_length' => (is => 'rw', default => sub {0} ); has 'lifted' => (is => 'rw', default => sub {0} ); @@ -418,7 +417,7 @@ sub retract { # reset extrusion distance during retracts # this makes sure we leave sufficient precision in the firmware - $gcode .= $self->reset_e if $self->config->gcode_flavor !~ /^(?:mach3|makerbot)$/; + $gcode .= $self->reset_e; return $gcode; } @@ -447,8 +446,9 @@ sub unretract { sub reset_e { my $self = shift; + return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerbot)$/; - $self->extrusion_distance(0); + $self->extruder->e(0) if $self->extruder; return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') if $self->config->extrusion_axis && !$self->config->use_relative_e_distances; } @@ -545,10 +545,10 @@ sub _Gx { # output extrusion distance if ($e && $self->config->extrusion_axis) { - $self->extrusion_distance(0) if $self->config->use_relative_e_distances; - $self->extrusion_distance($self->extrusion_distance + $e); + $self->extruder->e(0) if $self->config->use_relative_e_distances; + $self->extruder->e($self->extruder->e + $e); $self->total_extrusion_length($self->total_extrusion_length + $e); - $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extrusion_distance; + $gcode .= sprintf " %s%.5f", $self->config->extrusion_axis, $self->extruder->e; } $gcode .= sprintf " ; %s", $comment if $comment && $self->config->gcode_comments; From 974379c95558773e6f5ce3f79a5f4c77a28f962a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 15:37:32 +0200 Subject: [PATCH 43/78] Add M103 after retraction and M101 before restart when Makerbot G-code flavor is selected. #1119 --- lib/Slic3r/GCode.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 409609d483..706ec8d44f 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -419,6 +419,8 @@ sub retract { # this makes sure we leave sufficient precision in the firmware $gcode .= $self->reset_e; + $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerbot'; + return $gcode; } @@ -426,6 +428,7 @@ sub unretract { my $self = shift; my $gcode = ""; + $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerbot'; if ($self->lifted) { $self->speed('travel'); From 167ec7a4e7a99efb10cef9d6834152d9f051a38c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 16:21:22 +0200 Subject: [PATCH 44/78] Use M135 for makerbot toolchange; also disable any G90 and G21 command. #1034 --- lib/Slic3r/GCode.pm | 16 +++++++--------- lib/Slic3r/Print.pm | 4 ++-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 706ec8d44f..7f5ed876fe 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -588,18 +588,16 @@ sub set_extruder { # set the new extruder $self->extruder($extruder); - my $toolchange_gcode = sprintf "%s%d%s\n", - ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M108 T' : 'T'), + $gcode .= sprintf "%s%d%s\n", + ($self->config->gcode_flavor eq 'makerbot' + ? 'M135 T' + : $self->config->gcode_flavor eq 'sailfish' + ? 'M108 T' + : 'T'), $extruder->id, ($self->config->gcode_comments ? ' ; change extruder' : ''); - if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { - $gcode .= $self->reset_e; - $gcode .= $toolchange_gcode; - } else { - $gcode .= $toolchange_gcode; - $gcode .= $self->reset_e; - } + $gcode .= $self->reset_e; return $gcode; } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 0d5830614a..47c2bbadbc 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -711,7 +711,7 @@ sub write_gcode { multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); - print $fh "G21 ; set units to millimeters\n"; + print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; # write start commands to file @@ -729,7 +729,7 @@ sub write_gcode { printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t) if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; } - print $fh "G90 ; use absolute coordinates\n"; + print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup|makerbot|sailfish)$/) { From c95245f143d22409ed617e3f2480b9a6d0a5a422 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 18:01:14 +0200 Subject: [PATCH 45/78] Some G-code flavor logic simplification --- lib/Slic3r/Print.pm | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 47c2bbadbc..87d83b2972 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -732,12 +732,10 @@ sub write_gcode { print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; - if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup|makerbot|sailfish)$/) { - if ($Slic3r::Config->use_relative_e_distances) { - print $fh "M83 ; use relative distances for extrusion\n"; - } else { - print $fh "M82 ; use absolute distances for extrusion\n"; - } + if ($Slic3r::Config->use_relative_e_distances) { + print $fh "M83 ; use relative distances for extrusion\n"; + } else { + print $fh "M82 ; use absolute distances for extrusion\n"; } } From 09b81ad02792802bc6cc2a3396ae8cc817940bb7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 21:27:09 +0200 Subject: [PATCH 46/78] Fix typo causing regression in island ordering. #1211 --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 1e32aff94c..472c2da520 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -247,7 +247,7 @@ sub make_perimeters { )}; my @loops = (); - foreach my $polynode (@$polynodes) { + foreach my $polynode (@nodes) { push @loops, $traverse->($polynode->{children}, $depth+1, $is_contour); my $role = EXTR_ROLE_PERIMETER; From 3ffe98f0c39978057911d908b58c978b993551b8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 21:40:13 +0200 Subject: [PATCH 47/78] Use actual Z to order objects in sequential printing rather than layer count, as layer heights might be different --- lib/Slic3r/Print.pm | 2 +- lib/Slic3r/Print/Object.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 87d83b2972..eb81df845f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -783,7 +783,7 @@ sub write_gcode { # print objects from the smallest to the tallest to avoid collisions # when moving onto next object starting point - my @obj_idx = sort { $self->objects->[$a]->layer_count <=> $self->objects->[$b]->layer_count } 0..$#{$self->objects}; + my @obj_idx = sort { $self->objects->[$a]->size->[Z] <=> $self->objects->[$b]->size->[Z] } 0..$#{$self->objects}; my $finished_objects = 0; for my $obj_idx (@obj_idx) { diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index dc5428dc94..bfc46ce620 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -11,7 +11,7 @@ use Slic3r::Surface ':types'; has 'print' => (is => 'ro', weak_ref => 1, required => 1); has 'input_file' => (is => 'rw', required => 0); has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id -has 'size' => (is => 'rw', required => 1); +has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordinates has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] From 230367b38353766f241863f5e2807f0a61bb8d58 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 21:54:55 +0200 Subject: [PATCH 48/78] Order objects using a nearest neighbor search instead of relying on the order in plater. #1184 --- lib/Slic3r/Print.pm | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index eb81df845f..c2b59a20c6 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -6,7 +6,8 @@ use File::Spec; use List::Util qw(max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points nearest_point); +use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points + nearest_point chained_path); use Slic3r::Geometry::Clipper qw(diff_ex union_ex union_pt intersection_ex offset offset2 traverse_pt JT_ROUND JT_SQUARE PFT_EVENODD); use Time::HiRes qw(gettimeofday tv_interval); @@ -823,18 +824,32 @@ sub write_gcode { } } } else { + # order objects using a nearest neighbor search + my @obj_idx = chained_path([ map $_->copies->[0], @{$self->objects} ]); + + # sort layers by Z + my %layers = (); # print_z => [ layer, layer, layer ] by obj_idx + foreach my $obj_idx (0 .. $#{$self->objects}) { + foreach my $layer (@{$self->objects->[$obj_idx]->layers}) { + $layers{ $layer->print_z } ||= []; + $layers{ $layer->print_z }[$obj_idx] = $layer; # turn this into [$layer] when merging support layers + } + } + my $buffer = Slic3r::GCode::CoolingBuffer->new( config => $Slic3r::Config, gcodegen => $gcodegen, ); - my @layers = sort { $a->print_z <=> $b->print_z } map @{$_->layers}, @{$self->objects}; - foreach my $layer (@layers) { - print $fh $buffer->append( - $layer_gcode->process_layer($layer, $layer->object->copies), - $layer->object."", - $layer->id, - $layer->print_z, - ); + foreach my $print_z (sort { $a <=> $b } keys %layers) { + foreach my $obj_idx (@obj_idx) { + next unless my $layer = $layers{$print_z}[$obj_idx]; + print $fh $buffer->append( + $layer_gcode->process_layer($layer, $layer->object->copies), + $layer->object."", + $layer->id, + $layer->print_z, + ); + } } print $fh $buffer->flush; } From 8b3aa32a7e49994d3e27415fdf4a33975faac857 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 22:40:29 +0200 Subject: [PATCH 49/78] Update tooltip for only_retract_when_crossing_perimeters --- lib/Slic3r/Config.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 41b5fc4f78..22edd0ff63 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -596,7 +596,7 @@ our $Options = { }, 'only_retract_when_crossing_perimeters' => { label => 'Only retract when crossing perimeters', - tooltip => 'Disables retraction when travelling between infill paths inside the same island.', + tooltip => 'Disables retraction when the travel path does not exceed the upper layer\'s perimeters (and thus any ooze will be probably invisible).', cli => 'only-retract-when-crossing-perimeters!', type => 'bool', default => 1, From b3f1795cb4ad82fbf2168dd8faf6c00bd09d414e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 3 Jun 2013 22:49:47 +0200 Subject: [PATCH 50/78] Reduce wipe feedrate a bit --- lib/Slic3r/Extruder.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 925c44fc4f..91c6314cb2 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -39,7 +39,9 @@ sub _build_retract_speed_mm_min { sub _build_scaled_wipe_distance { my $self = shift; - return scale $self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed; + # reduce feedrate a bit; travel speed is often too high to move on existing material + # too fast = ripping of existing material; too slow = short wipe path, thus more blob + return scale($self->retract_length / $self->retract_speed * $Slic3r::Config->travel_speed * 0.8); } sub make_flow { From 532ae53d0b87d05d4114848ee1a7d3b419e1df9e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 5 Jun 2013 16:58:30 +0200 Subject: [PATCH 51/78] Use G1 instead of G0 for restart after retraction to avoid blending with the previous travel move. #1212 --- lib/Slic3r/GCode.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 7f5ed876fe..b4a8d6b2f5 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -439,7 +439,8 @@ sub unretract { my $to_unretract = $self->extruder->retracted + $self->extruder->restart_extra; if ($to_unretract) { $self->speed('retract'); - $gcode .= $self->G0(undef, undef, $to_unretract, "compensate retraction"); + # use G1 instead of G0 because G0 will blend the restart with the previous travel move + $gcode .= $self->G1(undef, undef, $to_unretract, "compensate retraction"); $self->extruder->retracted(0); $self->extruder->restart_extra(0); } From 8c40cefe1ed8dbaecafc02282ba40326d9171dbc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 5 Jun 2013 17:12:34 +0200 Subject: [PATCH 52/78] Also use G1 when traveling with avoid_crossing_perimeters enabled --- lib/Slic3r/GCode.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b4a8d6b2f5..b7f855e83d 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -339,7 +339,8 @@ sub _plan { # append the actual path and return $self->speed('travel'); - $gcode .= join '', map $self->G0($_->[B], undef, 0, $comment || ""), @travel; + # use G1 because we rely on paths being straight (G0 may make round paths) + $gcode .= join '', map $self->G1($_->[B], undef, 0, $comment || ""), @travel; return $gcode; } From 43814e99f6c8bfc1a2d8cc0eae4a44d01a08b2af Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 6 Jun 2013 10:46:58 +0200 Subject: [PATCH 53/78] Remove smart match and lexical $_ for compatibility with perl 5.18. #1216 --- lib/Slic3r/Config.pm | 4 ++-- lib/Slic3r/Format/OBJ.pm | 2 +- lib/Slic3r/Format/STL.pm | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 22edd0ff63..8b9fb1ca44 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -1141,7 +1141,7 @@ sub set { my ($opt_key, $value, $deserialize) = @_; # handle legacy options - return if $opt_key ~~ @Ignore; + return if first { $_ eq $opt_key } @Ignore; if ($opt_key =~ /^(extrusion_width|bottom_layer_speed|first_layer_height)_ratio$/) { $opt_key = $1; $opt_key =~ s/^bottom_layer_speed$/first_layer_speed/; @@ -1413,7 +1413,7 @@ sub read_ini { my $ini = { _ => {} }; my $category = '_'; - while (my $_ = <$fh>) { + while (<$fh>) { s/\R+$//; next if /^\s+/; next if /^$/; diff --git a/lib/Slic3r/Format/OBJ.pm b/lib/Slic3r/Format/OBJ.pm index c5cc085558..05a141db1d 100644 --- a/lib/Slic3r/Format/OBJ.pm +++ b/lib/Slic3r/Format/OBJ.pm @@ -8,7 +8,7 @@ sub read_file { Slic3r::open(\my $fh, '<', $file) or die "Failed to open $file\n"; my $vertices = []; my $facets = []; - while (my $_ = <$fh>) { + while (<$fh>) { if (/^v ([^ ]+)\s+([^ ]+)\s+([^ ]+)/) { push @$vertices, [$1, $2, $3]; } elsif (/^f (\d+).*? (\d+).*? (\d+).*?/) { diff --git a/lib/Slic3r/Format/STL.pm b/lib/Slic3r/Format/STL.pm index 0d0331c856..cf07b1cad2 100644 --- a/lib/Slic3r/Format/STL.pm +++ b/lib/Slic3r/Format/STL.pm @@ -53,7 +53,7 @@ sub _read_ascii { my $facet; my %vertices_map = (); seek $fh, 0, 0; - while (my $_ = <$fh>) { + while (<$fh>) { if (!$facet) { /^\s*facet\s+normal\s+/ or next; $facet = []; # ignore normal @@ -88,7 +88,7 @@ sub _read_binary { my %vertices_map = (); binmode $fh; seek $fh, 80 + 4, 0; - while (read $fh, my $_, 4*4*3+2) { + while (read $fh, $_, 4*4*3+2) { push @$facets, my $facet = []; for (unpack 'x[f3](a[f3])3') { # ignore normal my $vertex_idx; From fcc442882c8a372ec6780ea068f9fa910857d80f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 6 Jun 2013 20:53:56 +0200 Subject: [PATCH 54/78] Fix plater defect causing misalignment --- 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 0dbca23b0e..286a524e2e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -717,6 +717,7 @@ sub make_model { $model->set_material($volume->material_id || 0, {}); } $new_model_object->scale($plater_object->scale); + $new_model_object->align_to_origin; $new_model_object->add_instance( rotation => $plater_object->rotate, # around center point offset => Slic3r::Point->new($_), From f991e3bc106ec1402bd68c7ad555ff2f65dffbe1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 12:00:03 +0200 Subject: [PATCH 55/78] Fixed SVG export and added regression test --- MANIFEST | 1 + lib/Slic3r/Print.pm | 14 ++++++++------ t/svg.t | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 t/svg.t diff --git a/MANIFEST b/MANIFEST index c319935b98..36389aef1f 100644 --- a/MANIFEST +++ b/MANIFEST @@ -81,6 +81,7 @@ t/shells.t t/slice.t t/skirt_brim.t t/support.t +t/svg.t t/vibrationlimit.t utils/amf-to-stl.pl utils/file_info.pl diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c2b59a20c6..d177743189 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -488,13 +488,15 @@ sub export_svg { $self->init_extruders; $_->slice for @{$self->objects}; - $self->arrange_objects; - my $output_file = $self->expanded_output_filepath($params{output_file}); - $output_file =~ s/\.gcode$/.svg/i; + my $fh = $params{output_fh}; + if ($params{output_file}) { + my $output_file = $self->expanded_output_filepath($params{output_file}); + $output_file =~ s/\.gcode$/.svg/i; + Slic3r::open(\$fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; + print "Exporting to $output_file..." unless $params{quiet}; + } - Slic3r::open(\my $fh, ">", $output_file) or die "Failed to open $output_file for writing\n"; - print "Exporting to $output_file..."; my $print_size = $self->size; print $fh sprintf <<"EOF", unscale($print_size->[X]), unscale($print_size->[Y]); @@ -563,7 +565,7 @@ EOF print $fh "\n"; close $fh; - print "Done.\n"; + print "Done.\n" unless $params{quiet}; } sub make_skirt { diff --git a/t/svg.t b/t/svg.t new file mode 100644 index 0000000000..9e2a8dcb19 --- /dev/null +++ b/t/svg.t @@ -0,0 +1,23 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use Slic3r::Test; + +{ + my $print = Slic3r::Test::init_print('20mm_cube'); + eval { + my $fh = IO::Scalar->new(\my $gcode); + $print->export_svg(output_fh => $fh, quiet => 1); + $fh->close; + }; + ok !$@, 'successful SVG export'; +} + +__END__ From 7a8e1e778a1871bd031aa27e5328ca727d5254c6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 13:38:29 +0200 Subject: [PATCH 56/78] New test to ensure brim and raft are extruded with the support material extruder. #123 --- t/support.t | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/t/support.t b/t/support.t index 1e9a24a6e3..bee1987f1b 100644 --- a/t/support.t +++ b/t/support.t @@ -14,8 +14,24 @@ use Slic3r::Test; my $config = Slic3r::Config->new_from_defaults; $config->set('raft_layers', 3); $config->set('brim_width', 6); + $config->set('skirts', 0); + $config->set('support_material_extruder', 2); + $config->set('layer_height', 0.4); + $config->set('first_layer_height', '100%'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - ok Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; + ok my $gcode = Slic3r::Test::gcode($print), 'no conflict between raft/support and brim'; + + my $tool = 0; + Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd =~ /^T(\d+)/) { + $tool = $1; + } elsif ($info->{extruding} && $self->Z <= ($config->raft_layers * $config->layer_height)) { + fail 'not extruding raft/brim with support material extruder' + if $tool != ($config->support_material_extruder-1); + } + }); } __END__ From 9ea55497c255930b92984e1457ea9e1caa8f3a65 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 13:54:40 +0200 Subject: [PATCH 57/78] Prevent crash when user deleted object from plater before thumbnail was generated in the other thread. #1207 --- lib/Slic3r/GUI.pm | 1 + lib/Slic3r/GUI/Plater.pm | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index fd46cc5ebb..00e56f4faa 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -253,6 +253,7 @@ sub warning_catcher { my ($self, $message_dialog) = @_; return sub { my $message = shift; + return if $message =~ /GLUquadricObjPtr|Attempt to free unreferenced scalar/; my @params = ($message, 'Warning', wxOK | wxICON_WARNING); $message_dialog ? $message_dialog->(@params) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 286a524e2e..dc17bdcf89 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -159,6 +159,7 @@ sub new { EVT_COMMAND($self, -1, $THUMBNAIL_DONE_EVENT, sub { my ($self, $event) = @_; my ($obj_idx, $thumbnail) = @{$event->GetData}; + return if !$self->{objects}[$obj_idx]; # object was deleted before thumbnail generation completed $self->{objects}[$obj_idx]->thumbnail($thumbnail->clone); $self->on_thumbnail_made($obj_idx); }); From 510c2092dff31a17201e4de20d160a0b643a8e32 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 23:16:02 +0200 Subject: [PATCH 58/78] Fix rotation and scaling in plater producing mispositioned objects in G-code after recent changes. Includes a large refactoring and the new Slic3r::Geometry::BoundingBox class. #1171 #1191 --- MANIFEST | 1 + lib/Slic3r.pm | 2 + lib/Slic3r/ExPolygon.pm | 14 +++++ lib/Slic3r/GUI/Plater.pm | 74 ++++++++++++++----------- lib/Slic3r/GUI/Plater/ObjectDialog.pm | 2 +- lib/Slic3r/Geometry/BoundingBox.pm | 78 +++++++++++++++++++++++++++ lib/Slic3r/Point.pm | 7 +++ lib/Slic3r/TriangleMesh.pm | 5 ++ 8 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 lib/Slic3r/Geometry/BoundingBox.pm diff --git a/MANIFEST b/MANIFEST index 36389aef1f..b2fe05edab 100644 --- a/MANIFEST +++ b/MANIFEST @@ -30,6 +30,7 @@ lib/Slic3r/GCode/MotionPlanner.pm lib/Slic3r/GCode/Reader.pm lib/Slic3r/GCode/SpiralVase.pm lib/Slic3r/Geometry.pm +lib/Slic3r/Geometry/BoundingBox.pm lib/Slic3r/Geometry/Clipper.pm lib/Slic3r/GUI.pm lib/Slic3r/GUI/AboutDialog.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1338ce0669..67314e2170 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -51,6 +51,8 @@ use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::Reader; use Slic3r::GCode::SpiralVase; use Slic3r::Geometry qw(PI); +use Slic3r::Geometry::BoundingBox; +use Slic3r::Geometry::Clipper; use Slic3r::Layer; use Slic3r::Layer::Region; use Slic3r::Line; diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 45b59acec8..5190357f36 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -335,11 +335,25 @@ sub align_to_origin { my @bb = Slic3r::Geometry::bounding_box([ map @$_, map @$_, @{$self->expolygons} ]); $_->translate(-$bb[X1], -$bb[Y1]) for @{$self->expolygons}; + $self; +} + +sub scale { + my $self = shift; + $_->scale(@_) for @{$self->expolygons}; + $self; } sub rotate { my $self = shift; $_->rotate(@_) for @{$self->expolygons}; + $self; +} + +sub translate { + my $self = shift; + $_->translate(@_) for @{$self->expolygons}; + $self; } sub size { diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index dc17bdcf89..7b5dde5bb6 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -452,7 +452,7 @@ sub arrange { my $total_parts = sum(map $_->instances_count, @{$self->{objects}}) or return; my @size = (); for my $a (X,Y) { - $size[$a] = max(map $_->size->[$a], @{$self->{objects}}); + $size[$a] = max(map $_->transformed_size->[$a], @{$self->{objects}}); } eval { @@ -769,10 +769,9 @@ sub recenter { my @print_bb = Slic3r::Geometry::bounding_box([ map { my $obj = $_; - map { - my $instance = $_; - $instance, [ map $instance->[$_] + $obj->size->[$_], X,Y ]; - } @{$obj->instances}; + my $bb = $obj->transformed_bounding_box; + my @points = ($bb->min_point, $bb->max_point); + map Slic3r::Geometry::move_points($_, @points), @{$obj->instances}; } @{$self->{objects}}, ]); $self->{shift} = [ @@ -867,9 +866,12 @@ sub repaint { next unless $object->thumbnail && @{$object->thumbnail->expolygons}; for my $instance_idx (0 .. $#{$object->instances}) { my $instance = $object->instances->[$instance_idx]; - push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $object->thumbnail->clone ]; - $_->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y)) - for @{$parent->{object_previews}->[-1][2]->expolygons}; + + my $thumbnail = $object->thumbnail + ->clone + ->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y)); + + push @{$parent->{object_previews}}, [ $obj_idx, $instance_idx, $thumbnail ]; my $drag_object = $self->{drag_object}; if (defined $drag_object && $obj_idx == $drag_object->[0] && $instance_idx == $drag_object->[1]) { @@ -1065,15 +1067,15 @@ package Slic3r::GUI::Plater::Object; use Moo; use Math::ConvexHull::MonotoneChain qw(convex_hull); -use Slic3r::Geometry qw(X Y Z); +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 'size' => (is => 'rw'); +has 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling has 'scale' => (is => 'rw', default => sub { 1 }); -has 'rotate' => (is => 'rw', default => sub { 0 }); +has 'rotate' => (is => 'rw', default => sub { 0 }); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis has 'thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw'); @@ -1088,8 +1090,9 @@ has 'is_manifold' => (is => 'rw'); sub _trigger_model_object { my $self = shift; if ($self->model_object) { + $self->model_object->align_to_origin; my $mesh = $self->model_object->mesh; - $self->size([$mesh->size]); + $self->bounding_box($mesh->bounding_box); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); $self->materials($self->model_object->materials_count); @@ -1131,22 +1134,22 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; + $mesh->align_to_origin; my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection : [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ], ); - for (map @$_, map @$_, @{$thumbnail->expolygons}) { - @$_ = map $_ * $self->thumbnail_scaling_factor, @$_; - } + $thumbnail->scale($self->thumbnail_scaling_factor); + # only simplify expolygons larger than the threshold - @{$thumbnail->expolygons} = map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } @{$thumbnail->expolygons}; - foreach my $expolygon (@{$thumbnail->expolygons}) { - $expolygon->rotate(Slic3r::Geometry::deg2rad($self->rotate)); - $expolygon->scale($self->scale); - } - @{$thumbnail->expolygons} = grep @$_, @{$thumbnail->expolygons}; - $thumbnail->align_to_origin; + @{$thumbnail->expolygons} = grep @$_, + map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } + @{$thumbnail->expolygons}; + + $thumbnail->rotate(deg2rad($self->rotate)); # TODO: around center + $thumbnail->scale($self->scale); + $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object; @@ -1158,10 +1161,8 @@ sub set_rotation { my ($angle) = @_; if ($self->thumbnail) { - $self->thumbnail->rotate(Slic3r::Geometry::deg2rad($angle - $self->rotate)); - $self->thumbnail->align_to_origin; - my $z_size = $self->size->[Z]; - $self->size([ (map $_ / $self->thumbnail_scaling_factor, @{$self->thumbnail->size}), $z_size ]); + # rotate around object centerpoint + $self->thumbnail->rotate(deg2rad($angle - $self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)); } $self->rotate($angle); } @@ -1170,14 +1171,25 @@ sub set_scale { my $self = shift; my ($scale) = @_; - my $factor = $scale / $self->scale; - return if $factor == 1; - $self->size->[$_] *= $factor for X,Y,Z; if ($self->thumbnail) { - $_->scale($factor) for @{$self->thumbnail->expolygons}; - $self->thumbnail->align_to_origin; + $self->thumbnail->scale($scale / $self->scale); } $self->scale($scale); } +# bounding box with applied rotation and scaling +sub transformed_bounding_box { + my $self = shift; + + return $self->bounding_box + ->clone + ->rotate(deg2rad($self->rotate), $self->bounding_box->center) + ->scale($self->scale); +} + +sub transformed_size { + my $self = shift; + return $self->transformed_bounding_box->size; +} + 1; diff --git a/lib/Slic3r/GUI/Plater/ObjectDialog.pm b/lib/Slic3r/GUI/Plater/ObjectDialog.pm index 3cb725e649..4fc8388412 100644 --- a/lib/Slic3r/GUI/Plater/ObjectDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectDialog.pm @@ -78,7 +78,7 @@ sub get_properties { my $object = $self->{object}; return [ ['Name' => $object->name], - ['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->size}], + ['Size' => sprintf "%.2f x %.2f x %.2f", @{$object->transformed_size}], ['Facets' => $object->facets], ['Vertices' => $object->vertices], ['Materials' => $object->materials], diff --git a/lib/Slic3r/Geometry/BoundingBox.pm b/lib/Slic3r/Geometry/BoundingBox.pm new file mode 100644 index 0000000000..cbcec824c1 --- /dev/null +++ b/lib/Slic3r/Geometry/BoundingBox.pm @@ -0,0 +1,78 @@ +package Slic3r::Geometry::BoundingBox; +use Moo; +use Slic3r::Geometry qw(X Y Z MIN MAX X1 Y1 X2 Y2); +use Storable qw(); + +has 'extents' => (is => 'ro', required => 1); + +sub clone { Storable::dclone($_[0]) } + +# four-arguments 2D bb +sub bb { + my $self = shift; + my $extents = $self->extents; + return [ $extents->[X][MIN], $extents->[Y][MIN], $extents->[X][MAX], $extents->[Y][MAX] ]; +} + +sub polygon { + my $self = shift; + return Slic3r::Polygon->new_from_bounding_box($self->bb); +} + +sub rotate { + my $self = shift; + my ($angle, $center) = @_; + + # rotate the 2D bounding box polygon and leave Z unaltered + my $bb_p = $self->polygon; + $bb_p->rotate($angle, $center); + my @bb = $bb_p->bounding_box; + $self->extents->[X][MIN] = $bb[X1]; + $self->extents->[Y][MIN] = $bb[Y1]; + $self->extents->[X][MAX] = $bb[X2]; + $self->extents->[Y][MAX] = $bb[Y2]; + + $self; +} + +sub scale { + my $self = shift; + my ($factor) = @_; + + $_ *= $factor + for map @$_[MIN,MAX], + grep $_, @{$self->extents}[X,Y,Z]; + + $self; +} + +sub size { + my $self = shift; + + my $extents = $self->extents; + return [ map $extents->[$_][MAX] - $extents->[$_][MIN], grep $extents->[$_], (X,Y,Z) ]; +} + +sub center { + my $self = shift; + + my $extents = $self->extents; + return [ map +($extents->[$_][MAX] + $extents->[$_][MIN])/2, grep $extents->[$_], (X,Y,Z) ]; +} + +sub center_2D { + my $self = shift; + return Slic3r::Point->new(@{$self->center}[X,Y]); +} + +sub min_point { + my $self = shift; + return Slic3r::Point->new($self->extents->[X][MIN], $self->extents->[Y][MIN]); +} + +sub max_point { + my $self = shift; + return Slic3r::Point->new($self->extents->[X][MAX], $self->extents->[Y][MAX]); +} + +1; diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm index 26e79045af..b820a1e83f 100644 --- a/lib/Slic3r/Point.pm +++ b/lib/Slic3r/Point.pm @@ -35,6 +35,13 @@ sub distance_to { return Slic3r::Geometry::distance_between_points($self, $point); } +sub scale { + my $self = shift; + my ($factor) = @_; + $_ *= $factor for @$self; + $self; +} + sub rotate { my $self = shift; my ($angle, $center) = @_; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 3506d893e8..a58f3c0e60 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -408,6 +408,11 @@ sub extents { return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } +sub bounding_box { + my $self = shift; + return Slic3r::Geometry::BoundingBox->new(extents => [ $self->extents ]); +} + sub size { my $self = shift; return Slic3r::Geometry::size_3D($self->used_vertices); From b12a09ed71b5264e5e0123ff0e38b12df07a7657 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Jun 2013 23:24:53 +0200 Subject: [PATCH 59/78] Rename makerbot G-code flavor to makerware; also mention Repetier among RepRap firmwares. #1034 --- README.markdown | 4 ++-- lib/Slic3r/Config.pm | 11 +++++++++-- lib/Slic3r/GCode.pm | 20 ++++++++++---------- lib/Slic3r/Print.pm | 4 ++-- slic3r.pl | 2 +- utils/zsh/functions/_slic3r | 2 +- 6 files changed, 25 insertions(+), 18 deletions(-) diff --git a/README.markdown b/README.markdown index 0a4e2b95df..4fe41b8790 100644 --- a/README.markdown +++ b/README.markdown @@ -7,7 +7,7 @@ A: Yes. ## What's it? Slic3r is a G-code generator for 3D printers. It's compatible with RepRaps, -Makerbots, Ultimakers and many more machines. +makerwares, Ultimakers and many more machines. See the [project homepage](http://slic3r.org/) at slic3r.org and the [documentation](https://github.com/alexrj/Slic3r/wiki/Documentation) on the Slic3r wiki for more information. @@ -112,7 +112,7 @@ The author of the Silk icon set is Mark James. (default: 100,100) --z-offset Additional height in mm to add to vertical coordinates (+/-, default: 0) - --gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/sailfish/mach3/no-extrusion, + --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion, default: reprap) --use-relative-e-distances Enable this to get relative E values --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 22edd0ff63..d83cc4c5f7 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -74,8 +74,8 @@ our $Options = { tooltip => 'Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer\'s firmware to get a compatible output. The "No extrusion" flavor prevents Slic3r from exporting any extrusion value at all.', cli => 'gcode-flavor=s', type => 'select', - values => [qw(reprap teacup makerbot sailfish mach3 no-extrusion)], - labels => ['RepRap (Marlin/Sprinter)', 'Teacup', 'MakerBot', 'Sailfish', 'Mach3/EMC', 'No extrusion'], + values => [qw(reprap teacup makerware sailfish mach3 no-extrusion)], + labels => ['RepRap (Marlin/Sprinter/Repetier)', 'Teacup', 'MakerWare (MakerBot)', 'Sailfish (MakerBot)', 'Mach3/EMC', 'No extrusion'], default => 'reprap', }, 'use_relative_e_distances' => { @@ -1150,6 +1150,9 @@ sub set { if ($opt_key eq 'threads' && !$Slic3r::have_threads) { $value = 1; } + if ($opt_key eq 'gcode_flavor' && $value eq 'makerbot') { + $value = 'makerware'; + } # For historical reasons, the world's full of configs having these very low values; # to avoid unexpected behavior we need to ignore them. Banning these two hard-coded @@ -1271,6 +1274,10 @@ sub validate { die "Invalid value for --top-solid-layers\n" if $self->top_solid_layers < 0; die "Invalid value for --bottom-solid-layers\n" if $self->bottom_solid_layers < 0; + # --gcode-flavor + die "Invalid value for --gcode-flavor\n" + if !first { $_ eq $self->gcode_flavor } @{$Options->{gcode_flavor}{values}}; + # --print-center die "Invalid value for --print-center\n" if !ref $self->print_center diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b7f855e83d..3b05deb960 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -88,7 +88,7 @@ sub change_layer { } my $gcode = ""; - if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { $gcode .= sprintf "M73 P%s%s\n", int(99 * ($layer->id / ($self->layer_count - 1))), ($self->config->gcode_comments ? ' ; update progress' : ''); @@ -420,7 +420,7 @@ sub retract { # this makes sure we leave sufficient precision in the firmware $gcode .= $self->reset_e; - $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerbot'; + $gcode .= "M103 ; extruder off\n" if $self->config->gcode_flavor eq 'makerware'; return $gcode; } @@ -429,7 +429,7 @@ sub unretract { my $self = shift; my $gcode = ""; - $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerbot'; + $gcode .= "M101 ; extruder on\n" if $self->config->gcode_flavor eq 'makerware'; if ($self->lifted) { $self->speed('travel'); @@ -451,7 +451,7 @@ sub unretract { sub reset_e { my $self = shift; - return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerbot)$/; + return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware)$/; $self->extruder->e(0) if $self->extruder; return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') @@ -591,7 +591,7 @@ sub set_extruder { # set the new extruder $self->extruder($extruder); $gcode .= sprintf "%s%d%s\n", - ($self->config->gcode_flavor eq 'makerbot' + ($self->config->gcode_flavor eq 'makerware' ? 'M135 T' : $self->config->gcode_flavor eq 'sailfish' ? 'M108 T' @@ -613,12 +613,12 @@ sub set_fan { if ($speed == 0) { my $code = $self->config->gcode_flavor eq 'teacup' ? 'M106 S0' - : $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ + : $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M127' : 'M107'; return sprintf "$code%s\n", ($self->config->gcode_comments ? ' ; disable fan' : ''); } else { - if ($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) { + if ($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/) { return sprintf "M126%s\n", ($self->config->gcode_comments ? ' ; enable fan' : ''); } else { return sprintf "M106 %s%d%s\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), @@ -633,14 +633,14 @@ sub set_temperature { my $self = shift; my ($temperature, $wait, $tool) = @_; - return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/; + return "" if $wait && $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/; my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') ? ('M109', 'wait for temperature to be reached') : ('M104', 'set temperature'); my $gcode = sprintf "$code %s%d %s; $comment\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature, - (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/)) ? "T$tool " : ""; + (defined $tool && ($self->multiple_extruders || $self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/)) ? "T$tool " : ""; $gcode .= "M116 ; wait for temperature to be reached\n" if $self->config->gcode_flavor eq 'teacup' && $wait; @@ -653,7 +653,7 @@ sub set_bed_temperature { my ($temperature, $wait) = @_; my ($code, $comment) = ($wait && $self->config->gcode_flavor ne 'teacup') - ? (($self->config->gcode_flavor =~ /^(?:makerbot|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') + ? (($self->config->gcode_flavor =~ /^(?:makerware|sailfish)$/ ? 'M109' : 'M190'), 'wait for bed temperature to be reached') : ('M140', 'set bed temperature'); my $gcode = sprintf "$code %s%d ; $comment\n", ($self->config->gcode_flavor eq 'mach3' ? 'P' : 'S'), $temperature; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index d177743189..6464ba90eb 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -714,7 +714,7 @@ sub write_gcode { multiple_extruders => (@{$self->extruders} > 1), layer_count => $self->layer_count, ); - print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; + print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; # write start commands to file @@ -732,7 +732,7 @@ sub write_gcode { printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t) if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; } - print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerbot'; + print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; if ($Slic3r::Config->use_relative_e_distances) { diff --git a/slic3r.pl b/slic3r.pl index e792548ef5..0b05cd0436 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -173,7 +173,7 @@ $j (default: $config->{print_center}->[0],$config->{print_center}->[1]) --z-offset Additional height in mm to add to vertical coordinates (+/-, default: $config->{z_offset}) - --gcode-flavor The type of G-code to generate (reprap/teacup/makerbot/sailfish/mach3/no-extrusion, + --gcode-flavor The type of G-code to generate (reprap/teacup/makerware/sailfish/mach3/no-extrusion, default: $config->{gcode_flavor}) --use-relative-e-distances Enable this to get relative E values --gcode-arcs Use G2/G3 commands for native arcs (experimental, not supported diff --git a/utils/zsh/functions/_slic3r b/utils/zsh/functions/_slic3r index 41b2594bd8..10a0ce05b0 100644 --- a/utils/zsh/functions/_slic3r +++ b/utils/zsh/functions/_slic3r @@ -22,7 +22,7 @@ _arguments -S \ '*--nozzle-diameter[specify nozzle diameter]:nozzle diameter in mm' \ '--print-center[specify print center coordinates]:print center coordinates in mm,mm' \ '--z-offset[specify Z-axis offset]:Z-axis offset in mm' \ - '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerbot sailfish mach3 no-extrusion)' \ + '--gcode-flavor[specify the type of G-code to generate]:G-code flavor:(reprap teacup makerware sailfish mach3 no-extrusion)' \ '(--use-relative-e-distances --no-use-relative-e-distances)'--{no-,}use-relative-e-distances'[disable/enable relative E values]' \ '--extrusion-axis[specify letter associated with the extrusion axis]:extrusion axis letter' \ '(--gcode-arcs --no-gcode-arcs)'--{no-,}gcode-arcs'[disable/enable G2/G3 commands for native arcs]' \ From d5a9320587ebc77f813aee0be4901c611d2adb70 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 8 Jun 2013 17:48:34 +0200 Subject: [PATCH 60/78] Bugfix: narrow top surfaces didn't generate solid layers. #1161 --- lib/Slic3r/Print/Object.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index bfc46ce620..9f9f435393 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -383,7 +383,6 @@ sub detect_surfaces_type { 1, ); return map Slic3r::Surface->new(expolygon => $_, surface_type => $result_type), - grep $_->is_printable($layerm->perimeter_flow->scaled_width), @$expolygons; }; From 8b2c13cc6f225915bf6df837859b0685979f9e62 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 8 Jun 2013 20:01:26 +0200 Subject: [PATCH 61/78] Regression test for top solid surfaces in V-shaped object. #1161 --- lib/Slic3r/Test.pm | 7 +++++++ t/shells.t | 25 ++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 46443cc6af..1a0f0e2d44 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -27,6 +27,13 @@ sub model { $facets = [ [0,1,2], [0,2,3], [4,5,6], [4,6,7], [0,4,7], [0,7,1], [1,7,6], [1,6,2], [2,6,5], [2,5,3], [4,0,3], [4,3,5], ], + } elsif ($model_name eq 'V') { + $vertices = [ + [-14,0,20],[-14,15,20],[0,0,0],[0,15,0],[-4,0,20],[-4,15,20],[5,0,7.14286],[10,0,0],[24,0,20],[14,0,20],[10,15,0],[5,15,7.14286],[14,15,20],[24,15,20] + ]; + $facets = [ + [0,1,2],[2,1,3],[1,0,4],[5,1,4],[4,0,2],[6,4,2],[7,6,2],[8,9,7],[9,6,7],[2,3,7],[7,3,10],[1,5,3],[3,5,11],[11,12,13],[11,13,3],[3,13,10],[5,4,6],[11,5,6],[6,9,11],[11,9,12],[12,9,8],[13,12,8],[8,7,10],[13,8,10] + ], } my $model = Slic3r::Model->new; diff --git a/t/shells.t b/t/shells.t index 7977f491e9..56a3ca5950 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 2; +use Test::More tests => 3; use strict; use warnings; @@ -45,4 +45,27 @@ use Slic3r::Test; ok $test->(), "proper number of shells is applied even when fill density is none"; } +# issue #1161 +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('layer_height', 0.3); + $config->set('first_layer_height', '100%'); + $config->set('bottom_solid_layers', 0); + $config->set('top_solid_layers', 3); + $config->set('cooling', 0); + $config->set('solid_infill_speed', 99); + $config->set('top_solid_infill_speed', 99); + + my $print = Slic3r::Test::init_print('V', config => $config); + my %layers_with_solid_infill = (); # Z => 1 + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + $layers_with_solid_infill{$self->Z} = 1 + if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; + }); + is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3, + "correct number of top solid shells is generated in V-shaped object"; +} + __END__ From 6ae766600657cac98bf3b129fefd28ea33c8cda1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 8 Jun 2013 20:02:21 +0200 Subject: [PATCH 62/78] New utility script to dump STL contents in Perl syntax for writing tests --- utils/dump-stl.pl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 utils/dump-stl.pl diff --git a/utils/dump-stl.pl b/utils/dump-stl.pl new file mode 100644 index 0000000000..a5c716b839 --- /dev/null +++ b/utils/dump-stl.pl @@ -0,0 +1,34 @@ +#!/usr/bin/perl +# This script dumps a STL file into Perl syntax for writing tests + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +$|++; + +$ARGV[0] or usage(1); + +{ + my $model = Slic3r::Format::STL->read_file($ARGV[0]); + my $mesh = $model->mesh; + printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; + printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; +} + + +sub usage { + my ($exit_code) = @_; + + print <<"EOF"; +Usage: dump-stl.pl file.stl +EOF + exit ($exit_code || 0); +} + +__END__ From 8e0e03247d72da806c919db6f1b1505c528f793e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 10 Jun 2013 15:34:45 +0200 Subject: [PATCH 63/78] Bugfix: ineffective scale() method in recently created BoundingBox objects caused wrong positioning for scaled objects in plater. Includes regression test. #1171 --- lib/Slic3r/Geometry/BoundingBox.pm | 26 +++++++++++++++++++++++--- t/geometry.t | 12 ++++++++++-- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Geometry/BoundingBox.pm b/lib/Slic3r/Geometry/BoundingBox.pm index cbcec824c1..3cd019f0cb 100644 --- a/lib/Slic3r/Geometry/BoundingBox.pm +++ b/lib/Slic3r/Geometry/BoundingBox.pm @@ -7,6 +7,26 @@ has 'extents' => (is => 'ro', required => 1); sub clone { Storable::dclone($_[0]) } +# 2D +sub new_from_points { + my $class = shift; + my ($points) = @_; + + my $bb = [ Slic3r::Geometry::bounding_box($points) ]; + return $class->new(extents => [ + [ $bb->[X1], $bb->[X2] ], + [ $bb->[Y1], $bb->[Y2] ], + ]); +} + +# 3D +sub new_from_points_3D { + my $class = shift; + my ($points) = @_; + + return $class->new(extents => [ Slic3r::Geometry::bounding_box_3D($points) ]); +} + # four-arguments 2D bb sub bb { my $self = shift; @@ -39,9 +59,9 @@ sub scale { my $self = shift; my ($factor) = @_; - $_ *= $factor - for map @$_[MIN,MAX], - grep $_, @{$self->extents}[X,Y,Z]; + for (@{$self->extents}) { + $_ *= $factor for @$_[MIN,MAX]; + } $self; } diff --git a/t/geometry.t b/t/geometry.t index c0e1f753ba..edbe7262a3 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 23; +plan tests => 24; BEGIN { use FindBin; @@ -173,4 +173,12 @@ is Slic3r::Geometry::can_connect_points(@$points, $polygons), 0, 'can_connect_po is_deeply $result, [ [10, 0], [5, 5], [0, 0], [10, 0] ], 'split_at_index'; } -#========================================================== \ No newline at end of file +#========================================================== + +{ + my $bb = Slic3r::Geometry::BoundingBox->new_from_points([ [0, 1], [10, 2], [20, 2] ]); + $bb->scale(2); + is_deeply $bb->extents, [ [0,40], [2,4] ], 'bounding box is scaled correctly'; +} + +#========================================================== From aee7b70d79fdc6bad638c282aab010517db42872 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 12 Jun 2013 16:53:19 +0200 Subject: [PATCH 64/78] Bugfix: the order of transformations was not consistent in plater and actual G-code generation. #1171 #1191 --- lib/Slic3r/GUI/Plater.pm | 62 +++++++++++++++++++++++----------------- lib/Slic3r/Print.pm | 3 ++ 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 7b5dde5bb6..460f10edb3 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -326,7 +326,7 @@ sub load_file { $object->check_manifoldness; # we only consider the rotation of the first instance for now - $object->set_rotation($model->objects->[$i]->instances->[0]->rotation) + $object->rotate($model->objects->[$i]->instances->[0]->rotation) if $model->objects->[$i]->instances; push @{ $self->{objects} }, $object; @@ -427,7 +427,7 @@ sub rotate { return if !$angle || $angle == -1; } - $object->set_rotation($object->rotate + $angle); + $object->rotate($object->rotate + $angle); $self->recenter; $self->{canvas}->Refresh; } @@ -437,12 +437,15 @@ sub changescale { my ($obj_idx, $object) = $self->selected_object; + # we need thumbnail to be computed before allowing scaling + return if !$object->thumbnail; + # max scale factor should be above 2540 to allow importing files exported in inches my $scale = Wx::GetNumberFromUser("", "Enter the scale % for the selected object:", "Scale", $object->scale*100, 0, 100000, $self); return if !$scale || $scale == -1; $self->{list}->SetItem($obj_idx, 2, "$scale%"); - $object->set_scale($scale / 100); + $object->scale($scale / 100); $self->arrange; } @@ -867,7 +870,7 @@ sub repaint { for my $instance_idx (0 .. $#{$object->instances}) { my $instance = $object->instances->[$instance_idx]; - my $thumbnail = $object->thumbnail + my $thumbnail = $object->transformed_thumbnail ->clone ->translate(map $parent->to_pixel($instance->[$_]) + $parent->{shift}[$_], (X,Y)); @@ -1074,10 +1077,11 @@ 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 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling -has 'scale' => (is => 'rw', default => sub { 1 }); -has 'rotate' => (is => 'rw', default => sub { 0 }); # around object center point +has 'scale' => (is => 'rw', default => sub { 1 }, trigger => 1); +has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => 1); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis -has 'thumbnail' => (is => 'rw'); +has 'thumbnail' => (is => 'rw', trigger => 1); +has 'transformed_thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw'); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -1099,6 +1103,21 @@ sub _trigger_model_object { } } +sub _trigger_scale { + my $self = shift; + $self->_transform_thumbnail; +} + +sub _trigger_rotate { + my $self = shift; + $self->_transform_thumbnail; +} + +sub _trigger_thumbnail { + my $self = shift; + $self->_transform_thumbnail; +} + sub check_manifoldness { my $self = shift; @@ -1147,34 +1166,23 @@ sub make_thumbnail { map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } @{$thumbnail->expolygons}; - $thumbnail->rotate(deg2rad($self->rotate)); # TODO: around center - $thumbnail->scale($self->scale); - $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object; return $thumbnail; } -sub set_rotation { +sub _transform_thumbnail { my $self = shift; - my ($angle) = @_; - if ($self->thumbnail) { - # rotate around object centerpoint - $self->thumbnail->rotate(deg2rad($angle - $self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)); - } - $self->rotate($angle); -} - -sub set_scale { - my $self = shift; - my ($scale) = @_; + # the order of these transformations MUST be the same everywhere, including + # in Slic3r::Print->add_model() + my $t = $self->thumbnail + ->clone + ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)) + ->scale($self->scale); - if ($self->thumbnail) { - $self->thumbnail->scale($scale / $self->scale); - } - $self->scale($scale); + $self->transformed_thumbnail($t); } # bounding box with applied rotation and scaling @@ -1183,7 +1191,7 @@ sub transformed_bounding_box { return $self->bounding_box ->clone - ->rotate(deg2rad($self->rotate), $self->bounding_box->center) + ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D) ->scale($self->scale); } diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 6464ba90eb..e20d4acaa2 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -120,6 +120,9 @@ sub add_model { foreach my $mesh (grep $_, @meshes) { $mesh->check_manifoldness; + # the order of these transformations must be the same as the one used in plater + # to make the object positioning consistent with the visual preview + # we ignore the per-instance rotation currently and only # consider the first one $mesh->rotate($object->instances->[0]->rotation, $mesh->center) From 5dcf2775638d3508b543b03d78d61dfccb351d11 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 10:27:47 +0200 Subject: [PATCH 65/78] Rotate cw when using free rotation button to be consistent with icon. #1171 --- 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 460f10edb3..5f55575abd 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -425,6 +425,7 @@ sub rotate { if (!defined $angle) { $angle = Wx::GetNumberFromUser("", "Enter the rotation angle:", "Rotate", $object->rotate, -364, 364, $self); return if !$angle || $angle == -1; + $angle = 0 - $angle; # rotate clockwise (be consistent with button icon) } $object->rotate($object->rotate + $angle); From 0a74e45432a764b1b711abd1b23983d461f746c3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 11:27:15 +0200 Subject: [PATCH 66/78] Scale and recenter thumbnails when bed size and/or print center is changed --- lib/Slic3r/GUI/Plater.pm | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5f55575abd..ffe54c52f7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -818,6 +818,8 @@ sub _update_bed_size { my $canvas_side = CANVAS_SIZE->[X]; # when the canvas is not rendered yet, its GetSize() method returns 0,0 my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y]; $self->{scaling_factor} = $canvas_side / $bed_largest_side; + $_->change_thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; + $self->recenter; } # this is called on the canvas @@ -1078,10 +1080,10 @@ 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 'bounding_box' => (is => 'rw'); # 3D bb of original object (aligned to origin) with no rotation or scaling -has 'scale' => (is => 'rw', default => sub { 1 }, trigger => 1); -has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => 1); # around object center point +has 'scale' => (is => 'rw', default => sub { 1 }, trigger => \&_transform_thumbnail); +has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_transform_thumbnail); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis -has 'thumbnail' => (is => 'rw', trigger => 1); +has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail); has 'transformed_thumbnail' => (is => 'rw'); has 'thumbnail_scaling_factor' => (is => 'rw'); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] @@ -1104,21 +1106,6 @@ sub _trigger_model_object { } } -sub _trigger_scale { - my $self = shift; - $self->_transform_thumbnail; -} - -sub _trigger_rotate { - my $self = shift; - $self->_transform_thumbnail; -} - -sub _trigger_thumbnail { - my $self = shift; - $self->_transform_thumbnail; -} - sub check_manifoldness { my $self = shift; @@ -1201,4 +1188,14 @@ sub transformed_size { return $self->transformed_bounding_box->size; } +sub change_thumbnail_scaling_factor { + my $self = shift; + my ($new_factor) = @_; + + return unless $self->thumbnail; + $self->thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); + $self->transformed_thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); + $self->thumbnail_scaling_factor($new_factor); +} + 1; From def013ba91d3e23a94b8d109a1cc4ac47db8754a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 11:28:27 +0200 Subject: [PATCH 67/78] Use _transform_thumbnail when rescaling thumbnails --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index ffe54c52f7..8c0020e84b 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1194,8 +1194,8 @@ sub change_thumbnail_scaling_factor { return unless $self->thumbnail; $self->thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); - $self->transformed_thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); $self->thumbnail_scaling_factor($new_factor); + $self->_transform_thumbnail; } 1; From 962a51cc800d8912739b25ab8248b087d3131289 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 14:33:10 +0200 Subject: [PATCH 68/78] Some cleanup for the plater code --- lib/Slic3r/GUI/Plater.pm | 28 ++++++++++++---------------- lib/Slic3r/Model.pm | 22 ++++++++++++++++++++-- lib/Slic3r/Print.pm | 23 +++++++++++------------ 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 8c0020e84b..3adf65f71e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -704,11 +704,6 @@ sub make_model { foreach my $plater_object (@{$self->{objects}}) { my $model_object = $plater_object->get_model_object; - # if we need to alter the mesh, clone it first - if ($plater_object->scale != 1) { - $model_object = $model_object->clone; - } - my $new_model_object = $model->add_object( vertices => $model_object->vertices, input_file => $plater_object->input_file, @@ -721,10 +716,10 @@ sub make_model { ); $model->set_material($volume->material_id || 0, {}); } - $new_model_object->scale($plater_object->scale); $new_model_object->align_to_origin; $new_model_object->add_instance( rotation => $plater_object->rotate, # around center point + scaling_factor => $plater_object->scale, offset => Slic3r::Point->new($_), ) for @{$plater_object->instances}; } @@ -778,9 +773,12 @@ sub recenter { map Slic3r::Geometry::move_points($_, @points), @{$obj->instances}; } @{$self->{objects}}, ]); + + # $self->{shift} contains the offset in pixels to add to object instances in order to center them + # it is expressed in upwards Y $self->{shift} = [ - ($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_bb[X2] + $print_bb[X1])) / 2, - ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] + $print_bb[Y1])) / 2, + $self->to_pixel(-$print_bb[X1]) + ($self->{canvas}->GetSize->GetWidth - $self->to_pixel($print_bb[X2] - $print_bb[X1])) / 2, + $self->to_pixel(-$print_bb[Y1]) + ($self->{canvas}->GetSize->GetHeight - $self->to_pixel($print_bb[Y2] - $print_bb[Y1])) / 2, ]; } @@ -814,10 +812,8 @@ sub _update_bed_size { # supposing the preview canvas is square, calculate the scaling factor # to constrain print bed area inside preview - my $bed_size = $self->{config}->bed_size; - my $canvas_side = CANVAS_SIZE->[X]; # when the canvas is not rendered yet, its GetSize() method returns 0,0 - my $bed_largest_side = $bed_size->[X] > $bed_size->[Y] ? $bed_size->[X] : $bed_size->[Y]; - $self->{scaling_factor} = $canvas_side / $bed_largest_side; + # when the canvas is not rendered yet, its GetSize() method returns 0,0 + $self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size }); $_->change_thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; $self->recenter; } @@ -949,7 +945,6 @@ sub mouse_event { my ($obj_idx, $instance_idx, $thumbnail) = @$preview; my $instance = $parent->{objects}[$obj_idx]->instances->[$instance_idx]; $instance->[$_] = $parent->to_units($pos->[$_] - $self->{drag_start_pos}[$_] - $parent->{shift}[$_]) for X,Y; - $instance = $parent->_y([$instance])->[0]; $parent->Refresh; } } elsif ($event->Moving) { @@ -1098,10 +1093,12 @@ 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); + my $mesh = $self->model_object->mesh; - $self->bounding_box($mesh->bounding_box); $self->facets(scalar @{$mesh->facets}); $self->vertices(scalar @{$mesh->vertices}); + $self->materials($self->model_object->materials_count); } } @@ -1140,8 +1137,7 @@ sub instances_count { sub make_thumbnail { my $self = shift; - my $mesh = $self->model_object->mesh; - $mesh->align_to_origin; + my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index b7ff6f7aec..b0f422ea17 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -119,6 +119,7 @@ sub arrange_objects { $object->add_instance( offset => $_, rotation => $instance->rotation, + scaling_factor => $instance->scaling_factor, ) for move_points($instance->offset, @positions); } } @@ -213,6 +214,7 @@ sub mesh { my $mesh = $object->mesh->clone; if ($instance) { $mesh->rotate($instance->rotation); + $mesh->scale($instance->scaling_factor); $mesh->align_to_origin; $mesh->move(@{$instance->offset}); } @@ -259,6 +261,7 @@ sub split_meshes { $new_object->add_instance( offset => [ $_->offset->[X] + $extents[X][MIN], $_->offset->[Y] + $extents[Y][MIN] ], rotation => $_->rotation, + scaling_factor => $_->scaling_factor, ) for @{ $object->instances // [] }; } } @@ -274,7 +277,7 @@ package Slic3r::Model::Object; use Moo; use List::Util qw(first); -use Slic3r::Geometry qw(X Y Z MIN move_points move_points_3D); +use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D); use Storable qw(dclone); has 'input_file' => (is => 'rw'); @@ -338,13 +341,27 @@ sub extents { return Slic3r::Geometry::bounding_box_3D($self->used_vertices); } +sub center { + my $self = shift; + + my @extents = $self->extents; + return [ map +($extents[$_][MAX] + $extents[$_][MIN])/2, X,Y,Z ]; +} + +sub bounding_box { + my $self = shift; + return Slic3r::Geometry::BoundingBox->new(extents => [ $self->extents ]); +} + sub align_to_origin { my $self = shift; # calculate the displacements needed to # have lowest value for each axis at coordinate 0 my @extents = $self->extents; - $self->move(map -$extents[$_][MIN], X,Y,Z); + my @shift = map -$extents[$_][MIN], X,Y,Z; + $self->move(@shift); + return @shift; } sub move { @@ -410,6 +427,7 @@ use Moo; has 'object' => (is => 'ro', weak_ref => 1, required => 1); has 'rotation' => (is => 'rw', default => sub { 0 }); # around mesh center point +has 'scaling_factor' => (is => 'rw', default => sub { 1 }); has 'offset' => (is => 'rw'); # must be Slic3r::Point object 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index e20d4acaa2..638ce8d3a4 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -106,6 +106,8 @@ sub add_model { $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; foreach my $object (@{ $model->objects }) { + my @align = $object->align_to_origin; + # extract meshes by material my @meshes = (); # by region_id foreach my $volume (@{$object->volumes}) { @@ -123,21 +125,18 @@ sub add_model { # the order of these transformations must be the same as the one used in plater # to make the object positioning consistent with the visual preview - # we ignore the per-instance rotation currently and only + # we ignore the per-instance transformations currently and only # consider the first one - $mesh->rotate($object->instances->[0]->rotation, $mesh->center) - if @{ $object->instances // [] }; + if ($object->instances && @{$object->instances}) { + $mesh->rotate($object->instances->[0]->rotation, $object->center); + $mesh->scale($object->instances->[0]->scaling_factor); + } $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - # align the object to origin; not sure this is required by the toolpath generation - # algorithms, but it's good practice to avoid negative coordinates; it probably - # provides also some better performance in infill generation - my @extents = Slic3r::Geometry::bounding_box_3D([ map @{$_->used_vertices}, grep $_, @meshes ]); - foreach my $mesh (grep $_, @meshes) { - $mesh->move(map -$extents[$_][MIN], X,Y,Z); - } + # calculate transformed size + my $size = [ Slic3r::Geometry::size_3D([ map @{$_->used_vertices}, grep $_, @meshes ]) ]; # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( @@ -145,10 +144,10 @@ sub add_model { meshes => [ @meshes ], copies => [ $object->instances - ? (map [ (scale $_->offset->[X]) + $extents[X][MIN], (scale $_->offset->[Y]) + $extents[Y][MIN] ], @{$object->instances}) + ? (map [ scale($_->offset->[X] - $align[X]), scale($_->offset->[Y] - $align[Y]) ], @{$object->instances}) : [0,0], ], - size => [ map $extents[$_][MAX] - $extents[$_][MIN], (X,Y,Z) ], + size => $size, input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); From 145fe0820314c5f807784afd38c22126a1508791 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 13 Jun 2013 20:05:32 +0200 Subject: [PATCH 69/78] More fixes for plater positioning. #1171 --- lib/Slic3r/GUI/Plater.pm | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 3adf65f71e..999a18680f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -814,7 +814,7 @@ sub _update_bed_size { # to constrain print bed area inside preview # when the canvas is not rendered yet, its GetSize() method returns 0,0 $self->{scaling_factor} = CANVAS_SIZE->[X] / max(@{ $self->{config}->bed_size }); - $_->change_thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; + $_->thumbnail_scaling_factor($self->{scaling_factor}) for @{ $self->{objects} }; $self->recenter; } @@ -1080,7 +1080,7 @@ has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_ has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis has 'thumbnail' => (is => 'rw', trigger => \&_transform_thumbnail); has 'transformed_thumbnail' => (is => 'rw'); -has 'thumbnail_scaling_factor' => (is => 'rw'); +has 'thumbnail_scaling_factor' => (is => 'rw', trigger => \&_transform_thumbnail); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] # statistics @@ -1143,7 +1143,6 @@ sub make_thumbnail { ? $mesh->horizontal_projection : [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ], ); - $thumbnail->scale($self->thumbnail_scaling_factor); # only simplify expolygons larger than the threshold @{$thumbnail->expolygons} = grep @$_, @@ -1159,12 +1158,9 @@ sub make_thumbnail { sub _transform_thumbnail { my $self = shift; - # the order of these transformations MUST be the same everywhere, including - # in Slic3r::Print->add_model() - my $t = $self->thumbnail - ->clone - ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D->clone->scale($self->thumbnail_scaling_factor)) - ->scale($self->scale); + return unless $self->thumbnail; + my $t = $self->_apply_transform($self->thumbnail); + $t->scale($self->thumbnail_scaling_factor); $self->transformed_thumbnail($t); } @@ -1172,8 +1168,16 @@ sub _transform_thumbnail { # bounding box with applied rotation and scaling sub transformed_bounding_box { my $self = shift; + return $self->_apply_transform($self->bounding_box); +} + +sub _apply_transform { + my $self = shift; + my ($entity) = @_; # can be anything that implements ->clone(), ->rotate() and ->scale() - return $self->bounding_box + # the order of these transformations MUST be the same everywhere, including + # in Slic3r::Print->add_model() + return $entity ->clone ->rotate(deg2rad($self->rotate), $self->bounding_box->center_2D) ->scale($self->scale); @@ -1184,14 +1188,4 @@ sub transformed_size { return $self->transformed_bounding_box->size; } -sub change_thumbnail_scaling_factor { - my $self = shift; - my ($new_factor) = @_; - - return unless $self->thumbnail; - $self->thumbnail->scale($new_factor / $self->thumbnail_scaling_factor); - $self->thumbnail_scaling_factor($new_factor); - $self->_transform_thumbnail; -} - 1; From 878d17605c0585dcfb895e9302a99e6611022bf4 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 14 Jun 2013 16:48:24 +0200 Subject: [PATCH 70/78] Ignore solid_infill_every_layers when fill_density is 0. Includes regression test. #1240 --- lib/Slic3r/Print/Object.pm | 3 ++- t/fill.t | 11 +++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index bfc46ce620..88a445f03a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -597,7 +597,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 && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { + if ($Slic3r::Config->solid_infill_every_layers && $Slic3r::Config->fill_density > 0 + && ($i % $Slic3r::Config->solid_infill_every_layers) == 0) { $_->surface_type(S_TYPE_INTERNALSOLID) for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; } diff --git a/t/fill.t b/t/fill.t index e0fdd3cc08..4665a0c494 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 9; +plan tests => 10; BEGIN { use FindBin; @@ -109,14 +109,17 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } $config->set('top_solid_layers', 0); $config->set('bottom_solid_layers', 0); $config->set('solid_infill_below_area', 20000000); + $config->set('solid_infill_every_layers', 2); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my %layers_with_extrusion = (); Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; - - fail "solid_infill_below_area should be ignored when fill_density is 0" - if $info->{extruding}; + $layers_with_extrusion{$self->Z} = 1 if $info->{extruding}; }); + + ok !%layers_with_extrusion, + "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0"; } __END__ From 5fa49aad02d646a9d71cc051b0a2c6c8bc471973 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 14 Jun 2013 16:59:20 +0200 Subject: [PATCH 71/78] Releasing 0.9.10 --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 67314e2170..78ac6e5b86 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.10-dev"; +our $VERSION = "0.9.10"; our $debug = 0; sub debugf { From a8981b8b359eedc2294404f72aa9b70182d62d97 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 12:10:57 +0200 Subject: [PATCH 72/78] Bugfix: infill was clipped badly. Includes regression test. #1245 --- MANIFEST | 1 + lib/Slic3r/Print.pm | 12 ++++++++---- lib/Slic3r/Test.pm | 9 ++++++--- t/print.t | 20 ++++++++++++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 t/print.t diff --git a/MANIFEST b/MANIFEST index b2fe05edab..86390ed372 100644 --- a/MANIFEST +++ b/MANIFEST @@ -76,6 +76,7 @@ t/geometry.t t/layers.t t/loops.t t/polyclip.t +t/print.t t/retraction.t t/serialize.t t/shells.t diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 638ce8d3a4..956de69925 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -106,6 +106,7 @@ sub add_model { $model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; foreach my $object (@{ $model->objects }) { + # we align object to origin before applying transformations my @align = $object->align_to_origin; # extract meshes by material @@ -135,8 +136,11 @@ sub add_model { $mesh->scale(1 / &Slic3r::SCALING_FACTOR); } - # calculate transformed size - my $size = [ Slic3r::Geometry::size_3D([ map @{$_->used_vertices}, grep $_, @meshes ]) ]; + # we also align object after transformations so that we only work with positive coordinates + # and the assumption that bounding_box === size works + my $bb = Slic3r::Geometry::BoundingBox->new_from_points_3D([ map @{$_->used_vertices}, grep $_, @meshes ]); + my @align2 = map -$bb->extents->[$_][MIN], (X,Y,Z); + $_->move(@align2) for grep $_, @meshes; # initialize print object push @{$self->objects}, Slic3r::Print::Object->new( @@ -144,10 +148,10 @@ sub add_model { meshes => [ @meshes ], copies => [ $object->instances - ? (map [ scale($_->offset->[X] - $align[X]), scale($_->offset->[Y] - $align[Y]) ], @{$object->instances}) + ? (map [ scale($_->offset->[X] - $align[X]) - $align2[X], scale($_->offset->[Y] - $align[Y]) - $align2[Y] ], @{$object->instances}) : [0,0], ], - size => $size, + size => $bb->size, # transformed size input_file => $object->input_file, layer_height_ranges => $object->layer_height_ranges, ); diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 46443cc6af..3192149974 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -16,7 +16,7 @@ my %cuboids = ( ); sub model { - my ($model_name) = @_; + my ($model_name, %params) = @_; my ($vertices, $facets); if ($cuboids{$model_name}) { @@ -32,7 +32,10 @@ sub model { my $model = Slic3r::Model->new; my $object = $model->add_object(vertices => $vertices); $object->add_volume(facets => $facets); - $object->add_instance(offset => [0,0]); + $object->add_instance( + offset => [0,0], + rotation => $params{rotation}, + ); return $model; } @@ -46,7 +49,7 @@ sub init_print { my $print = Slic3r::Print->new(config => $config); $model_name = [$model_name] if ref($model_name) ne 'ARRAY'; - $print->add_model(model($_)) for @$model_name; + $print->add_model(model($_, %params)) for @$model_name; $print->validate; return $print; diff --git a/t/print.t b/t/print.t new file mode 100644 index 0000000000..485ad12bbb --- /dev/null +++ b/t/print.t @@ -0,0 +1,20 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use List::Util qw(first); +use Slic3r; +use Slic3r::Test; + +{ + my $print = Slic3r::Test::init_print('20mm_cube', rotation => 45); + ok !(first { $_ < 0 } map @$_, map @{$_->used_vertices}, grep $_, map @{$_->meshes}, @{$print->objects}), + "object is still in positive coordinate space even after rotation"; +} + +__END__ From 7bf308c08fec43d45649f0a20787372ba046e9a6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 15:50:02 +0200 Subject: [PATCH 73/78] Fix one more centering problem caused by wrong bounding box implementation --- lib/Slic3r/GUI/Plater.pm | 11 ++++++++--- lib/Slic3r/Geometry/BoundingBox.pm | 15 ++------------- lib/Slic3r/Polyline.pm | 7 ++++--- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 999a18680f..b34efa82ef 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1067,7 +1067,7 @@ sub OnDropFiles { package Slic3r::GUI::Plater::Object; use Moo; -use Math::ConvexHull::MonotoneChain qw(convex_hull); +use Math::ConvexHull::MonotoneChain qw(); use Slic3r::Geometry qw(X Y Z MIN MAX deg2rad); has 'name' => (is => 'rw', required => 1); @@ -1075,6 +1075,7 @@ 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 '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); has 'rotate' => (is => 'rw', default => sub { 0 }, trigger => \&_transform_thumbnail); # around object center point has 'instances' => (is => 'rw', default => sub { [] }); # upward Y axis @@ -1138,10 +1139,11 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin + $self->convex_hull(Slic3r::Polygon->new(Math::ConvexHull::MonotoneChain::convex_hull($mesh->vertices))); my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection - : [ Slic3r::ExPolygon->new(convex_hull($mesh->vertices)) ], + : [ Slic3r::ExPolygon->new($self->convex_hull) ], ); # only simplify expolygons larger than the threshold @@ -1168,7 +1170,10 @@ sub _transform_thumbnail { # bounding box with applied rotation and scaling sub transformed_bounding_box { my $self = shift; - return $self->_apply_transform($self->bounding_box); + + my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull)); + $bb->extents->[Z] = $self->bounding_box->clone->extents->[Z]; + return $bb; } sub _apply_transform { diff --git a/lib/Slic3r/Geometry/BoundingBox.pm b/lib/Slic3r/Geometry/BoundingBox.pm index 3cd019f0cb..c2d27a1fe8 100644 --- a/lib/Slic3r/Geometry/BoundingBox.pm +++ b/lib/Slic3r/Geometry/BoundingBox.pm @@ -39,20 +39,9 @@ sub polygon { return Slic3r::Polygon->new_from_bounding_box($self->bb); } +# note to $self sub rotate { - my $self = shift; - my ($angle, $center) = @_; - - # rotate the 2D bounding box polygon and leave Z unaltered - my $bb_p = $self->polygon; - $bb_p->rotate($angle, $center); - my @bb = $bb_p->bounding_box; - $self->extents->[X][MIN] = $bb[X1]; - $self->extents->[Y][MIN] = $bb[Y1]; - $self->extents->[X][MAX] = $bb[X2]; - $self->extents->[Y][MAX] = $bb[Y2]; - - $self; + die "Rotating an axis-aligned bounding box doesn't make any sense"; } sub scale { diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 6646d1aaeb..2de79893a7 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -156,11 +156,12 @@ sub translate { sub scale { my $self = shift; my ($factor) = @_; - return if $factor == 1; # transform point coordinates - foreach my $point (@$self) { - $point->[$_] *= $factor for X,Y; + if ($factor != 1) { + foreach my $point (@$self) { + $point->[$_] *= $factor for X,Y; + } } return $self; } From 8b6afb61d911b028b87ee133ad1b2248f1811468 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 17:17:48 +0200 Subject: [PATCH 74/78] Fix little error in tests --- lib/Slic3r/Test.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 3192149974..f4942d0c3a 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -34,7 +34,7 @@ sub model { $object->add_volume(facets => $facets); $object->add_instance( offset => [0,0], - rotation => $params{rotation}, + rotation => $params{rotation} // 0, ); return $model; } From cb75b1e47bc81656a5e4818be615c27e4cc15ace Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 19:15:24 +0200 Subject: [PATCH 75/78] Fix plater crash on threaded perls --- lib/Slic3r/GUI/Plater.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index b34efa82ef..8ce659486a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1097,6 +1097,7 @@ sub _trigger_model_object { $self->bounding_box($self->model_object->bounding_box); my $mesh = $self->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}); @@ -1139,7 +1140,6 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin - $self->convex_hull(Slic3r::Polygon->new(Math::ConvexHull::MonotoneChain::convex_hull($mesh->vertices))); my $thumbnail = Slic3r::ExPolygon::Collection->new( expolygons => (@{$mesh->facets} <= 5000) ? $mesh->horizontal_projection From 8b48d79563d4a504e6d7ee0c8e4cc93eb8094190 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 21:10:04 +0200 Subject: [PATCH 76/78] Improve zigzag gap fill. #1234 --- lib/Slic3r/Layer/Region.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 472c2da520..a9dbc3bdcd 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -315,8 +315,12 @@ sub _fill_gaps { 1, )}; + # medial axis-based gap fill should benefit from detection of larger gaps too, so + # we could try with 1.5*$w for example, but that doesn't work well for zigzag fill + # because it tends to create very sparse points along the gap when the infill direction + # is not parallel to the gap (1.5*$w thus may only work well with a straight line) my $w = $self->perimeter_flow->width; - my @widths = (1.5 * $w, $w, 0.4 * $w); # worth trying 0.2 too? + my @widths = ($w, 0.4 * $w); # worth trying 0.2 too? foreach my $width (@widths) { my $flow = $self->perimeter_flow->clone(width => $width); From d0eac88ff9586b17dcc1766874f69dbd7e8c534f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 15 Jun 2013 21:27:36 +0200 Subject: [PATCH 77/78] Reduce vibrations when doing gap fill --- lib/Slic3r/Layer/Region.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index a9dbc3bdcd..1e6b7c169e 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -361,7 +361,7 @@ sub _fill_gaps { push @{ $self->thin_fills }, map { - $_->polyline->simplify($flow->scaled_width / 3); + $_->simplify($flow->scaled_width/3); $_->pack; } map Slic3r::ExtrusionPath->new( From 2993a1659e10f7e2c05abf7ab5e0032f96e5ea7e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 16 Jun 2013 10:22:22 +0200 Subject: [PATCH 78/78] Bump version number --- lib/Slic3r.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 78ac6e5b86..d4caf4b460 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -7,7 +7,7 @@ use strict; use warnings; require v5.10; -our $VERSION = "0.9.10"; +our $VERSION = "0.9.11-dev"; our $debug = 0; sub debugf {