diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index a4e1d07d8..0d7d350d6 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -18,6 +18,8 @@ sub debugf { printf @_ if $debug; } +our $loglevel = 0; + # load threads before Moo as required by it our $have_threads; BEGIN { @@ -104,6 +106,10 @@ my $pause_sema = Thread::Semaphore->new; my $parallel_sema; my $paused = 0; +# Set the logging level at the Slic3r XS module. +$Slic3r::loglevel = (defined($ENV{'SLIC3R_LOGLEVEL'}) && $ENV{'SLIC3R_LOGLEVEL'} =~ /^[1-9]/) ? $ENV{'SLIC3R_LOGLEVEL'} : 0; +set_logging_level($Slic3r::loglevel); + sub spawn_thread { my ($cb) = @_; diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 087f13cc2..e655bb1b3 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -14,6 +14,7 @@ use Slic3r::GUI::ConfigWizard; use Slic3r::GUI::Controller; use Slic3r::GUI::Controller::ManualControlDialog; use Slic3r::GUI::Controller::PrinterPanel; +use Slic3r::GUI::GLShader; use Slic3r::GUI::MainFrame; use Slic3r::GUI::Notifier; use Slic3r::GUI::Plater; diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index 374469a0e..8acb9ba27 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -19,7 +19,8 @@ package Slic3r::GUI::3DScene::Base; use strict; use warnings; -use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS); +use Wx qw(:timer); +use Wx::Event qw(EVT_PAINT EVT_SIZE EVT_ERASE_BACKGROUND EVT_IDLE EVT_MOUSEWHEEL EVT_MOUSE_EVENTS EVT_TIMER); # must load OpenGL *before* Wx::GLCanvas use OpenGL qw(:glconstants :glfunctions :glufunctions :gluconstants); use base qw(Wx::GLCanvas Class::Accessor); @@ -53,10 +54,15 @@ __PACKAGE__->mk_accessors( qw(_quat _dirty init origin _mouse_pos _hover_volume_idx + _drag_volume_idx _drag_start_pos _drag_start_xy _dragged + + layer_editing_enabled + _layer_height_edited + _camera_type _camera_target _camera_distance @@ -82,7 +88,10 @@ use constant VIEW_BOTTOM => [0.0,180.0]; use constant VIEW_FRONT => [0.0,90.0]; use constant VIEW_REAR => [180.0,90.0]; -#use constant GIMBALL_LOCK_THETA_MAX => 150; +use constant MANIPULATION_IDLE => 0; +use constant MANIPULATION_DRAGGING => 1; +use constant MANIPULATION_LAYER_HEIGHT => 2; + use constant GIMBALL_LOCK_THETA_MAX => 170; # make OpenGL::Array thread-safe @@ -130,6 +139,15 @@ sub new { # $self->_camera_type('perspective'); $self->_camera_target(Slic3r::Pointf3->new(0,0,0)); $self->_camera_distance(0.); + + # Size of a layer height texture, used by a shader to color map the object print layers. + $self->{layer_preview_z_texture_width} = 512; + $self->{layer_preview_z_texture_height} = 512; + $self->{layer_height_edit_band_width} = 2.; + $self->{layer_height_edit_strength} = 0.005; + $self->{layer_height_edit_last_object_id} = -1; + $self->{layer_height_edit_last_z} = 0.; + $self->{layer_height_edit_last_action} = 0; $self->reset_objects; @@ -144,88 +162,142 @@ sub new { $self->Resize( $self->GetSizeWH ); $self->Refresh; }); - EVT_MOUSEWHEEL($self, sub { - my ($self, $e) = @_; - - # Calculate the zoom delta and apply it to the current zoom factor - my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); - $zoom = max(min($zoom, 4), -4); - $zoom /= 10; - $self->_zoom($self->_zoom / (1-$zoom)); - - # In order to zoom around the mouse point we need to translate - # the camera target - my $size = Slic3r::Pointf->new($self->GetSizeWH); - my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- - $self->_camera_target->translate( - # ($pos - $size/2) represents the vector from the viewport center - # to the mouse point. By multiplying it by $zoom we get the new, - # transformed, length of such vector. - # Since we want that point to stay fixed, we move our camera target - # in the opposite direction by the delta of the length of such vector - # ($zoom - 1). We then scale everything by 1/$self->_zoom since - # $self->_camera_target is expressed in terms of model units. - -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, - -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, - 0, - ) if 0; - $self->on_viewport_changed->() if $self->on_viewport_changed; - $self->_dirty(1); - $self->Refresh; - }); + EVT_MOUSEWHEEL($self, \&mouse_wheel_event); EVT_MOUSE_EVENTS($self, \&mouse_event); + $self->{layer_height_edit_timer_id} = &Wx::NewId(); + $self->{layer_height_edit_timer} = Wx::Timer->new($self, $self->{layer_height_edit_timer_id}); + EVT_TIMER($self, $self->{layer_height_edit_timer_id}, sub { + my ($self, $event) = @_; + return if ! $self->_layer_height_edited; + return if $self->{layer_height_edit_last_object_id} == -1; + $self->_variable_layer_thickness_action(undef, 1); + }); + return $self; } +sub Destroy { + my ($self) = @_; + $self->{layer_height_edit_timer}->Stop; + $self->DestroyGL; + return $self->SUPER::Destroy; +} + +sub _first_selected_object_id { + my ($self) = @_; + for my $i (0..$#{$self->volumes}) { + if ($self->volumes->[$i]->selected) { + return int($self->volumes->[$i]->select_group_id / 1000000); + } + } + return -1; +} + +# Returns an array with (left, top, right, bottom) of the variable layer thickness bar on the screen. +sub _variable_layer_thickness_bar_rect { + my ($self) = @_; + my ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + return ($cw - $bar_width, 0, $cw, $ch); +} + +sub _variable_layer_thickness_bar_rect_mouse_inside { + my ($self, $mouse_evt) = @_; + my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect; + return $mouse_evt->GetX >= $bar_left && $mouse_evt->GetX <= $bar_right && $mouse_evt->GetY >= $bar_top && $mouse_evt->GetY <= $bar_bottom; +} + +sub _variable_layer_thickness_bar_mouse_cursor_z { + my ($self, $object_idx, $mouse_evt) = @_; + my ($bar_left, $bar_top, $bar_right, $bar_bottom) = $self->_variable_layer_thickness_bar_rect; + return unscale($self->{print}->get_object($object_idx)->size->z) * ($bar_bottom - $mouse_evt->GetY - 1.) / ($bar_bottom - $bar_top); +} + +sub _variable_layer_thickness_action { + my ($self, $mouse_event, $do_modification) = @_; + # A volume is selected. Test, whether hovering over a layer thickness bar. + if (defined($mouse_event)) { + $self->{layer_height_edit_last_z} = $self->_variable_layer_thickness_bar_mouse_cursor_z($self->{layer_height_edit_last_object_id}, $mouse_event); + $self->{layer_height_edit_last_action} = $mouse_event->ShiftDown ? ($mouse_event->RightIsDown ? 3 : 2) : ($mouse_event->RightIsDown ? 0 : 1); + } + if ($self->{layer_height_edit_last_object_id} != -1) { + $self->{print}->get_object($self->{layer_height_edit_last_object_id})->adjust_layer_height_profile( + $self->{layer_height_edit_last_z}, + $self->{layer_height_edit_strength}, + $self->{layer_height_edit_band_width}, + $self->{layer_height_edit_last_action}); + $self->{print}->get_object($self->{layer_height_edit_last_object_id})->generate_layer_height_texture( + $self->volumes->[$self->{layer_height_edit_last_object_id}]->layer_height_texture_data->ptr, + $self->{layer_preview_z_texture_height}, + $self->{layer_preview_z_texture_width}); + $self->Refresh; + # Automatic action on mouse down with the same coordinate. + $self->{layer_height_edit_timer}->Start(100, wxTIMER_CONTINUOUS); + } +} + sub mouse_event { my ($self, $e) = @_; my $pos = Slic3r::Pointf->new($e->GetPositionXY); + my $object_idx_selected = $self->{layer_height_edit_last_object_id} = ($self->layer_editing_enabled && $self->{print}) ? $self->_first_selected_object_id : -1; + if ($e->Entering && &Wx::wxMSW) { # wxMSW needs focus in order to catch mouse wheel events $self->SetFocus; } elsif ($e->LeftDClick) { - $self->on_double_click->() - if $self->on_double_click; + if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { + } elsif ($self->on_double_click) { + $self->on_double_click->(); + } } elsif ($e->LeftDown || $e->RightDown) { # If user pressed left or right button we first check whether this happened # on a volume or not. my $volume_idx = $self->_hover_volume_idx // -1; - - # select volume in this 3D canvas - if ($self->enable_picking) { - $self->deselect_volumes; - $self->select_volume($volume_idx); + $self->_layer_height_edited(0); + if ($object_idx_selected != -1 && $self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { + # A volume is selected and the mouse is hovering over a layer thickness bar. + # Start editing the layer height. + $self->_layer_height_edited(1); + $self->_variable_layer_thickness_action($e, 1); + } else { + # Select volume in this 3D canvas. + # Don't deselect a volume if layer editing is enabled. We want the object to stay selected + # during the scene manipulation. + if ($self->enable_picking && ($volume_idx != -1 || ! $self->layer_editing_enabled)) { + $self->deselect_volumes; + $self->select_volume($volume_idx); + + if ($volume_idx != -1) { + my $group_id = $self->volumes->[$volume_idx]->select_group_id; + my @volumes; + if ($group_id != -1) { + $self->select_volume($_) + for grep $self->volumes->[$_]->select_group_id == $group_id, + 0..$#{$self->volumes}; + } + } + + $self->Refresh; + } + + # propagate event through callback + $self->on_select->($volume_idx) + if $self->on_select; if ($volume_idx != -1) { - my $group_id = $self->volumes->[$volume_idx]->select_group_id; - my @volumes; - if ($group_id != -1) { - $self->select_volume($_) - for grep $self->volumes->[$_]->select_group_id == $group_id, - 0..$#{$self->volumes}; + if ($e->LeftDown && $self->enable_moving) { + $self->_drag_volume_idx($volume_idx); + $self->_drag_start_pos($self->mouse_to_3d(@$pos)); + } elsif ($e->RightDown) { + # if right clicking on volume, propagate event through callback + $self->on_right_click->($e->GetPosition) + if $self->on_right_click; } } - - $self->Refresh; } - - # propagate event through callback - $self->on_select->($volume_idx) - if $self->on_select; - - if ($volume_idx != -1) { - if ($e->LeftDown && $self->enable_moving) { - $self->_drag_volume_idx($volume_idx); - $self->_drag_start_pos($self->mouse_to_3d(@$pos)); - } elsif ($e->RightDown) { - # if right clicking on volume, propagate event through callback - $self->on_right_click->($e->GetPosition) - if $self->on_right_click; - } - } - } elsif ($e->Dragging && $e->LeftIsDown && defined($self->_drag_volume_idx)) { + } elsif ($e->Dragging && $e->LeftIsDown && ! $self->_layer_height_edited && defined($self->_drag_volume_idx)) { # get new position at the same Z of the initial click point my $mouse_ray = $self->mouse_ray($e->GetX, $e->GetY); my $cur_pos = $mouse_ray->intersect_plane($self->_drag_start_pos->z); @@ -250,7 +322,9 @@ sub mouse_event { $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging) { - if ($e->LeftIsDown) { + if ($self->_layer_height_edited && $object_idx_selected != -1) { + $self->_variable_layer_thickness_action($e, 0); + } elsif ($e->LeftIsDown) { # if dragging over blank area with left button, rotate if (defined $self->_drag_start_pos) { my $orig = $self->_drag_start_pos; @@ -305,14 +379,61 @@ sub mouse_event { $self->_drag_start_pos(undef); $self->_drag_start_xy(undef); $self->_dragged(undef); + $self->_layer_height_edited(undef); + $self->{layer_height_edit_timer}->Stop; } elsif ($e->Moving) { $self->_mouse_pos($pos); - $self->Refresh; + # Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor + # hovers over. + $self->Refresh if ($self->enable_picking); } else { $e->Skip(); } } +sub mouse_wheel_event { + my ($self, $e) = @_; + + if ($self->layer_editing_enabled && $self->{print}) { + my $object_idx_selected = $self->_first_selected_object_id; + if ($object_idx_selected != -1) { + # A volume is selected. Test, whether hovering over a layer thickness bar. + if ($self->_variable_layer_thickness_bar_rect_mouse_inside($e)) { + # Adjust the width of the selection. + $self->{layer_height_edit_band_width} = max(min($self->{layer_height_edit_band_width} * (1 + 0.1 * $e->GetWheelRotation() / $e->GetWheelDelta()), 10.), 1.5); + $self->Refresh; + return; + } + } + } + + # Calculate the zoom delta and apply it to the current zoom factor + my $zoom = $e->GetWheelRotation() / $e->GetWheelDelta(); + $zoom = max(min($zoom, 4), -4); + $zoom /= 10; + $self->_zoom($self->_zoom / (1-$zoom)); + + # In order to zoom around the mouse point we need to translate + # the camera target + my $size = Slic3r::Pointf->new($self->GetSizeWH); + my $pos = Slic3r::Pointf->new($e->GetX, $size->y - $e->GetY); #- + $self->_camera_target->translate( + # ($pos - $size/2) represents the vector from the viewport center + # to the mouse point. By multiplying it by $zoom we get the new, + # transformed, length of such vector. + # Since we want that point to stay fixed, we move our camera target + # in the opposite direction by the delta of the length of such vector + # ($zoom - 1). We then scale everything by 1/$self->_zoom since + # $self->_camera_target is expressed in terms of model units. + -($pos->x - $size->x/2) * ($zoom) / $self->_zoom, + -($pos->y - $size->y/2) * ($zoom) / $self->_zoom, + 0, + ) if 0; + $self->on_viewport_changed->() if $self->on_viewport_changed; + $self->_dirty(1); + $self->Refresh; +} + # Reset selection. sub reset_objects { my ($self) = @_; @@ -720,7 +841,22 @@ sub InitGL { return if $self->init; return unless $self->GetContext; $self->init(1); - + + my $shader; + $shader = $self->{shader} = new Slic3r::GUI::GLShader + if (defined($ENV{'SLIC3R_EXPERIMENTAL'}) && $ENV{'SLIC3R_EXPERIMENTAL'} == 1); + if ($self->{shader}) { + my $info = $shader->Load($self->_fragment_shader, $self->_vertex_shader); + print $info if $info; + + ($self->{layer_preview_z_texture_id}) = glGenTextures_p(1); + glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + glBindTexture(GL_TEXTURE_2D, 0); + } + glClearColor(0, 0, 0, 1); glColor3f(1, 0, 0); glEnable(GL_DEPTH_TEST); @@ -761,7 +897,14 @@ sub InitGL { glEnable(GL_COLOR_MATERIAL); glEnable(GL_MULTISAMPLE); } - + +sub DestroyGL { + my $self = shift; + if ($self->init && $self->GetContext) { + delete $self->{shader}; + } +} + sub Render { my ($self, $dc) = @_; @@ -798,11 +941,15 @@ sub Render { glLightfv_p(GL_LIGHT0, GL_POSITION, -0.5, -0.5, 1, 0); glLightfv_p(GL_LIGHT0, GL_SPECULAR, 0.2, 0.2, 0.2, 1); glLightfv_p(GL_LIGHT0, GL_DIFFUSE, 0.5, 0.5, 0.5, 1); + + # Head light + glLightfv_p(GL_LIGHT1, GL_POSITION, 1, 0, 1, 0); if ($self->enable_picking) { # Render the object for picking. # FIXME This cannot possibly work in a multi-sampled context as the color gets mangled by the anti-aliasing. # Better to use software ray-casting on a bounding-box hierarchy. + glDisable(GL_MULTISAMPLE); glDisable(GL_LIGHTING); $self->draw_volumes(1); glFlush(); @@ -829,6 +976,7 @@ sub Render { glFlush(); glFinish(); glEnable(GL_LIGHTING); + glEnable(GL_MULTISAMPLE); } # draw fixed background @@ -928,7 +1076,7 @@ sub Render { # draw objects $self->draw_volumes; - + # draw cutting plane if (defined $self->cutting_plane_z) { my $plane_z = $self->cutting_plane_z; @@ -947,10 +1095,13 @@ sub Render { glEnable(GL_CULL_FACE); glDisable(GL_BLEND); } + + $self->draw_active_object_annotations; - glFlush(); - $self->SwapBuffers(); + + # Calling glFinish has a performance penalty, but it seems to fix some OpenGL driver hang-up with extremely large scenes. + glFinish(); } sub draw_volumes { @@ -963,10 +1114,61 @@ sub draw_volumes { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); + # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), + # where x, y is the window size divided by $self->_zoom. + my ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + my ($bar_left, $bar_right) = ((0.5 * $cw - $bar_width)/$self->_zoom, $cw/(2*$self->_zoom)); + my ($bar_bottom, $bar_top) = (-$ch/(2*$self->_zoom), $ch/(2*$self->_zoom)); + my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); + my $z_cursor_relative = ($mouse_pos->x < $cw - $bar_width) ? -1000. : + ($ch - $mouse_pos->y - 1.) / ($ch - 1); + foreach my $volume_idx (0..$#{$self->volumes}) { my $volume = $self->volumes->[$volume_idx]; - - if ($fakecolor) { + + my $shader_active = 0; + if ($self->layer_editing_enabled && ! $fakecolor && $volume->selected && $self->{shader} && $volume->{layer_height_texture_data} && $volume->{layer_height_texture_cells}) { + $self->{shader}->Enable; + my $z_to_texture_row_id = $self->{shader}->Map('z_to_texture_row'); + my $z_texture_row_to_normalized_id = $self->{shader}->Map('z_texture_row_to_normalized'); + my $z_cursor_id = $self->{shader}->Map('z_cursor'); + my $z_cursor_band_width_id = $self->{shader}->Map('z_cursor_band_width'); + die if ! defined($z_to_texture_row_id); + die if ! defined($z_texture_row_to_normalized_id); + die if ! defined($z_cursor_id); + die if ! defined($z_cursor_band_width_id); + my $ncells = $volume->{layer_height_texture_cells}; + my $z_max = $volume->{bounding_box}->z_max; + glUniform1fARB($z_to_texture_row_id, ($ncells - 1) / ($self->{layer_preview_z_texture_width} * $z_max)); + glUniform1fARB($z_texture_row_to_normalized_id, 1. / $self->{layer_preview_z_texture_height}); + glUniform1fARB($z_cursor_id, $z_max * $z_cursor_relative); + glUniform1fARB($z_cursor_band_width_id, $self->{layer_height_edit_band_width}); + glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LEVEL, 0); +# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); + if (1) { + glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +# glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +# glPixelStorei(GL_UNPACK_ROW_LENGTH, $self->{layer_preview_z_texture_width}); + glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr); + glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->offset($self->{layer_preview_z_texture_width} * $self->{layer_preview_z_texture_height} * 4)); + } else { + glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + 0, GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr); + glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $self->{layer_preview_z_texture_width}/2, $self->{layer_preview_z_texture_height}/2, + 0, GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr + $self->{layer_preview_z_texture_width} * $self->{layer_preview_z_texture_height} * 4); + } + +# my $nlines = ceil($ncells / ($self->{layer_preview_z_texture_width} - 1)); + + $shader_active = 1; + } elsif ($fakecolor) { # Object picking mode. Render the object with a color encoding the object index. my $r = ($volume_idx & 0x000000FF) >> 0; my $g = ($volume_idx & 0x0000FF00) >> 8; @@ -1017,16 +1219,39 @@ sub draw_volumes { if ($qverts_begin < $qverts_end) { glVertexPointer_c(3, GL_FLOAT, 0, $volume->qverts->verts_ptr); glNormalPointer_c(GL_FLOAT, 0, $volume->qverts->norms_ptr); - glDrawArrays(GL_QUADS, $qverts_begin / 3, ($qverts_end-$qverts_begin) / 3); + $qverts_begin /= 3; + $qverts_end /= 3; + my $nvertices = $qverts_end-$qverts_begin; + while ($nvertices > 0) { + my $nvertices_this = ($nvertices > 4096) ? 4096 : $nvertices; + glDrawArrays(GL_QUADS, $qverts_begin, $nvertices_this); + $qverts_begin += $nvertices_this; + $nvertices -= $nvertices_this; + } } if ($tverts_begin < $tverts_end) { glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts->verts_ptr); glNormalPointer_c(GL_FLOAT, 0, $volume->tverts->norms_ptr); - glDrawArrays(GL_TRIANGLES, $tverts_begin / 3, ($tverts_end-$tverts_begin) / 3); + $tverts_begin /= 3; + $tverts_end /= 3; + my $nvertices = $tverts_end-$tverts_begin; + while ($nvertices > 0) { + my $nvertices_this = ($nvertices > 4095) ? 4095 : $nvertices; + glDrawArrays(GL_TRIANGLES, $tverts_begin, $nvertices_this); + $tverts_begin += $nvertices_this; + $nvertices -= $nvertices_this; + } } - + + glVertexPointer_c(3, GL_FLOAT, 0, 0); + glNormalPointer_c(GL_FLOAT, 0, 0); glPopMatrix(); + + if ($shader_active) { + glBindTexture(GL_TEXTURE_2D, 0); + $self->{shader}->Disable; + } } glDisableClientState(GL_NORMAL_ARRAY); glDisable(GL_BLEND); @@ -1036,10 +1261,98 @@ sub draw_volumes { glColor3f(0, 0, 0); glVertexPointer_p(3, $self->cut_lines_vertices); glDrawArrays(GL_LINES, 0, $self->cut_lines_vertices->elements / 3); + glVertexPointer_c(3, GL_FLOAT, 0, 0); } glDisableClientState(GL_VERTEX_ARRAY); } +sub draw_active_object_annotations { + # $fakecolor is a boolean indicating, that the objects shall be rendered in a color coding the object index for picking. + my ($self) = @_; + + return if (! $self->{shader} || ! $self->layer_editing_enabled); + + my $volume; + foreach my $volume_idx (0..$#{$self->volumes}) { + my $v = $self->volumes->[$volume_idx]; + if ($v->selected && $v->{layer_height_texture_data} && $v->{layer_height_texture_cells}) { + $volume = $v; + last; + } + } + return if (! $volume); + + # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), + # where x, y is the window size divided by $self->_zoom. + my ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + my ($bar_left, $bar_right) = ((0.5 * $cw - $bar_width)/$self->_zoom, $cw/(2*$self->_zoom)); + my ($bar_bottom, $bar_top) = (-$ch/(2*$self->_zoom), $ch/(2*$self->_zoom)); + my $mouse_pos = $self->ScreenToClientPoint(Wx::GetMousePosition()); + my $z_cursor_relative = ($mouse_pos->x < $cw - $bar_width) ? -1000. : + ($ch - $mouse_pos->y - 1.) / ($ch - 1); + + $self->{shader}->Enable; + my $z_to_texture_row_id = $self->{shader}->Map('z_to_texture_row'); + my $z_texture_row_to_normalized_id = $self->{shader}->Map('z_texture_row_to_normalized'); + my $z_cursor_id = $self->{shader}->Map('z_cursor'); + my $ncells = $volume->{layer_height_texture_cells}; + my $z_max = $volume->{bounding_box}->z_max; + glUniform1fARB($z_to_texture_row_id, ($ncells - 1) / ($self->{layer_preview_z_texture_width} * $z_max)); + glUniform1fARB($z_texture_row_to_normalized_id, 1. / $self->{layer_preview_z_texture_height}); + glUniform1fARB($z_cursor_id, $z_max * $z_cursor_relative); + glBindTexture(GL_TEXTURE_2D, $self->{layer_preview_z_texture_id}); + glTexImage2D_c(GL_TEXTURE_2D, 0, GL_RGBA8, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexImage2D_c(GL_TEXTURE_2D, 1, GL_RGBA8, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexSubImage2D_c(GL_TEXTURE_2D, 0, 0, 0, $self->{layer_preview_z_texture_width}, $self->{layer_preview_z_texture_height}, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->ptr); + glTexSubImage2D_c(GL_TEXTURE_2D, 1, 0, 0, $self->{layer_preview_z_texture_width} / 2, $self->{layer_preview_z_texture_height} / 2, + GL_RGBA, GL_UNSIGNED_BYTE, $volume->{layer_height_texture_data}->offset($self->{layer_preview_z_texture_width} * $self->{layer_preview_z_texture_height} * 4)); + + # Render the color bar. + glDisable(GL_DEPTH_TEST); + # The viewport and camera are set to complete view and glOrtho(-$x/2, $x/2, -$y/2, $y/2, -$depth, $depth), + # where x, y is the window size divided by $self->_zoom. + glPushMatrix(); + glLoadIdentity(); + # Paint the overlay. + glBegin(GL_QUADS); + glVertex3f($bar_left, $bar_bottom, 0); + glVertex3f($bar_right, $bar_bottom, 0); + glVertex3f($bar_right, $bar_top, $volume->{bounding_box}->z_max); + glVertex3f($bar_left, $bar_top, $volume->{bounding_box}->z_max); + glEnd(); + glBindTexture(GL_TEXTURE_2D, 0); + $self->{shader}->Disable; + + # Paint the graph. + my $object_idx = int($volume->select_group_id / 1000000); + my $print_object = $self->{print}->get_object($object_idx); + my $max_z = unscale($print_object->size->z); + my $profile = $print_object->layer_height_profile; + my $layer_height = $print_object->config->get('layer_height'); + # Baseline + glColor3f(0., 0., 0.); + glBegin(GL_LINE_STRIP); + glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / 0.45, $bar_bottom); + glVertex2f($bar_left + $layer_height * ($bar_right - $bar_left) / 0.45, $bar_top); + glEnd(); + # Curve + glColor3f(0., 0., 1.); + glBegin(GL_LINE_STRIP); + for (my $i = 0; $i < int(@{$profile}); $i += 2) { + my $z = $profile->[$i]; + my $h = $profile->[$i+1]; + glVertex3f($bar_left + $h * ($bar_right - $bar_left) / 0.45, $bar_bottom + $z * ($bar_top - $bar_bottom) / $max_z, $z); + } + glEnd(); + # Revert the matrices. + glPopMatrix(); + glEnable(GL_DEPTH_TEST); +} + sub _report_opengl_state { my ($self, $comment) = @_; @@ -1069,6 +1382,112 @@ sub _report_opengl_state } } +sub _vertex_shader { + return <<'VERTEX'; +#version 110 + +#define LIGHT_TOP_DIR 0., 1., 0. +#define LIGHT_TOP_DIFFUSE 0.2 +#define LIGHT_TOP_SPECULAR 0.3 + +#define LIGHT_FRONT_DIR 0., 0., 1. +#define LIGHT_FRONT_DIFFUSE 0.5 +#define LIGHT_FRONT_SPECULAR 0.3 + +#define INTENSITY_AMBIENT 0.1 + +uniform float z_to_texture_row; +varying float intensity_specular; +varying float intensity_tainted; +varying float object_z; + +void main() +{ + vec3 eye, normal, lightDir, viewVector, halfVector; + float NdotL, NdotHV; + +// eye = gl_ModelViewMatrixInverse[3].xyz; + eye = vec3(0., 0., 1.); + + // First transform the normal into eye space and normalize the result. + normal = normalize(gl_NormalMatrix * gl_Normal); + + // Now normalize the light's direction. Note that according to the OpenGL specification, the light is stored in eye space. + // Also since we're talking about a directional light, the position field is actually direction. + lightDir = vec3(LIGHT_TOP_DIR); + halfVector = normalize(lightDir + eye); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + NdotL = max(dot(normal, lightDir), 0.0); + + intensity_tainted = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + intensity_specular = 0.; + +// if (NdotL > 0.0) +// intensity_specular = LIGHT_TOP_SPECULAR * pow(max(dot(normal, halfVector), 0.0), gl_FrontMaterial.shininess); + + // Perform the same lighting calculation for the 2nd light source. + lightDir = vec3(LIGHT_FRONT_DIR); + halfVector = normalize(lightDir + eye); + NdotL = max(dot(normal, lightDir), 0.0); + intensity_tainted += NdotL * LIGHT_FRONT_DIFFUSE; + + // compute the specular term if NdotL is larger than zero + if (NdotL > 0.0) + intensity_specular += LIGHT_FRONT_SPECULAR * pow(max(dot(normal, halfVector), 0.0), gl_FrontMaterial.shininess); + + // Scaled to widths of the Z texture. + object_z = gl_Vertex.z / gl_Vertex.w; + + gl_Position = ftransform(); +} + +VERTEX +} + +sub _fragment_shader { + return <<'FRAGMENT'; +#version 110 + +#define M_PI 3.1415926535897932384626433832795 + +// 2D texture (1D texture split by the rows) of color along the object Z axis. +uniform sampler2D z_texture; +// Scaling from the Z texture rows coordinate to the normalized texture row coordinate. +uniform float z_to_texture_row; +uniform float z_texture_row_to_normalized; + +varying float intensity_specular; +varying float intensity_tainted; +varying float object_z; +uniform float z_cursor; +uniform float z_cursor_band_width; + +void main() +{ + float object_z_row = z_to_texture_row * object_z; + // Index of the row in the texture. + float z_texture_row = floor(object_z_row); + // Normalized coordinate from 0. to 1. + float z_texture_col = object_z_row - z_texture_row; +// float z_blend = 0.5 + 0.5 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) / 3.))); +// float z_blend = 0.5 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor)))) + 0.5; + float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; + // Scale z_texture_row to normalized coordinates. + // Sample the Z texture. + gl_FragColor = + vec4(intensity_specular, intensity_specular, intensity_specular, 1.) + +// intensity_tainted * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5)), -2.5); + (1. - z_blend) * intensity_tainted * texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5)), -200.) + + z_blend * vec4(1., 1., 0., 0.); + // and reset the transparency. + gl_FragColor.a = 1.; +} + +FRAGMENT +} + # Container for object geometry and selection status. package Slic3r::GUI::3DScene::Volume; use Moo; @@ -1076,6 +1495,8 @@ use Moo; has 'bounding_box' => (is => 'ro', required => 1); has 'origin' => (is => 'rw', default => sub { Slic3r::Pointf3->new(0,0,0) }); has 'color' => (is => 'ro', required => 1); +# An ID containing the object ID, volume ID and instance ID. +has 'composite_id' => (is => 'rw', default => sub { -1 }); # An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance. has 'select_group_id' => (is => 'rw', default => sub { -1 }); # An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance. @@ -1097,6 +1518,26 @@ has 'tverts' => (is => 'rw'); # The offsets stores tripples of (z_top, qverts_idx, tverts_idx) in a linear array. has 'offsets' => (is => 'rw'); +# RGBA texture along the Z axis of an object, to visualize layers by stripes colored by their height. +has 'layer_height_texture_data' => (is => 'rw'); +# Number of texture cells. +has 'layer_height_texture_cells' => (is => 'rw'); + +sub object_idx { + my ($self) = @_; + return $self->composite_id / 1000000; +} + +sub volume_idx { + my ($self) = @_; + return ($self->composite_id / 1000) % 1000; +} + +sub instance_idx { + my ($self) = @_; + return $self->composite_id % 1000; +} + sub transformed_bounding_box { my ($self) = @_; @@ -1122,8 +1563,6 @@ __PACKAGE__->mk_accessors(qw( color_by select_by drag_by - volumes_by_object - _objects_by_volumes )); sub new { @@ -1133,14 +1572,12 @@ sub new { $self->color_by('volume'); # object | volume $self->select_by('object'); # object | volume | instance $self->drag_by('instance'); # object | instance - $self->volumes_by_object({}); # obj_idx => [ volume_idx, volume_idx ... ] - $self->_objects_by_volumes({}); # volume_idx => [ obj_idx, instance_idx ] return $self; } sub load_object { - my ($self, $model, $obj_idx, $instance_idxs) = @_; + my ($self, $model, $print, $obj_idx, $instance_idxs) = @_; my $model_object; if ($model->isa('Slic3r::Model::Object')) { @@ -1152,6 +1589,19 @@ sub load_object { } $instance_idxs ||= [0..$#{$model_object->instances}]; + + # Object will have a single common layer height texture for all volumes. + my $layer_height_texture_data; + my $layer_height_texture_cells; + if ($print && $obj_idx < $print->object_count) { + # Generate the layer height texture. Allocate data for the 0th and 1st mipmap levels. + $layer_height_texture_data = OpenGL::Array->new($self->{layer_preview_z_texture_width}*$self->{layer_preview_z_texture_height}*5, GL_UNSIGNED_BYTE); +# $print->get_object($obj_idx)->update_layer_height_profile_from_ranges(); + $layer_height_texture_cells = $print->get_object($obj_idx)->generate_layer_height_texture( + $layer_height_texture_data->ptr, + $self->{layer_preview_z_texture_height}, + $self->{layer_preview_z_texture_width}); + } my @volumes_idx = (); foreach my $volume_idx (0..$#{$model_object->volumes}) { @@ -1174,16 +1624,18 @@ sub load_object { # not correspond to the color of the filament. my $color = [ @{COLORS->[ $color_idx % scalar(@{&COLORS}) ]} ]; $color->[3] = $volume->modifier ? 0.5 : 1; + print "Reloading object $volume_idx, $instance_idx\n"; push @{$self->volumes}, my $v = Slic3r::GUI::3DScene::Volume->new( bounding_box => $mesh->bounding_box, color => $color, ); + $v->composite_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx); if ($self->select_by eq 'object') { $v->select_group_id($obj_idx*1000000); } elsif ($self->select_by eq 'volume') { $v->select_group_id($obj_idx*1000000 + $volume_idx*1000); } elsif ($self->select_by eq 'instance') { - $v->select_group_id($obj_idx*1000000 + $volume_idx*1000 + $instance_idx); + $v->select_group_id($v->composite_id); } if ($self->drag_by eq 'object') { $v->drag_group_id($obj_idx*1000); @@ -1191,15 +1643,18 @@ sub load_object { $v->drag_group_id($obj_idx*1000 + $instance_idx); } push @volumes_idx, my $scene_volume_idx = $#{$self->volumes}; - $self->_objects_by_volumes->{$scene_volume_idx} = [ $obj_idx, $volume_idx, $instance_idx ]; my $verts = Slic3r::GUI::_3DScene::GLVertexArray->new; $verts->load_mesh($mesh); $v->tverts($verts); + + if (! $volume->modifier) { + $v->layer_height_texture_data($layer_height_texture_data); + $v->layer_height_texture_cells($layer_height_texture_cells); + } } } - $self->volumes_by_object->{$obj_idx} = [@volumes_idx]; return @volumes_idx; } @@ -1538,19 +1993,4 @@ sub _extrusionentity_to_verts { $tverts); } -sub object_idx { - my ($self, $volume_idx) = @_; - return $self->_objects_by_volumes->{$volume_idx}[0]; -} - -sub volume_idx { - my ($self, $volume_idx) = @_; - return $self->_objects_by_volumes->{$volume_idx}[1]; -} - -sub instance_idx { - my ($self, $volume_idx) = @_; - return $self->_objects_by_volumes->{$volume_idx}[2]; -} - 1; diff --git a/lib/Slic3r/GUI/GLShader.pm b/lib/Slic3r/GUI/GLShader.pm new file mode 100644 index 000000000..766fe0807 --- /dev/null +++ b/lib/Slic3r/GUI/GLShader.pm @@ -0,0 +1,184 @@ +############################################################ +# +# Stripped down from the Perl OpenGL::Shader package by Vojtech Bubnik +# to only support the GLSL shaders. The original source was not maintained +# and did not install properly through the CPAN archive, and it was unnecessary +# complex. +# +# Original copyright: +# +# Copyright 2007 Graphcomp - ALL RIGHTS RESERVED +# Author: Bob "grafman" Free - grafman@graphcomp.com +# +# This program is free software; you can redistribute it and/or +# modify it under the same terms as Perl itself. +# +############################################################ + +package Slic3r::GUI::GLShader; +use OpenGL(':all'); + +# Avoid cloning this class by the worker threads. +sub CLONE_SKIP { 1 } + +# Shader constructor +sub new +{ + # Check for required OpenGL extensions + return undef if (OpenGL::glpCheckExtension('GL_ARB_shader_objects')); + return undef if (OpenGL::glpCheckExtension('GL_ARB_fragment_shader')); + return undef if (OpenGL::glpCheckExtension('GL_ARB_vertex_shader')); + return undef if (OpenGL::glpCheckExtension('GL_ARB_shading_language_100')); +# my $glsl_version = glGetString(GL_SHADING_LANGUAGE_VERSION); +# my $glsl_version_ARB = glGetString(GL_SHADING_LANGUAGE_VERSION_ARB ); +# print "GLSL version: $glsl_version, ARB: $glsl_version_ARB\n"; + + my $this = shift; + my $class = ref($this) || $this; + my($type) = @_; + my $self = {type => uc($type)}; + bless($self,$class); + + # Get GL_SHADING_LANGUAGE_VERSION_ARB + my $shader_ver = glGetString(0x8B8C); + $shader_ver =~ m|([\d\.]+)|; + $self->{version} = $1 || '0'; + print + return $self; +} + +# Shader destructor +# Must be disabled first +sub DESTROY +{ + my($self) = @_; + + if ($self->{program}) + { + glDetachObjectARB($self->{program},$self->{fragment_id}) if ($self->{fragment_id}); + glDetachObjectARB($self->{program},$self->{vertex_id}) if ($self->{vertex_id}); + glDeleteProgramsARB_p($self->{program}); + } + + glDeleteProgramsARB_p($self->{fragment_id}) if ($self->{fragment_id}); + glDeleteProgramsARB_p($self->{vertex_id}) if ($self->{vertex_id}); +} + + +# Load shader strings +sub Load +{ + my($self,$fragment,$vertex) = @_; + + # Load fragment code + if ($fragment) + { + $self->{fragment_id} = glCreateShaderObjectARB(GL_FRAGMENT_SHADER); + return undef if (!$self->{fragment_id}); + + glShaderSourceARB_p($self->{fragment_id}, $fragment); + #my $frag = glGetShaderSourceARB_p($self->{fragment_id}); + #print STDERR "Loaded fragment:\n$frag\n"; + + glCompileShaderARB($self->{fragment_id}); + my $stat = glGetInfoLogARB_p($self->{fragment_id}); + return "Fragment shader: $stat" if ($stat); + } + + # Load vertex code + if ($vertex) + { + $self->{vertex_id} = glCreateShaderObjectARB(GL_VERTEX_SHADER); + return undef if (!$self->{vertex_id}); + + glShaderSourceARB_p($self->{vertex_id}, $vertex); + #my $vert = glGetShaderSourceARB_p($self->{vertex_id}); + #print STDERR "Loaded vertex:\n$vert\n"; + + glCompileShaderARB($self->{vertex_id}); + $stat = glGetInfoLogARB_p($self->{vertex_id}); + return "Vertex shader: $stat" if ($stat); + } + + + # Link shaders + my $sp = glCreateProgramObjectARB(); + glAttachObjectARB($sp, $self->{fragment_id}) if ($fragment); + glAttachObjectARB($sp, $self->{vertex_id}) if ($vertex); + glLinkProgramARB($sp); + my $linked = glGetObjectParameterivARB_p($sp, GL_OBJECT_LINK_STATUS_ARB); + if (!$linked) + { + $stat = glGetInfoLogARB_p($sp); + #print STDERR "Load shader: $stat\n"; + return "Link shader: $stat" if ($stat); + return 'Unable to link shader'; + } + + $self->{program} = $sp; + + return ''; +} + +# Enable shader +sub Enable +{ + my($self) = @_; + glUseProgramObjectARB($self->{program}) if ($self->{program}); +} + +# Disable shader +sub Disable +{ + my($self) = @_; + glUseProgramObjectARB(0) if ($self->{program}); +} + +# Return shader vertex attribute ID +sub MapAttr +{ + my($self,$attr) = @_; + return undef if (!$self->{program}); + my $id = glGetAttribLocationARB_p($self->{program},$attr); + return undef if ($id < 0); + return $id; +} + +# Return shader uniform variable ID +sub Map +{ + my($self,$var) = @_; + return undef if (!$self->{program}); + my $id = glGetUniformLocationARB_p($self->{program},$var); + return undef if ($id < 0); + return $id; +} + +# Set shader vector +sub SetVector +{ + my($self,$var,@values) = @_; + + my $id = $self->Map($var); + return 'Unable to map $var' if (!defined($id)); + + my $count = scalar(@values); + eval('glUniform'.$count.'fARB($id,@values)'); + + return ''; +} + +# Set shader 4x4 matrix +sub SetMatrix +{ + my($self,$var,$oga) = @_; + + my $id = $self->Map($var); + return 'Unable to map $var' if (!defined($id)); + + glUniformMatrix4fvARB_c($id,1,0,$oga->ptr()); + return ''; +} + +1; +__END__ diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index ca7143e28..f08adf672 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -42,6 +42,11 @@ sub new { $self->_init_tabpanel; $self->_init_menubar; + # set default tooltip timer in msec + # SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values + # (SetAutoPop is not available on GTK.) + eval { Wx::ToolTip::SetAutoPop(32767) }; + # initialize status bar $self->{statusbar} = Slic3r::GUI::ProgressStatusBar->new($self, -1); $self->{statusbar}->SetStatusText("Version $Slic3r::VERSION - Remember to check for updates at http://github.com/prusa3d/slic3r/releases"); @@ -292,7 +297,7 @@ sub _init_menubar { # View menu if (!$self->{no_plater}) { $self->{viewMenu} = Wx::Menu->new; - $self->_append_menu_item($self->{viewMenu}, "Default", 'Default View', sub { $self->select_view('default'); }); + $self->_append_menu_item($self->{viewMenu}, "Iso" , 'Iso View' , sub { $self->select_view('iso' ); }); $self->_append_menu_item($self->{viewMenu}, "Top" , 'Top View' , sub { $self->select_view('top' ); }); $self->_append_menu_item($self->{viewMenu}, "Bottom" , 'Bottom View' , sub { $self->select_view('bottom' ); }); $self->_append_menu_item($self->{viewMenu}, "Front" , 'Front View' , sub { $self->select_view('front' ); }); diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 54f66e221..ead1fc4fe 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -8,10 +8,11 @@ use utf8; use File::Basename qw(basename dirname); use List::Util qw(sum first max); use Slic3r::Geometry qw(X Y Z MIN MAX scale unscale deg2rad); +use LWP::UserAgent; use threads::shared qw(shared_clone); use Wx qw(:button :cursor :dialog :filedialog :keycode :icon :font :id :listctrl :misc - :panel :sizer :toolbar :window wxTheApp :notebook :combobox); -use Wx::Event qw(EVT_BUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED + :panel :sizer :toolbar :window wxTheApp :notebook :combobox wxNullBitmap); +use Wx::Event qw(EVT_BUTTON EVT_TOGGLEBUTTON EVT_COMMAND EVT_KEY_DOWN EVT_LIST_ITEM_ACTIVATED EVT_LIST_ITEM_DESELECTED EVT_LIST_ITEM_SELECTED EVT_MOUSE_EVENTS EVT_PAINT EVT_TOOL EVT_CHOICE EVT_COMBOBOX EVT_TIMER EVT_NOTEBOOK_PAGE_CHANGED); use base 'Wx::Panel'; @@ -30,6 +31,7 @@ use constant TB_SCALE => &Wx::NewId; use constant TB_SPLIT => &Wx::NewId; use constant TB_CUT => &Wx::NewId; use constant TB_SETTINGS => &Wx::NewId; +use constant TB_LAYER_EDITING => &Wx::NewId; # package variables to avoid passing lexicals to threads our $THUMBNAIL_DONE_EVENT : shared = Wx::NewEventType; @@ -94,7 +96,7 @@ sub new { # Initialize 3D plater if ($Slic3r::GUI::have_OpenGL) { - $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{config}); + $self->{canvas3D} = Slic3r::GUI::Plater::3D->new($self->{preview_notebook}, $self->{objects}, $self->{model}, $self->{print}, $self->{config}); $self->{preview_notebook}->AddPage($self->{canvas3D}, '3D'); $self->{canvas3D}->set_on_select_object($on_select_object); $self->{canvas3D}->set_on_double_click($on_double_click); @@ -154,6 +156,10 @@ sub new { $self->{htoolbar}->AddTool(TB_CUT, "Cut…", Wx::Bitmap->new($Slic3r::var->("package.png"), wxBITMAP_TYPE_PNG), ''); $self->{htoolbar}->AddSeparator; $self->{htoolbar}->AddTool(TB_SETTINGS, "Settings…", Wx::Bitmap->new($Slic3r::var->("cog.png"), wxBITMAP_TYPE_PNG), ''); + + # FIXME add a button for layer editing + $self->{htoolbar}->AddTool(TB_LAYER_EDITING, 'Layer Editing', Wx::Bitmap->new($Slic3r::var->("delete.png"), wxBITMAP_TYPE_PNG), wxNullBitmap, 1, undef, 'Layer Editing') + if (defined($ENV{'SLIC3R_EXPERIMENTAL'}) && $ENV{'SLIC3R_EXPERIMENTAL'} == 1); } else { my %tbar_buttons = ( add => "Add…", @@ -168,12 +174,17 @@ sub new { split => "Split", cut => "Cut…", settings => "Settings…", + layer_editing => "Layer editing", ); $self->{btoolbar} = Wx::BoxSizer->new(wxHORIZONTAL); for (qw(add remove reset arrange increase decrease rotate45ccw rotate45cw changescale split cut settings)) { $self->{"btn_$_"} = Wx::Button->new($self, -1, $tbar_buttons{$_}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); $self->{btoolbar}->Add($self->{"btn_$_"}); } + if (defined($ENV{'SLIC3R_EXPERIMENTAL'}) && $ENV{'SLIC3R_EXPERIMENTAL'} == 1) { + $self->{"btn_layer_editing"} = Wx::ToggleButton->new($self, -1, $tbar_buttons{'layer_editing'}, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); + $self->{btoolbar}->Add($self->{"btn_layer_editing"}); + } } $self->{list} = Wx::ListView->new($self, -1, wxDefaultPosition, wxDefaultSize, @@ -256,6 +267,11 @@ sub new { EVT_TOOL($self, TB_SPLIT, sub { $self->split_object; }); EVT_TOOL($self, TB_CUT, sub { $_[0]->object_cut_dialog }); EVT_TOOL($self, TB_SETTINGS, sub { $_[0]->object_settings_dialog }); + EVT_TOOL($self, TB_LAYER_EDITING, sub { + my $state = $self->{canvas3D}->layer_editing_enabled; + $self->{htoolbar}->ToggleTool(TB_LAYER_EDITING, ! $state); + $self->on_layer_editing_toggled(! $state); + }); } else { EVT_BUTTON($self, $self->{btn_add}, sub { $self->add; }); EVT_BUTTON($self, $self->{btn_remove}, sub { $self->remove() }); # explicitly pass no argument to remove @@ -269,6 +285,7 @@ sub new { EVT_BUTTON($self, $self->{btn_split}, sub { $self->split_object; }); EVT_BUTTON($self, $self->{btn_cut}, sub { $_[0]->object_cut_dialog }); EVT_BUTTON($self, $self->{btn_settings}, sub { $_[0]->object_settings_dialog }); + EVT_TOGGLEBUTTON($self, $self->{btn_layer_editing}, sub { $self->on_layer_editing_toggled($self->{btn_layer_editing}->GetValue); }); } $_->SetDropTarget(Slic3r::GUI::Plater::DropTarget->new($self)) @@ -464,6 +481,12 @@ sub _on_select_preset { $self->on_config_change($self->GetFrame->config); } +sub on_layer_editing_toggled { + my ($self, $new_state) = @_; + $self->{canvas3D}->layer_editing_enabled($new_state); + $self->{canvas3D}->update; +} + sub GetFrame { my ($self) = @_; return &Wx::GetTopLevelParent($self); @@ -1481,6 +1504,8 @@ sub on_thumbnail_made { # (i.e. when an object is added/removed/moved/rotated/scaled) sub update { my ($self, $force_autocenter) = @_; + + print "Platter - update\n"; if ($Slic3r::GUI::Settings->{_}{autocenter} || $force_autocenter) { $self->{model}->center_instances_around_point($self->bed_centerf); @@ -1679,7 +1704,7 @@ sub object_list_changed { my $have_objects = @{$self->{objects}} ? 1 : 0; my $method = $have_objects ? 'Enable' : 'Disable'; $self->{"btn_$_"}->$method - for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode); + for grep $self->{"btn_$_"}, qw(reset arrange reslice export_gcode export_stl print send_gcode layer_editing); if ($self->{export_gcode_output_file} || $self->{send_gcode_file}) { $self->{btn_reslice}->Disable; @@ -1712,6 +1737,7 @@ sub selection_changed { if ($self->{object_info_size}) { # have we already loaded the info pane? if ($have_sel) { my $model_object = $self->{model}->objects->[$obj_idx]; + $model_object->print_info; my $model_instance = $model_object->instances->[0]; $self->{object_info_size}->SetLabel(sprintf("%.2f x %.2f x %.2f", @{$model_object->instance_bounding_box(0)->size})); $self->{object_info_materials}->SetLabel($model_object->materials_count); diff --git a/lib/Slic3r/GUI/Plater/2D.pm b/lib/Slic3r/GUI/Plater/2D.pm index 8fb8908e1..a24a5a6ff 100644 --- a/lib/Slic3r/GUI/Plater/2D.pm +++ b/lib/Slic3r/GUI/Plater/2D.pm @@ -173,7 +173,7 @@ sub repaint { # if sequential printing is enabled and we have more than one object, draw clearance area if ($self->{config}->complete_objects && (map @{$_->instances}, @{$self->{model}->objects}) > 1) { - my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), 1, JT_ROUND, scale(0.1))}; + my ($clearance) = @{offset([$thumbnail->convex_hull], (scale($self->{config}->extruder_clearance_radius) / 2), JT_ROUND, scale(0.1))}; $dc->SetPen($self->{clearance_pen}); $dc->SetBrush($self->{transparent_brush}); $dc->DrawPolygon($self->scaled_points_to_pixel($clearance, 1), 0, 0); @@ -185,7 +185,7 @@ sub repaint { if (@{$self->{objects}} && $self->{config}->skirts) { my @points = map @{$_->contour}, map @$_, map @{$_->instance_thumbnails}, @{$self->{objects}}; if (@points >= 3) { - my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), 1, JT_ROUND, scale(0.1))}; + my ($convex_hull) = @{offset([convex_hull(\@points)], scale max($self->{config}->brim_width + $self->{config}->skirt_distance), JT_ROUND, scale(0.1))}; $dc->SetPen($self->{skirt_pen}); $dc->SetBrush($self->{transparent_brush}); $dc->DrawPolygon($self->scaled_points_to_pixel($convex_hull, 1), 0, 0); diff --git a/lib/Slic3r/GUI/Plater/3D.pm b/lib/Slic3r/GUI/Plater/3D.pm index 07818a2b5..ce56c67b4 100644 --- a/lib/Slic3r/GUI/Plater/3D.pm +++ b/lib/Slic3r/GUI/Plater/3D.pm @@ -12,7 +12,7 @@ use base qw(Slic3r::GUI::3DScene Class::Accessor); sub new { my $class = shift; - my ($parent, $objects, $model, $config) = @_; + my ($parent, $objects, $model, $print, $config) = @_; my $self = $class->SUPER::new($parent); $self->enable_picking(1); @@ -22,6 +22,7 @@ sub new { $self->{objects} = $objects; $self->{model} = $model; + $self->{print} = $print; $self->{config} = $config; $self->{on_select_object} = sub {}; $self->{on_instances_moved} = sub {}; @@ -31,7 +32,7 @@ sub new { my $obj_idx = undef; if ($volume_idx != -1) { - $obj_idx = $self->object_idx($volume_idx); + $obj_idx = $self->volumes->[$volume_idx]->object_idx; } $self->{on_select_object}->($obj_idx) if $self->{on_select_object}; @@ -42,8 +43,8 @@ sub new { my %done = (); # prevent moving instances twice foreach my $volume_idx (@volume_idxs) { my $volume = $self->volumes->[$volume_idx]; - my $obj_idx = $self->object_idx($volume_idx); - my $instance_idx = $self->instance_idx($volume_idx); + my $obj_idx = $volume->object_idx; + my $instance_idx = $volume->instance_idx; next if $done{"${obj_idx}_${instance_idx}"}; $done{"${obj_idx}_${instance_idx}"} = 1; @@ -89,7 +90,7 @@ sub update { $self->update_bed_size; foreach my $obj_idx (0..$#{$self->{model}->objects}) { - my @volume_idxs = $self->load_object($self->{model}, $obj_idx); + my @volume_idxs = $self->load_object($self->{model}, $self->{print}, $obj_idx); if ($self->{objects}[$obj_idx]->selected) { $self->select_volume($_) for @volume_idxs; diff --git a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm index 9e4b33976..7889b3d66 100644 --- a/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm +++ b/lib/Slic3r/GUI/Plater/ObjectCutDialog.pm @@ -114,7 +114,7 @@ sub new { my $canvas; if ($Slic3r::GUI::have_OpenGL) { $canvas = $self->{canvas} = Slic3r::GUI::3DScene->new($self); - $canvas->load_object($self->{model_object}, undef, [0]); + $canvas->load_object($self->{model_object}, undef, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->SetMinSize($canvas->GetSize); @@ -244,7 +244,7 @@ sub _update { } $self->{canvas}->reset_objects; - $self->{canvas}->load_object($_, undef, [0]) for @objects; + $self->{canvas}->load_object($_, undef, undef, [0]) for @objects; $self->{canvas}->SetCuttingPlane( $self->{cut_options}{z}, [@expolygons], diff --git a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm index 4882233a6..51a58366c 100644 --- a/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm +++ b/lib/Slic3r/GUI/Plater/ObjectPartsPanel.pm @@ -22,6 +22,7 @@ sub new { my $self = $class->SUPER::new($parent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); my $object = $self->{model_object} = $params{model_object}; + my $print_object = $self->{print_object} = $params{print_object}; # create TreeCtrl my $tree = $self->{tree} = Wx::TreeCtrl->new($self, -1, wxDefaultPosition, [300, 100], @@ -82,7 +83,7 @@ sub new { $self->reload_tree($canvas->volume_idx($volume_idx)); }); - $canvas->load_object($self->{model_object}, undef, [0]); + $canvas->load_object($self->{model_object}, undef, undef, [0]); $canvas->set_auto_bed_shape; $canvas->SetSize([500,500]); $canvas->zoom_to_volumes; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index f91f273f9..3cf914862 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -523,6 +523,7 @@ sub build { max_volumetric_extrusion_rate_slope_positive max_volumetric_extrusion_rate_slope_negative perimeter_speed small_perimeter_speed external_perimeter_speed infill_speed solid_infill_speed top_solid_infill_speed support_material_speed + support_material_xy_spacing support_material_interface_speed bridge_speed gap_fill_speed travel_speed first_layer_speed @@ -532,8 +533,8 @@ sub build { brim_width support_material support_material_threshold support_material_enforce_layers raft_layers - support_material_pattern support_material_with_sheath support_material_spacing support_material_angle - support_material_interface_layers support_material_interface_spacing + support_material_pattern support_material_with_sheath support_material_spacing support_material_synchronize_layers support_material_angle + support_material_interface_layers support_material_interface_spacing support_material_interface_contact_loops support_material_contact_distance support_material_buildplate_only dont_support_bridges notes complete_objects extruder_clearance_radius extruder_clearance_height @@ -646,8 +647,11 @@ sub build { $optgroup->append_single_option_line('support_material_angle'); $optgroup->append_single_option_line('support_material_interface_layers'); $optgroup->append_single_option_line('support_material_interface_spacing'); + $optgroup->append_single_option_line('support_material_interface_contact_loops'); $optgroup->append_single_option_line('support_material_buildplate_only'); + $optgroup->append_single_option_line('support_material_xy_spacing'); $optgroup->append_single_option_line('dont_support_bridges'); + $optgroup->append_single_option_line('support_material_synchronize_layers'); } } @@ -910,12 +914,12 @@ sub _update { my $have_support_interface = $config->support_material_interface_layers > 0; $self->get_field($_)->toggle($have_support_material) for qw(support_material_threshold support_material_pattern support_material_with_sheath - support_material_spacing support_material_angle + support_material_spacing support_material_synchronize_layers support_material_angle support_material_interface_layers dont_support_bridges - support_material_extrusion_width support_material_contact_distance); + support_material_extrusion_width support_material_contact_distance support_material_xy_spacing); $self->get_field($_)->toggle($have_support_material && $have_support_interface) for qw(support_material_interface_spacing support_material_interface_extruder - support_material_interface_speed); + support_material_interface_speed support_material_interface_contact_loops); $self->get_field('perimeter_extrusion_width')->toggle($have_perimeters || $have_skirt || $have_brim); $self->get_field('support_material_extruder')->toggle($have_support_material || $have_skirt); diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 652abb8db..37fc89914 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -5,9 +5,9 @@ use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw(offset offset_ex - diff_ex diff union_ex intersection_ex xor_ex JT_ROUND JT_MITER - JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex - intersection intersection_pl diff_pl union CLIPPER_OFFSET_SCALE - union_pt_chained diff_ppl intersection_ppl); + diff_ex diff union_ex intersection_ex JT_ROUND JT_MITER + JT_SQUARE is_counter_clockwise offset2 offset2_ex + intersection intersection_pl diff_pl union + union_pt_chained); 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 187ee84e5..47a58c3a0 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -41,8 +41,12 @@ sub size { sub process { my ($self) = @_; + $self->status_cb->(20, "Generating perimeters"); $_->make_perimeters for @{$self->objects}; + + $self->status_cb->(70, "Infilling layers"); $_->infill for @{$self->objects}; + $_->generate_support_material for @{$self->objects}; $self->make_skirt; $self->make_brim; # must come after make_skirt @@ -276,7 +280,7 @@ sub make_skirt { my $distance = scale max($self->config->skirt_distance, $self->config->brim_width); for (my $i = $skirts; $i > 0; $i--) { $distance += scale $spacing; - my $loop = offset([$convex_hull], $distance, 1, JT_ROUND, scale(0.1))->[0]; + my $loop = offset([$convex_hull], $distance, JT_ROUND, scale(0.1))->[0]; my $eloop = Slic3r::ExtrusionLoop->new_from_paths( Slic3r::ExtrusionPath->new( polyline => Slic3r::Polygon->new(@$loop)->split_at_first_point, @@ -369,7 +373,7 @@ sub make_brim { # -0.5 because islands are not represented by their centerlines # (first offset more, then step back - reverse order than the one used for # perimeters because here we're offsetting outwards) - push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, 100000, JT_SQUARE)}; + push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, JT_SQUARE)}; } $self->brim->append(map Slic3r::ExtrusionLoop->new_from_paths( diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index d1b0009ad..f7fdf1a4e 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -262,7 +262,10 @@ sub export { $gcodegen->avoid_crossing_perimeters->set_disable_once(1); } - my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; + # Order layers by print_z, support layers preceding the object layers. + my @layers = sort + { ($a->print_z == $b->print_z) ? ($a->isa('Slic3r::Layer::Support') ? -1 : 0) : $a->print_z <=> $b->print_z } + @{$object->layers}, @{$object->support_layers}; for my $layer (@layers) { # if we are printing the bottom layer of an object, and we have already finished # another one, set first layer temperatures. this happens before the Z move @@ -289,7 +292,8 @@ sub export { my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx foreach my $obj_idx (0 .. ($self->print->object_count - 1)) { my $object = $self->objects->[$obj_idx]; - foreach my $layer (@{$object->layers}, @{$object->support_layers}) { + # Collect the object layers by z, support layers first, object layers second. + foreach my $layer (@{$object->support_layers}, @{$object->layers}) { $layers{ $layer->print_z } ||= []; $layers{ $layer->print_z }[$obj_idx] ||= []; push @{$layers{ $layer->print_z }[$obj_idx]}, $layer; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 8fd2bf4cf..c0d80629c 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -7,7 +7,7 @@ use List::Util qw(min max sum first); use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(X Y Z PI scale unscale chained_path epsilon); use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex - offset offset_ex offset2 offset2_ex intersection_ppl CLIPPER_OFFSET_SCALE JT_MITER); + offset offset_ex offset2 offset2_ex JT_MITER); use Slic3r::Print::State ':steps'; use Slic3r::Surface ':types'; @@ -45,223 +45,7 @@ sub slice { $self->set_step_started(STEP_SLICE); $self->print->status_cb->(10, "Processing triangulated mesh"); - # init layers - { - $self->clear_layers; - - # make layers taking custom heights into account - my $id = 0; - my $print_z = 0; - my $first_object_layer_height = -1; - my $first_object_layer_distance = -1; - - # add raft layers - if ($self->config->raft_layers > 0) { - # Reserve object layers for the raft. Last layer of the raft is the contact layer. - $id += $self->config->raft_layers; - - # Raise first object layer Z by the thickness of the raft itself - # plus the extra distance required by the support material logic. - #FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case. - my $first_layer_height = $self->config->get_value('first_layer_height'); - $print_z += $first_layer_height; - - # Use as large as possible layer height for the intermediate raft layers. - my $support_material_layer_height; - { - my @nozzle_diameters = ( - map $self->print->config->get_at('nozzle_diameter', $_), - $self->config->support_material_extruder-1, - $self->config->support_material_interface_extruder-1, - ); - $support_material_layer_height = 0.75 * min(@nozzle_diameters); - } - $print_z += $support_material_layer_height * ($self->config->raft_layers - 1); - - # compute the average of all nozzles used for printing the object - #FIXME It is expected, that the 1st layer of the object is printed with a bridging flow over a full raft. Shall it not be vice versa? - my $nozzle_diameter; - { - my @nozzle_diameters = ( - map $self->print->config->get_at('nozzle_diameter', $_), @{$self->print->object_extruders} - ); - $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; - } - $first_object_layer_distance = $self->_support_material->contact_distance($self->config->layer_height, $nozzle_diameter); - - # force first layer print_z according to the contact distance - # (the loop below will raise print_z by such height) - $first_object_layer_height = $first_object_layer_distance - $self->config->support_material_contact_distance; - } - - # loop until we have at least one layer and the max slice_z reaches the object height - my $slice_z = 0; - my $height = 0; - my $max_z = unscale($self->size->z); - while (($slice_z - $height) <= $max_z) { - # assign the default height to the layer according to the general settings - $height = ($id == 0) - ? $self->config->get_value('first_layer_height') - : $self->config->layer_height; - - # look for an applicable custom range - if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { - $height = $range->[2]; - - # if user set custom height to zero we should just skip the range and resume slicing over it - if ($height == 0) { - $slice_z += $range->[1] - $range->[0]; - next; - } - } - - if ($first_object_layer_height != -1 && !@{$self->layers}) { - $height = $first_object_layer_height; - $print_z += ($first_object_layer_distance - $height); - } - - $print_z += $height; - $slice_z += $height/2; - - ### Slic3r::debugf "Layer %d: height = %s; slice_z = %s; print_z = %s\n", $id, $height, $slice_z, $print_z; - - $self->add_layer($id, $height, $print_z, $slice_z); - if ($self->layer_count >= 2) { - my $lc = $self->layer_count; - $self->get_layer($lc - 2)->set_upper_layer($self->get_layer($lc - 1)); - $self->get_layer($lc - 1)->set_lower_layer($self->get_layer($lc - 2)); - } - $id++; - - $slice_z += $height/2; # add the other half layer - } - } - - # make sure all layers contain layer region objects for all regions - my $regions_count = $self->print->region_count; - foreach my $layer (@{ $self->layers }) { - $layer->region($_) for 0 .. ($regions_count-1); - } - - # get array of Z coordinates for slicing - my @z = map $_->slice_z, @{$self->layers}; - - # slice all non-modifier volumes - for my $region_id (0..($self->region_count - 1)) { - my $expolygons_by_layer = $self->_slice_region($region_id, \@z, 0); - for my $layer_id (0..$#$expolygons_by_layer) { - my $layerm = $self->get_layer($layer_id)->regions->[$region_id]; - $layerm->slices->clear; - foreach my $expolygon (@{ $expolygons_by_layer->[$layer_id] }) { - $layerm->slices->append(Slic3r::Surface->new( - expolygon => $expolygon, - surface_type => S_TYPE_INTERNAL, - )); - } - } - } - - # then slice all modifier volumes - if ($self->region_count > 1) { - for my $region_id (0..$self->region_count) { - my $expolygons_by_layer = $self->_slice_region($region_id, \@z, 1); - - # loop through the other regions and 'steal' the slices belonging to this one - for my $other_region_id (0..$self->region_count) { - next if $other_region_id == $region_id; - - for my $layer_id (0..$#$expolygons_by_layer) { - my $layerm = $self->get_layer($layer_id)->regions->[$region_id]; - my $other_layerm = $self->get_layer($layer_id)->regions->[$other_region_id]; - next if !defined $other_layerm; - - my $other_slices = [ map $_->p, @{$other_layerm->slices} ]; # Polygons - my $my_parts = intersection_ex( - $other_slices, - [ map @$_, @{ $expolygons_by_layer->[$layer_id] } ], - ); - next if !@$my_parts; - - # append new parts to our region - foreach my $expolygon (@$my_parts) { - $layerm->slices->append(Slic3r::Surface->new( - expolygon => $expolygon, - surface_type => S_TYPE_INTERNAL, - )); - } - - # remove such parts from original region - $other_layerm->slices->clear; - $other_layerm->slices->append(Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNAL, - )) for @{ diff_ex($other_slices, [ map @$_, @$my_parts ]) }; - } - } - } - } - - # remove last layer(s) if empty - $self->delete_layer($self->layer_count - 1) - while $self->layer_count && (!map @{$_->slices}, @{$self->get_layer($self->layer_count - 1)->regions}); - - foreach my $layer (@{ $self->layers }) { - # apply size compensation - if ($self->config->xy_size_compensation != 0) { - my $delta = scale($self->config->xy_size_compensation); - if (@{$layer->regions} == 1) { - # single region - my $layerm = $layer->regions->[0]; - my $slices = [ map $_->p, @{$layerm->slices} ]; - $layerm->slices->clear; - $layerm->slices->append(Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNAL, - )) for @{offset_ex($slices, $delta)}; - } else { - if ($delta < 0) { - # multiple regions, shrinking - # we apply the offset to the combined shape, then intersect it - # with the original slices for each region - my $slices = union([ map $_->p, map @{$_->slices}, @{$layer->regions} ]); - $slices = offset($slices, $delta); - foreach my $layerm (@{$layer->regions}) { - my $this_slices = intersection_ex( - $slices, - [ map $_->p, @{$layerm->slices} ], - ); - $layerm->slices->clear; - $layerm->slices->append(Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNAL, - )) for @$this_slices; - } - } else { - # multiple regions, growing - # this is an ambiguous case, since it's not clear how to grow regions where they are going to overlap - # so we give priority to the first one and so on - for my $i (0..$#{$layer->regions}) { - my $layerm = $layer->regions->[$i]; - my $slices = offset_ex([ map $_->p, @{$layerm->slices} ], $delta); - if ($i > 0) { - $slices = diff_ex( - [ map @$_, @$slices ], - [ map $_->p, map @{$_->slices}, map $layer->regions->[$_], 0..($i-1) ], # slices of already processed regions - ); - } - $layerm->slices->clear; - $layerm->slices->append(Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_INTERNAL, - )) for @$slices; - } - } - } - } - - # Merge all regions' slices to get islands, chain them by a shortest path. - $layer->make_slices; - } + $self->_slice; # detect slicing errors my $warning_thrown = 0; @@ -334,151 +118,15 @@ sub slice { $self->set_step_done(STEP_SLICE); } -# called from slice() -sub _slice_region { - my ($self, $region_id, $z, $modifier) = @_; - - return [] if !@{$self->get_region_volumes($region_id)}; - - # compose mesh - my $mesh; - foreach my $volume_id (@{ $self->get_region_volumes($region_id) }) { - my $volume = $self->model_object->volumes->[$volume_id]; - next if $volume->modifier && !$modifier; - next if !$volume->modifier && $modifier; - - if (defined $mesh) { - $mesh->merge($volume->mesh); - } else { - $mesh = $volume->mesh->clone; - } - } - return if !defined $mesh; - - # transform mesh - # we ignore the per-instance transformations currently and only - # consider the first one - $self->model_object->instances->[0]->transform_mesh($mesh, 1); - - # align mesh to Z = 0 (it should be already aligned actually) and apply XY shift - $mesh->translate((map unscale(-$_), @{$self->_copies_shift}), -$self->model_object->bounding_box->z_min); - - # perform actual slicing - return $mesh->slice($z); -} - # 1) Merges typed region slices into stInternal type. # 2) Increases an "extra perimeters" counter at region slices where needed. # 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). sub make_perimeters { - my $self = shift; + my ($self) = @_; # prerequisites $self->slice; - - return if $self->step_done(STEP_PERIMETERS); - $self->set_step_started(STEP_PERIMETERS); - $self->print->status_cb->(20, "Generating perimeters"); - - # Merge region slices if they were split into types. - # FIXME this is using a safety offset, so the region slices will be slightly bigger with each iteration. - if ($self->typed_slices) { - $_->merge_slices for @{$self->layers}; - $self->set_typed_slices(0); - $self->invalidate_step(STEP_PREPARE_INFILL); - } - - # compare each layer to the one below, and mark those slices needing - # one additional inner perimeter, like the top of domed objects- - - # this algorithm makes sure that at least one perimeter is overlapping - # but we don't generate any extra perimeter if fill density is zero, as they would be floating - # inside the object - infill_only_where_needed should be the method of choice for printing - # hollow objects - for my $region_id (0 .. ($self->print->region_count-1)) { - my $region = $self->print->regions->[$region_id]; - my $region_perimeters = $region->config->perimeters; - - next if !$region->config->extra_perimeters; - next if $region_perimeters == 0; - next if $region->config->fill_density == 0; - - for my $i (0 .. ($self->layer_count - 2)) { - my $layerm = $self->get_layer($i)->get_region($region_id); - my $upper_layerm = $self->get_layer($i+1)->get_region($region_id); - my $upper_layerm_polygons = [ map $_->p, @{$upper_layerm->slices} ]; - # Filter upper layer polygons in intersection_ppl by their bounding boxes? - # my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; - my $total_loop_length = sum(map $_->length, @$upper_layerm_polygons) // 0; - - my $perimeter_spacing = $layerm->flow(FLOW_ROLE_PERIMETER)->scaled_spacing; - my $ext_perimeter_flow = $layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER); - my $ext_perimeter_width = $ext_perimeter_flow->scaled_width; - my $ext_perimeter_spacing = $ext_perimeter_flow->scaled_spacing; - - foreach my $slice (@{$layerm->slices}) { - while (1) { - # compute the total thickness of perimeters - my $perimeters_thickness = $ext_perimeter_width/2 + $ext_perimeter_spacing/2 - + ($region_perimeters-1 + $slice->extra_perimeters) * $perimeter_spacing; - - # define a critical area where we don't want the upper slice to fall into - # (it should either lay over our perimeters or outside this area) - my $critical_area_depth = $perimeter_spacing*1.5; - my $critical_area = diff( - offset($slice->expolygon->arrayref, -$perimeters_thickness), - offset($slice->expolygon->arrayref, -($perimeters_thickness + $critical_area_depth)), - ); - - # check whether a portion of the upper slices falls inside the critical area - my $intersection = intersection_ppl( - $upper_layerm_polygons, - $critical_area, - ); - - # only add an additional loop if at least 30% of the slice loop would benefit from it - my $total_intersection_length = sum(map $_->length, @$intersection) // 0; - last unless $total_intersection_length > $total_loop_length*0.3; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "extra.svg", - no_arrows => 1, - expolygons => union_ex($critical_area), - polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], - ); - } - - $slice->extra_perimeters($slice->extra_perimeters + 1); - } - Slic3r::debugf " adding %d more perimeter(s) at layer %d\n", - $slice->extra_perimeters, $layerm->layer->id - if $slice->extra_perimeters > 0; - } - } - } - - Slic3r::parallelize( - threads => $self->print->config->threads, - items => sub { 0 .. ($self->layer_count - 1) }, - thread_cb => sub { - my $q = shift; - while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_perimeters; - } - }, - no_threads_cb => sub { - $_->make_perimeters for @{$self->layers}; - }, - ); - - # simplify slices (both layer and region slices), - # we only need the max resolution for perimeters - ### This makes this method not-idempotent, so we keep it disabled for now. - ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); - - $self->set_step_done(STEP_PERIMETERS); + $self->_make_perimeters; } sub prepare_infill { @@ -598,32 +246,7 @@ sub infill { # prerequisites $self->prepare_infill; - - return if $self->step_done(STEP_INFILL); - $self->set_step_started(STEP_INFILL); - $self->print->status_cb->(70, "Infilling layers"); - - Slic3r::parallelize( - threads => $self->print->config->threads, - items => sub { 0..$#{$self->layers} }, - thread_cb => sub { - my $q = shift; - while (defined (my $i = $q->dequeue)) { - $self->get_layer($i)->make_fills; - } - }, - no_threads_cb => sub { - foreach my $layer (@{$self->layers}) { - $layer->make_fills; - } - }, - ); - - ### we could free memory now, but this would make this step not idempotent - ### Vojtech: Cannot release the fill_surfaces, they are used by the support generator. - ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; - - $self->set_step_done(STEP_INFILL); + $self->_infill; } sub generate_support_material { @@ -637,44 +260,35 @@ sub generate_support_material { $self->clear_support_layers; - if ((!$self->config->support_material && $self->config->raft_layers == 0) || scalar(@{$self->layers}) < 2) { - $self->set_step_done(STEP_SUPPORTMATERIAL); - return; + if (($self->config->support_material || $self->config->raft_layers > 0) && scalar(@{$self->layers}) > 1) { + $self->print->status_cb->(85, "Generating support material"); + if (0) { + # Old supports, Perl implementation. + my $first_layer_flow = Slic3r::Flow->new_from_width( + width => ($self->print->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width), + role => FLOW_ROLE_SUPPORT_MATERIAL, + nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ] + // $self->print->config->nozzle_diameter->[0], + layer_height => $self->config->get_abs_value('first_layer_height'), + bridge_flow_ratio => 0, + ); + my $support_material = Slic3r::Print::SupportMaterial->new( + print_config => $self->print->config, + object_config => $self->config, + first_layer_flow => $first_layer_flow, + flow => $self->support_material_flow, + interface_flow => $self->support_material_flow(FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE), + ); + $support_material->generate($self); + } else { + # New supports, C++ implementation. + $self->_generate_support_material; + } } - $self->print->status_cb->(85, "Generating support material"); - - $self->_support_material->generate($self); $self->set_step_done(STEP_SUPPORTMATERIAL); } -sub _support_material { - my ($self) = @_; - - my $first_layer_flow = Slic3r::Flow->new_from_width( - width => ($self->print->config->first_layer_extrusion_width || $self->config->support_material_extrusion_width), - role => FLOW_ROLE_SUPPORT_MATERIAL, - nozzle_diameter => $self->print->config->nozzle_diameter->[ $self->config->support_material_extruder-1 ] - // $self->print->config->nozzle_diameter->[0], - layer_height => $self->config->get_abs_value('first_layer_height'), - bridge_flow_ratio => 0, - ); - - if (1) { - # Old supports, Perl implementation. - return Slic3r::Print::SupportMaterial->new( - print_config => $self->print->config, - object_config => $self->config, - first_layer_flow => $first_layer_flow, - flow => $self->support_material_flow, - interface_flow => $self->support_material_flow(FLOW_ROLE_SUPPORT_MATERIAL_INTERFACE), - ); - } else { - # New supports, C++ implementation. - return Slic3r::Print::SupportMaterial2->new($self); - } -} - # Idempotence of this method is guaranteed by the fact that we don't remove things from # fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. sub clip_fill_surfaces { @@ -863,7 +477,7 @@ sub discover_horizontal_shells { # and it's not wanted in a hollow print even if it would make sense when # obeying the solid shell count option strictly (DWIM!) my $margin = $neighbor_layerm->flow(FLOW_ROLE_EXTERNAL_PERIMETER)->scaled_width; - my $regularized = offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5); + my $regularized = offset2($new_internal_solid, -$margin, +$margin, JT_MITER, 5); my $too_narrow = diff( $new_internal_solid, $regularized, @@ -893,7 +507,7 @@ sub discover_horizontal_shells { # have the same angle, so the next shell would be grown even more and so on. my $too_narrow = diff( $new_internal_solid, - offset2($new_internal_solid, -$margin, +$margin, CLIPPER_OFFSET_SCALE, JT_MITER, 5), + offset2($new_internal_solid, -$margin, +$margin, JT_MITER, 5), 1, ); diff --git a/lib/Slic3r/Print/SupportMaterial.pm b/lib/Slic3r/Print/SupportMaterial.pm index 9dbfe4003..72f09269f 100644 --- a/lib/Slic3r/Print/SupportMaterial.pm +++ b/lib/Slic3r/Print/SupportMaterial.pm @@ -8,7 +8,7 @@ use Slic3r::ExtrusionPath ':roles'; use Slic3r::Flow ':roles'; use Slic3r::Geometry qw(epsilon scale scaled_epsilon PI rad2deg deg2rad convex_hull); use Slic3r::Geometry::Clipper qw(offset diff union union_ex intersection offset_ex offset2 - intersection_pl offset2_ex diff_pl CLIPPER_OFFSET_SCALE JT_MITER JT_ROUND); + intersection_pl offset2_ex diff_pl JT_MITER JT_ROUND); use Slic3r::Surface ':types'; has 'print_config' => (is => 'rw', required => 1); @@ -20,6 +20,7 @@ has 'interface_flow' => (is => 'rw', required => 1); use constant DEBUG_CONTACT_ONLY => 0; # increment used to reach MARGIN in steps to avoid trespassing thin objects +use constant MARGIN => 1.5; use constant MARGIN_STEP => MARGIN/3; # generate a tree-like structure to save material @@ -45,6 +46,7 @@ sub generate { # We now know the upper and lower boundaries for our support material object # (@$contact_z and @$top_z), so we can generate intermediate layers. my $support_z = $self->support_layers_z( + $object, [ sort keys %$contact ], [ sort keys %$top ], max(map $_->height, @{$object->layers}) @@ -299,9 +301,8 @@ sub contact_area { offset( $diff, $_, - CLIPPER_OFFSET_SCALE, JT_ROUND, - scale(0.05)*CLIPPER_OFFSET_SCALE), + scale(0.05)), $slices_margin ); } @@ -371,7 +372,7 @@ sub object_top { # grow top surfaces so that interface and support generation are generated # with some spacing from object - it looks we don't need the actual # top shapes so this can be done here - $top{ $layer->print_z } = offset($touching, $self->flow->scaled_width); + $top{ $layer->print_z } = offset($touching, $self->flow->scaled_width + $self->object_config->support_material_xy_spacing); } # remove the areas that touched from the projection that will continue on @@ -384,7 +385,7 @@ sub object_top { } sub support_layers_z { - my ($self, $contact_z, $top_z, $max_object_layer_height) = @_; + my ($self, $object, $contact_z, $top_z, $max_object_layer_height) = @_; # quick table to check whether a given Z is a top surface my %top = map { $_ => 1 } @$top_z; @@ -397,13 +398,18 @@ sub support_layers_z { my $contact_distance = $self->contact_distance($support_material_height, $nozzle_diameter); # initialize known, fixed, support layers - my @z = sort { $a <=> $b } - @$contact_z, - # TODO: why we have this? - # Vojtech: To detect the bottom interface layers by finding a Z value in the $top_z. - @$top_z, - # Top surfaces of the bottom interface layers. - (map $_ + $contact_distance, @$top_z); + my @z = @$contact_z; + my $synchronize = $self->object_config->support_material_synchronize_layers; + if (! $synchronize) { + push @z, + # TODO: why we have this? + # Vojtech: To detect the bottom interface layers by finding a Z value in the $top_z. + @$top_z; + push @z, + # Top surfaces of the bottom interface layers. + (map $_ + $contact_distance, @$top_z); + } + @z = sort { $a <=> $b } @z; # enforce first layer height my $first_layer_height = $self->object_config->get_value('first_layer_height'); @@ -423,23 +429,29 @@ sub support_layers_z { 1..($self->object_config->raft_layers - 2); } - # create other layers (skip raft layers as they're already done and use thicker layers) - for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) { - my $target_height = $support_material_height; - if ($i > 0 && $top{ $z[$i-1] }) { - # Bridge flow? - #FIXME We want to enforce not only the bridge flow height, but also the interface gap! - # This will introduce an additional layer if the gap is set to an extreme value! - $target_height = $nozzle_diameter; - } - - # enforce first layer height - #FIXME better to split the layers regularly, than to bite a constant height one at a time, - # and then be left with a very thin layer at the end. - if (($i == 0 && $z[$i] > $target_height + $first_layer_height) - || ($z[$i] - $z[$i-1] > $target_height + Slic3r::Geometry::epsilon)) { - splice @z, $i, 0, ($z[$i] - $target_height); - $i++; + if ($synchronize) { + @z = splice @z, $self->object_config->raft_layers; +# if ($self->object_config->raft_layers > scalar(@z)); + push @z, map $_->print_z, @{$object->layers}; + } else { + # create other layers (skip raft layers as they're already done and use thicker layers) + for (my $i = $#z; $i >= $self->object_config->raft_layers; $i--) { + my $target_height = $support_material_height; + if ($i > 0 && $top{ $z[$i-1] }) { + # Bridge flow? + #FIXME We want to enforce not only the bridge flow height, but also the interface gap! + # This will introduce an additional layer if the gap is set to an extreme value! + $target_height = $nozzle_diameter; + } + + # enforce first layer height + #FIXME better to split the layers regularly, than to bite a constant height one at a time, + # and then be left with a very thin layer at the end. + if (($i == 0 && $z[$i] > $target_height + $first_layer_height) + || ($z[$i] - $z[$i-1] > $target_height + Slic3r::Geometry::epsilon)) { + splice @z, $i, 0, ($z[$i] - $target_height); + $i++; + } } } @@ -584,9 +596,8 @@ sub generate_base_layers { $fillet_radius_scaled, -$fillet_radius_scaled, # Use a geometric offsetting for filleting. - CLIPPER_OFFSET_SCALE, JT_ROUND, - 0.2*$fillet_radius_scaled*CLIPPER_OFFSET_SCALE), + 0.2*$fillet_radius_scaled), $trim_polygons, 0); # don't apply the safety offset. } @@ -613,10 +624,11 @@ sub clip_with_object { # $layer->slices contains the full shape of layer, thus including # perimeter's width. $support contains the full shape of support # material, thus including the width of its foremost extrusion. - # We leave a gap equal to a full extrusion width. + # We leave a gap equal to a full extrusion width + an offset + # if the user wants to play around with this setting. $support->{$i} = diff( $support->{$i}, - offset([ map @$_, map @{$_->slices}, @layers ], +$self->flow->scaled_width), + offset([ map @$_, map @{$_->slices}, @layers ], +$self->object_config->support_material_xy_spacing), ); } } @@ -628,8 +640,8 @@ sub generate_toolpaths { my $interface_flow = $self->interface_flow; # shape of contact area - my $contact_loops = 1; - my $circle_radius = 1.5 * $interface_flow->scaled_width; + my $contact_loops = $self->object_config->support_material_interface_contact_loops ? 1 : 0; + my $circle_radius = 1.5 * $interface_flow->scaled_width - ($self->object_config->support_material_xy_spacing / 0.000001) ; my $circle_distance = 3 * $circle_radius; my $circle = Slic3r::Polygon->new(map [ $circle_radius * cos $_, $circle_radius * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); @@ -692,11 +704,14 @@ sub generate_toolpaths { # if no interface layers were requested we treat the contact layer # exactly as a generic base layer push @$base, @$contact; - } elsif (@$contact && $contact_loops > 0) { + } elsif ($contact_loops == 0) { + # No contact loops, but some interface layers. Print the contact layer as a normal interface layer. + push @$interface, @$contact; + } elsif (@$contact) { # generate the outermost loop # find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - $contact = offset($contact, -$_interface_flow->scaled_width/2); + $contact = offset($contact, -($_interface_flow->scaled_width + ($self->object_config->support_material_xy_spacing / 0.000001)) /2); my @loops0 = (); { diff --git a/slic3r.pl b/slic3r.pl index f6fe4bb4f..a593f93c9 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -426,6 +426,9 @@ $j Support material angle in degrees (range: 0-90, default: $config->{support_material_angle}) --support-material-contact-distance Vertical distance between object and support material (0+, default: $config->{support_material_contact_distance}) + --support-material-xy-spacing + "XY separation between an object and its support. If expressed as percentage (for example 50%), + it will be calculated over external perimeter width (default: half of exteral perimeter width) --support-material-interface-layers Number of perpendicular layers between support material and object (0+, default: $config->{support_material_interface_layers}) --support-material-interface-spacing diff --git a/t/support.t b/t/support.t index 3eba6e64b..303f77088 100644 --- a/t/support.t +++ b/t/support.t @@ -28,7 +28,7 @@ use Slic3r::Test; interface_flow => $flow, first_layer_flow => $flow, ); - my $support_z = $support->support_layers_z(\@contact_z, \@top_z, $config->layer_height); + my $support_z = $support->support_layers_z($print->print->objects->[0], \@contact_z, \@top_z, $config->layer_height); my $expected_top_spacing = $support->contact_distance($config->layer_height, $config->nozzle_diameter->[0]); is $support_z->[0], $config->first_layer_height, diff --git a/xs/Build.PL b/xs/Build.PL index 98af40ead..51d1a014f 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -117,17 +117,21 @@ if (defined $ENV{BOOST_LIBRARYDIR}) { # In order to generate the -l switches we need to know how Boost libraries are named my $have_boost = 0; -my @boost_libraries = qw(system thread); # we need these +my @boost_libraries = qw(system thread log); # we need these -# check without explicit lib path (works on Linux) -if (! $mswin) { - $have_boost = 1 - if check_lib( - lib => [ map "boost_${_}", @boost_libraries ], - ); +if (!$ENV{SLIC3R_STATIC}) { + # Dynamic linking of boost libraries. + push @cflags, qw(-DBOOST_LOG_DYN_LINK); + if (! $mswin) { + # Check without explicit lib path (works on Linux and OSX). + $have_boost = 1 + if check_lib( + lib => [ map "boost_${_}", @boost_libraries ], + ); + } } -if (!$ENV{SLIC3R_STATIC} && $have_boost) { +if ($have_boost) { # The boost library was detected by check_lib on Linux. push @LIBS, map "-lboost_${_}", @boost_libraries; } else { @@ -138,8 +142,7 @@ if (!$ENV{SLIC3R_STATIC} && $have_boost) { # Try to find the boost system library. my @files = glob "$path/${lib_prefix}system*$lib_ext"; next if !@files; - - if ($files[0] =~ /${lib_prefix}system([^.]+)$lib_ext$/) { + if ($files[0] =~ /\Q${lib_prefix}system\E([^.]*)\Q$lib_ext\E$/) { # Suffix contains the version number, the build type etc. my $suffix = $1; # Verify existence of all required boost libraries at $path. @@ -212,6 +215,10 @@ if ($cpp_guess->is_gcc) { } } +print "\n"; +print 'With @INC: ', join(', ', map "\"$_\"", @INC), "\n"; +print 'With @LIBS: ', join(', ', map "\"$_\"", @LIBS), "\n"; + my $build = Module::Build::WithXSpp->new( module_name => 'Slic3r::XS', dist_abstract => 'XS code for Slic3r', diff --git a/xs/MANIFEST b/xs/MANIFEST index 70d83afa7..01b9e1cd3 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -32,6 +32,8 @@ src/libslic3r/ExtrusionEntityCollection.cpp src/libslic3r/ExtrusionEntityCollection.hpp src/libslic3r/ExtrusionSimulator.cpp src/libslic3r/ExtrusionSimulator.hpp +src/libslic3r/Fill/Fill.cpp +src/libslic3r/Fill/Fill.hpp src/libslic3r/Fill/FillBase.cpp src/libslic3r/Fill/FillBase.hpp src/libslic3r/Fill/FillConcentric.cpp @@ -54,6 +56,8 @@ src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp +src/libslic3r/GCode/Analyzer.cpp +src/libslic3r/GCode/Analyzer.hpp src/libslic3r/GCode/PressureEqualizer.cpp src/libslic3r/GCode/PressureEqualizer.hpp src/libslic3r/Geometry.cpp @@ -88,6 +92,10 @@ src/libslic3r/PrintConfig.cpp src/libslic3r/PrintConfig.hpp src/libslic3r/PrintObject.cpp src/libslic3r/PrintRegion.cpp +src/libslic3r/Slicing.cpp +src/libslic3r/Slicing.hpp +src/libslic3r/SlicingAdaptive.cpp +src/libslic3r/SlicingAdaptive.hpp src/libslic3r/SupportMaterial.cpp src/libslic3r/SupportMaterial.hpp src/libslic3r/Surface.cpp @@ -99,6 +107,7 @@ src/libslic3r/SVG.hpp src/libslic3r/TriangleMesh.cpp src/libslic3r/TriangleMesh.hpp src/libslic3r/utils.cpp +src/libslic3r/Utils.hpp src/perlglue.cpp src/poly2tri/common/shapes.cc src/poly2tri/common/shapes.h @@ -200,7 +209,6 @@ xsp/Polygon.xsp xsp/Polyline.xsp xsp/PolylineCollection.xsp xsp/Print.xsp -xsp/SupportMaterial.xsp xsp/Surface.xsp xsp/SurfaceCollection.xsp xsp/TriangleMesh.xsp diff --git a/xs/src/admesh/stl.h b/xs/src/admesh/stl.h index d97760eb0..34e1907e7 100644 --- a/xs/src/admesh/stl.h +++ b/xs/src/admesh/stl.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #ifndef BOOST_LITTLE_ENDIAN #error "admesh works correctly on little endian machines only!" diff --git a/xs/src/admesh/stlinit.c b/xs/src/admesh/stlinit.c index de1760bd5..33293cd47 100644 --- a/xs/src/admesh/stlinit.c +++ b/xs/src/admesh/stlinit.c @@ -43,23 +43,8 @@ stl_open(stl_file *stl, char *file) { void stl_initialize(stl_file *stl) { - stl->error = 0; - stl->stats.degenerate_facets = 0; - stl->stats.edges_fixed = 0; - stl->stats.facets_added = 0; - stl->stats.facets_removed = 0; - stl->stats.facets_reversed = 0; - stl->stats.normals_fixed = 0; - stl->stats.number_of_parts = 0; - stl->stats.original_num_facets = 0; - stl->stats.number_of_facets = 0; - stl->stats.facets_malloced = 0; + memset(stl, 0, sizeof(stl_file)); stl->stats.volume = -1.0; - - stl->neighbors_start = NULL; - stl->facet_start = NULL; - stl->v_indices = NULL; - stl->v_shared = NULL; } void @@ -270,6 +255,7 @@ stl_read(stl_file *stl, int first_facet, int first) { rewind(stl->fp); } + char normal_buf[3][32]; for(i = first_facet; i < stl->stats.number_of_facets; i++) { if(stl->stats.type == binary) /* Read a single facet from a binary .STL file */ @@ -287,17 +273,25 @@ stl_read(stl_file *stl, int first_facet, int first) { fscanf(stl->fp, "endsolid\n"); fscanf(stl->fp, "solid%*[^\n]\n"); // name might contain spaces so %*s doesn't work and it also can be empty (just "solid") - if((fscanf(stl->fp, " facet normal %f %f %f\n", &facet.normal.x, &facet.normal.y, &facet.normal.z) + \ - fscanf(stl->fp, " outer loop\n") + \ - fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + \ - fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + \ - fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + \ - fscanf(stl->fp, " endloop\n") + \ + if((fscanf(stl->fp, " facet normal %31s %31s %31s\n", normal_buf[0], normal_buf[1], normal_buf[2]) + + fscanf(stl->fp, " outer loop\n") + + fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[0].x, &facet.vertex[0].y, &facet.vertex[0].z) + + fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[1].x, &facet.vertex[1].y, &facet.vertex[1].z) + + fscanf(stl->fp, " vertex %f %f %f\n", &facet.vertex[2].x, &facet.vertex[2].y, &facet.vertex[2].z) + + fscanf(stl->fp, " endloop\n") + fscanf(stl->fp, " endfacet\n")) != 12) { perror("Something is syntactically very wrong with this ASCII STL!"); stl->error = 1; return; } + // The facet normal has been parsed as a single string as to workaround for not a numbers in the normal definition. + if (sscanf(normal_buf[0], "%f", &facet.normal.x) != 1 || + sscanf(normal_buf[1], "%f", &facet.normal.y) != 1 || + sscanf(normal_buf[2], "%f", &facet.normal.z) != 1) { + // Normal was mangled. Maybe denormals or "not a number" were stored? + // Just reset the normal and silently ignore it. + memset(&facet.normal, 0, sizeof(facet.normal)); + } } #if 0 diff --git a/xs/src/clipper.hpp b/xs/src/clipper.hpp index c8009e64e..47de7b182 100644 --- a/xs/src/clipper.hpp +++ b/xs/src/clipper.hpp @@ -153,17 +153,35 @@ private: PolyNode* GetNextSiblingUp() const; void AddChild(PolyNode& child); friend class Clipper; //to access Index - friend class ClipperOffset; + friend class ClipperOffset; + friend class PolyTree; //to implement the PolyTree::move operator }; class PolyTree: public PolyNode { public: - ~PolyTree(){Clear();}; + PolyTree() {} + PolyTree(PolyTree &&src) { *this = std::move(src); } + virtual ~PolyTree(){Clear();}; + PolyTree& operator=(PolyTree &&src) { + AllNodes = std::move(src.AllNodes); + Contour = std::move(src.Contour); + Childs = std::move(src.Childs); + Parent = nullptr; + Index = src.Index; + m_IsOpen = src.m_IsOpen; + m_jointype = src.m_jointype; + m_endtype = src.m_endtype; + for (size_t i = 0; i < Childs.size(); ++ i) + Childs[i]->Parent = this; + return *this; + } PolyNode* GetFirst() const; void Clear(); int Total() const; private: + PolyTree(const PolyTree &src) = delete; + PolyTree& operator=(const PolyTree &src) = delete; PolyNodes AllNodes; friend class Clipper; //to access AllNodes }; diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index bffe1dbfb..75ece90bf 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -277,4 +277,26 @@ BoundingBoxBase::overlap(const BoundingBoxBase &other) c template bool BoundingBoxBase::overlap(const BoundingBoxBase &point) const; template bool BoundingBoxBase::overlap(const BoundingBoxBase &point) const; + +// Align a coordinate to a grid. The coordinate may be negative, +// the aligned value will never be bigger than the original one. +static inline coord_t _align_to_grid(const coord_t coord, const coord_t spacing) { + // Current C++ standard defines the result of integer division to be rounded to zero, + // for both positive and negative numbers. Here we want to round down for negative + // numbers as well. + coord_t aligned = (coord < 0) ? + ((coord - spacing + 1) / spacing) * spacing : + (coord / spacing) * spacing; + assert(aligned <= coord); + return aligned; +} + +void BoundingBox::align_to_grid(const coord_t cell_size) +{ + if (this->defined) { + min.x = _align_to_grid(min.x, cell_size); + min.y = _align_to_grid(min.y, cell_size); + } +} + } diff --git a/xs/src/libslic3r/BoundingBox.hpp b/xs/src/libslic3r/BoundingBox.hpp index 5f676151c..232dada80 100644 --- a/xs/src/libslic3r/BoundingBox.hpp +++ b/xs/src/libslic3r/BoundingBox.hpp @@ -65,6 +65,9 @@ class BoundingBox : public BoundingBoxBase BoundingBox rotated(double angle, const Point ¢er) const; void rotate(double angle) { (*this) = this->rotated(angle); } void rotate(double angle, const Point ¢er) { (*this) = this->rotated(angle, center); } + // Align the min corner to a grid of cell_size x cell_size cells, + // to encompass the original bounding box. + void align_to_grid(const coord_t cell_size); BoundingBox() : BoundingBoxBase() {}; BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {}; diff --git a/xs/src/libslic3r/BridgeDetector.cpp b/xs/src/libslic3r/BridgeDetector.cpp index 7e54bcf69..fa27134dc 100644 --- a/xs/src/libslic3r/BridgeDetector.cpp +++ b/xs/src/libslic3r/BridgeDetector.cpp @@ -40,13 +40,13 @@ void BridgeDetector::initialize() this->angle = -1.; // Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors. - Polygons grown = offset(this->expolygons, float(this->spacing)); + Polygons grown = offset(to_polygons(this->expolygons), float(this->spacing)); // Detect possible anchoring edges of this bridging region. // Detect what edges lie on lower slices by turning bridge contour and holes // into polylines and then clipping them with each lower slice's contour. // Currently _edges are only used to set a candidate direction of the bridge (see bridge_direction_candidates()). - intersection(to_polylines(grown), this->lower_slices.contours(), &this->_edges); + this->_edges = intersection_pl(to_polylines(grown), this->lower_slices.contours()); #ifdef SLIC3R_DEBUG printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); @@ -117,7 +117,7 @@ BridgeDetector::detect_angle() double total_length = 0; double max_length = 0; { - Lines clipped_lines = intersection(lines, clip_area); + Lines clipped_lines = intersection_ln(lines, clip_area); for (size_t i = 0; i < clipped_lines.size(); ++i) { const Line &line = clipped_lines[i]; if (expolygons_contain(this->_anchor_regions, line.a) && expolygons_contain(this->_anchor_regions, line.b)) { @@ -203,76 +203,72 @@ std::vector BridgeDetector::bridge_direction_candidates() const return angles; } -void -BridgeDetector::coverage(double angle, Polygons* coverage) const +Polygons BridgeDetector::coverage(double angle) const { - if (angle == -1) angle = this->angle; - if (angle == -1) return; + if (angle == -1) + angle = this->angle; - // Get anchors, convert them to Polygons and rotate them. - Polygons anchors = to_polygons(this->_anchor_regions); - polygons_rotate(anchors, PI/2.0 - angle); - Polygons covered; - for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) - { - // Clone our expolygon and rotate it so that we work with vertical lines. - ExPolygon expolygon = *it_expoly; - expolygon.rotate(PI/2.0 - angle); + + if (angle != -1) { + + // Get anchors, convert them to Polygons and rotate them. + Polygons anchors = to_polygons(this->_anchor_regions); + polygons_rotate(anchors, PI/2.0 - angle); - /* Outset the bridge expolygon by half the amount we used for detecting anchors; - we'll use this one to generate our trapezoids and be sure that their vertices - are inside the anchors and not on their contours leading to false negatives. */ - ExPolygons grown = offset_ex(expolygon, 0.5f * float(this->spacing)); - - // Compute trapezoids according to a vertical orientation - Polygons trapezoids; - for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) - it->get_trapezoids2(&trapezoids, PI/2.0); - - for (Polygons::iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { - Lines supported = intersection(trapezoid->lines(), anchors); - size_t n_supported = 0; - // not nice, we need a more robust non-numeric check - for (size_t i = 0; i < supported.size(); ++i) - if (supported[i].length() >= this->spacing) - ++ n_supported; - if (n_supported >= 2) - covered.push_back(STDMOVE(*trapezoid)); + for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) + { + // Clone our expolygon and rotate it so that we work with vertical lines. + ExPolygon expolygon = *it_expoly; + expolygon.rotate(PI/2.0 - angle); + + /* Outset the bridge expolygon by half the amount we used for detecting anchors; + we'll use this one to generate our trapezoids and be sure that their vertices + are inside the anchors and not on their contours leading to false negatives. */ + ExPolygons grown = offset_ex(expolygon, 0.5f * float(this->spacing)); + + // Compute trapezoids according to a vertical orientation + Polygons trapezoids; + for (ExPolygons::const_iterator it = grown.begin(); it != grown.end(); ++it) + it->get_trapezoids2(&trapezoids, PI/2.0); + + for (Polygons::iterator trapezoid = trapezoids.begin(); trapezoid != trapezoids.end(); ++trapezoid) { + Lines supported = intersection_ln(trapezoid->lines(), anchors); + size_t n_supported = 0; + // not nice, we need a more robust non-numeric check + for (size_t i = 0; i < supported.size(); ++i) + if (supported[i].length() >= this->spacing) + ++ n_supported; + if (n_supported >= 2) + covered.push_back(STDMOVE(*trapezoid)); + } } + + // Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids + // instead of exact overlaps. + covered = union_(covered); + + // Intersect trapezoids with actual bridge area to remove extra margins and append it to result. + polygons_rotate(covered, -(PI/2.0 - angle)); + covered = intersection(covered, to_polygons(this->expolygons)); + + /* + if (0) { + my @lines = map @{$_->lines}, @$trapezoids; + $_->rotate(-(PI/2 - $angle), [0,0]) for @lines; + + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "coverage_" . rad2deg($angle) . ".svg", + expolygons => [$self->expolygon], + green_expolygons => $self->_anchor_regions, + red_expolygons => $coverage, + lines => \@lines, + ); + } + */ } - - // Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids - // instead of exact overlaps. - covered = union_(covered); - - // Intersect trapezoids with actual bridge area to remove extra margins and append it to result. - polygons_rotate(covered, -(PI/2.0 - angle)); - intersection(covered, to_polygons(this->expolygons), coverage); - - /* - if (0) { - my @lines = map @{$_->lines}, @$trapezoids; - $_->rotate(-(PI/2 - $angle), [0,0]) for @lines; - - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "coverage_" . rad2deg($angle) . ".svg", - expolygons => [$self->expolygon], - green_expolygons => $self->_anchor_regions, - red_expolygons => $coverage, - lines => \@lines, - ); - } - */ -} - -Polygons -BridgeDetector::coverage(double angle) const -{ - Polygons pp; - this->coverage(angle, &pp); - return pp; + return covered; } /* This method returns the bridge edges (as polylines) that are not supported @@ -288,9 +284,7 @@ BridgeDetector::unsupported_edges(double angle, Polylines* unsupported) const for (ExPolygons::const_iterator it_expoly = this->expolygons.begin(); it_expoly != this->expolygons.end(); ++ it_expoly) { // get unsupported bridge edges (both contour and holes) - Polylines unuspported_polylines; - diff(to_polylines(*it_expoly), grown_lower, &unuspported_polylines); - Lines unsupported_lines = to_lines(unuspported_polylines); + Lines unsupported_lines = to_lines(diff_pl(to_polylines(*it_expoly), grown_lower)); /* Split into individual segments and filter out edges parallel to the bridging angle TODO: angle tolerance should probably be based on segment length and flow width, so that we build supports whenever there's a chance that at least one or two bridge diff --git a/xs/src/libslic3r/BridgeDetector.hpp b/xs/src/libslic3r/BridgeDetector.hpp index 825ce10b6..b04db63e4 100644 --- a/xs/src/libslic3r/BridgeDetector.hpp +++ b/xs/src/libslic3r/BridgeDetector.hpp @@ -32,7 +32,6 @@ public: BridgeDetector(ExPolygon _expolygon, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); BridgeDetector(const ExPolygons &_expolygons, const ExPolygonCollection &_lower_slices, coord_t _extrusion_width); bool detect_angle(); - void coverage(double angle, Polygons* coverage) const; Polygons coverage(double angle = -1) const; void unsupported_edges(double angle, Polylines* unsupported) const; Polylines unsupported_edges(double angle = -1) const; diff --git a/xs/src/libslic3r/ClipperUtils.cpp b/xs/src/libslic3r/ClipperUtils.cpp index f5e98b724..b04a018a7 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -9,65 +9,157 @@ #include +// Factor to convert from coord_t (which is int32) to an int64 type used by the Clipper library +// for general offsetting (the offset(), offset2(), offset_ex() functions) and for the safety offset, +// which is optionally executed by other functions (union, intersection, diff). +// By the way, is the scalling for offset needed at all? +#define CLIPPER_OFFSET_POWER_OF_2 17 +// 2^17=131072 +#define CLIPPER_OFFSET_SCALE (1 << CLIPPER_OFFSET_POWER_OF_2) +#define CLIPPER_OFFSET_SCALE_ROUNDING_DELTA ((1 << (CLIPPER_OFFSET_POWER_OF_2 - 1)) - 1) + namespace Slic3r { +#ifdef CLIPPER_UTILS_DEBUG +bool clipper_export_enabled = false; +// For debugging the Clipper library, for providing bug reports to the Clipper author. +bool export_clipper_input_polygons_bin(const char *path, const ClipperLib::Paths &input_subject, const ClipperLib::Paths &input_clip) +{ + FILE *pfile = fopen(path, "wb"); + if (pfile == NULL) + return false; + + uint32_t sz = uint32_t(input_subject.size()); + fwrite(&sz, 1, sizeof(sz), pfile); + for (size_t i = 0; i < input_subject.size(); ++i) { + const ClipperLib::Path &path = input_subject[i]; + sz = uint32_t(path.size()); + ::fwrite(&sz, 1, sizeof(sz), pfile); + ::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile); + } + sz = uint32_t(input_clip.size()); + ::fwrite(&sz, 1, sizeof(sz), pfile); + for (size_t i = 0; i < input_clip.size(); ++i) { + const ClipperLib::Path &path = input_clip[i]; + sz = uint32_t(path.size()); + ::fwrite(&sz, 1, sizeof(sz), pfile); + ::fwrite(path.data(), sizeof(ClipperLib::IntPoint), sz, pfile); + } + ::fclose(pfile); + return true; + +err: + ::fclose(pfile); + return false; +} +#endif /* CLIPPER_UTILS_DEBUG */ + +void scaleClipperPolygon(ClipperLib::Path &polygon) +{ + PROFILE_FUNC(); + for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { + pit->X <<= CLIPPER_OFFSET_POWER_OF_2; + pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; + } +} + +void scaleClipperPolygons(ClipperLib::Paths &polygons) +{ + PROFILE_FUNC(); + for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) + for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { + pit->X <<= CLIPPER_OFFSET_POWER_OF_2; + pit->Y <<= CLIPPER_OFFSET_POWER_OF_2; + } +} + +void unscaleClipperPolygon(ClipperLib::Path &polygon) +{ + PROFILE_FUNC(); + for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { + pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pit->X >>= CLIPPER_OFFSET_POWER_OF_2; + pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; + } +} + +void unscaleClipperPolygons(ClipperLib::Paths &polygons) +{ + PROFILE_FUNC(); + for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) + for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { + pit->X += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pit->Y += CLIPPER_OFFSET_SCALE_ROUNDING_DELTA; + pit->X >>= CLIPPER_OFFSET_POWER_OF_2; + pit->Y >>= CLIPPER_OFFSET_POWER_OF_2; + } +} + //----------------------------------------------------------- // legacy code from Clipper documentation -void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons* expolygons) +void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, ExPolygons* expolygons) { size_t cnt = expolygons->size(); expolygons->resize(cnt + 1); - ClipperPath_to_Slic3rMultiPoint(polynode.Contour, &(*expolygons)[cnt].contour); + (*expolygons)[cnt].contour = ClipperPath_to_Slic3rPolygon(polynode.Contour); (*expolygons)[cnt].holes.resize(polynode.ChildCount()); for (int i = 0; i < polynode.ChildCount(); ++i) { - ClipperPath_to_Slic3rMultiPoint(polynode.Childs[i]->Contour, &(*expolygons)[cnt].holes[i]); + (*expolygons)[cnt].holes[i] = ClipperPath_to_Slic3rPolygon(polynode.Childs[i]->Contour); //Add outer polygons contained by (nested within) holes ... for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++j) AddOuterPolyNodeToExPolygons(*polynode.Childs[i]->Childs[j], expolygons); } } -void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons* expolygons) +ExPolygons +PolyTreeToExPolygons(ClipperLib::PolyTree& polytree) { - PROFILE_FUNC(); - expolygons->clear(); + ExPolygons retval; for (int i = 0; i < polytree.ChildCount(); ++i) - AddOuterPolyNodeToExPolygons(*polytree.Childs[i], expolygons); + AddOuterPolyNodeToExPolygons(*polytree.Childs[i], &retval); + return retval; } //----------------------------------------------------------- -template -void -ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output) +Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input) { - PROFILE_FUNC(); - output->points.clear(); - output->points.reserve(input.size()); + Polygon retval; for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) - output->points.push_back(Slic3r::Point( (*pit).X, (*pit).Y )); -} -template void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, Slic3r::Polygon* output); - -template -void -ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input, T* output) -{ - PROFILE_FUNC(); - output->clear(); - output->reserve(input.size()); - for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) { - typename T::value_type p; - ClipperPath_to_Slic3rMultiPoint(*it, &p); - output->push_back(p); - } + retval.points.push_back(Point( (*pit).X, (*pit).Y )); + return retval; } -void -ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolygons* output) +Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input) { - PROFILE_FUNC(); + Polyline retval; + for (ClipperLib::Path::const_iterator pit = input.begin(); pit != input.end(); ++pit) + retval.points.push_back(Point( (*pit).X, (*pit).Y )); + return retval; +} +Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input) +{ + Slic3r::Polygons retval; + retval.reserve(input.size()); + for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) + retval.push_back(ClipperPath_to_Slic3rPolygon(*it)); + return retval; +} + +Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input) +{ + Slic3r::Polylines retval; + retval.reserve(input.size()); + for (ClipperLib::Paths::const_iterator it = input.begin(); it != input.end(); ++it) + retval.push_back(ClipperPath_to_Slic3rPolyline(*it)); + return retval; +} + +ExPolygons +ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input) +{ // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); @@ -78,213 +170,87 @@ ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolyg clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); // offset results work with both EvenOdd and NonZero // write to ExPolygons object - output->clear(); - PolyTreeToExPolygons(polytree, output); + return PolyTreeToExPolygons(polytree); } -void -Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path* output) +ClipperLib::Path +Slic3rMultiPoint_to_ClipperPath(const MultiPoint &input) { - PROFILE_FUNC(); - - output->clear(); - output->reserve(input.points.size()); - for (Slic3r::Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) - output->push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); + ClipperLib::Path retval; + for (Points::const_iterator pit = input.points.begin(); pit != input.points.end(); ++pit) + retval.push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); + return retval; } -void -Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input, ClipperLib::Path* output) +ClipperLib::Path +Slic3rMultiPoint_to_ClipperPath_reversed(const Slic3r::MultiPoint &input) { - PROFILE_FUNC(); - - output->clear(); - output->reserve(input.points.size()); + ClipperLib::Path output; + output.reserve(input.points.size()); for (Slic3r::Points::const_reverse_iterator pit = input.points.rbegin(); pit != input.points.rend(); ++pit) - output->push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); + output.push_back(ClipperLib::IntPoint( (*pit).x, (*pit).y )); + return output; } -template -void -Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output) +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input) { - PROFILE_FUNC(); - - output->clear(); - output->reserve(input.size()); - for (typename T::const_iterator it = input.begin(); it != input.end(); ++it) { - // std::vector< IntPoint >, IntPoint is a pair of int64_t - ClipperLib::Path p; - Slic3rMultiPoint_to_ClipperPath(*it, &p); - output->push_back(p); - } + ClipperLib::Paths retval; + for (Polygons::const_iterator it = input.begin(); it != input.end(); ++it) + retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it)); + return retval; } -void -scaleClipperPolygon(ClipperLib::Path &polygon, const double scale) +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input) { - PROFILE_FUNC(); - - for (ClipperLib::Path::iterator pit = polygon.begin(); pit != polygon.end(); ++pit) { - //FIXME multiplication of int64_t by double! - // Replace by bit shifts? - (*pit).X *= scale; - (*pit).Y *= scale; - } + ClipperLib::Paths retval; + for (Polylines::const_iterator it = input.begin(); it != input.end(); ++it) + retval.push_back(Slic3rMultiPoint_to_ClipperPath(*it)); + return retval; } -void -scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale) +ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { - PROFILE_FUNC(); - - for (ClipperLib::Paths::iterator it = polygons.begin(); it != polygons.end(); ++it) { - for (ClipperLib::Path::iterator pit = (*it).begin(); pit != (*it).end(); ++pit) { - //FIXME multiplication of int64_t by double! - // Replace by bit shifts? - (*pit).X *= scale; - (*pit).Y *= scale; - } - } -} - -void -offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - PROFILE_FUNC(); - // read input - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polygons, &input); - // scale input - scaleClipperPolygons(input, scale); + scaleClipperPolygons(input); // perform offset ClipperLib::ClipperOffset co; - if (joinType == jtRound) { + if (joinType == jtRound) co.ArcTolerance = miterLimit; - } else { + else co.MiterLimit = miterLimit; - } - { - PROFILE_BLOCK(offset_AddPaths); - co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); - } - { - PROFILE_BLOCK(offset_Execute); - co.Execute(*retval, (delta*scale)); - } + co.AddPaths(input, joinType, endType); + ClipperLib::Paths retval; + co.Execute(retval, delta * float(CLIPPER_OFFSET_SCALE)); // unscale output - scaleClipperPolygons(*retval, 1/scale); + unscaleClipperPolygons(retval); + return retval; } -void -offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) +ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit) { - // perform offset - ClipperLib::Paths output; - offset(polygons, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); -} - -Slic3r::Polygons -offset(const Slic3r::Polygons &polygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - Slic3r::Polygons pp; - offset(polygons, &pp, delta, scale, joinType, miterLimit); - return pp; -} - -void -offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // read input - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polylines, &input); - - // scale input - scaleClipperPolygons(input, scale); - - // perform offset - ClipperLib::ClipperOffset co; - if (joinType == jtRound) { - co.ArcTolerance = miterLimit; - } else { - co.MiterLimit = miterLimit; - } - co.AddPaths(input, joinType, ClipperLib::etOpenButt); - co.Execute(*retval, (delta*scale)); - - // unscale output - scaleClipperPolygons(*retval, 1/scale); -} - -void -offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // perform offset - ClipperLib::Paths output; - offset(polylines, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); -} - -void -offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // perform offset - Slic3r::ExPolygons expp; - offset(surface.expolygon, &expp, delta, scale, joinType, miterLimit); - - // clone the input surface for each expolygon we got - retval->clear(); - retval->reserve(expp.size()); - for (ExPolygons::iterator it = expp.begin(); it != expp.end(); ++it) { - Surface s = surface; // clone - s.expolygon = *it; - retval->push_back(s); - } -} - -void -offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // perform offset - ClipperLib::Paths output; - offset(polygons, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - ClipperPaths_to_Slic3rExPolygons(output, retval); + ClipperLib::Paths paths; + paths.push_back(std::move(input)); + return _offset(std::move(paths), endType, delta, joinType, miterLimit); } // This is a safe variant of the polygon offset, tailored for a single ExPolygon: // a single polygon with multiple non-overlapping holes. // Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours. -void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) +ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, + ClipperLib::JoinType joinType, double miterLimit) { // printf("new ExPolygon offset\n"); // 1) Offset the outer contour. - const float delta_scaled = float(delta * scale); + const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); ClipperLib::Paths contours; { - ClipperLib::Path input; - Slic3rMultiPoint_to_ClipperPath(expolygon.contour, &input); - scaleClipperPolygon(input, scale); + ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(expolygon.contour); + scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) - co.ArcTolerance = miterLimit; + co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.AddPath(input, joinType, ClipperLib::etClosedPolygon); @@ -296,12 +262,11 @@ void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const { holes.reserve(expolygon.holes.size()); for (Polygons::const_iterator it_hole = expolygon.holes.begin(); it_hole != expolygon.holes.end(); ++ it_hole) { - ClipperLib::Path input; - Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole, &input); - scaleClipperPolygon(input, scale); + ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); + scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) - co.ArcTolerance = miterLimit; + co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.AddPath(input, joinType, ClipperLib::etClosedPolygon); @@ -313,173 +278,137 @@ void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const // 3) Subtract holes from the contours. ClipperLib::Paths output; - { + if (holes.empty()) { + output = std::move(contours); + } else { ClipperLib::Clipper clipper; clipper.Clear(); clipper.AddPaths(contours, ClipperLib::ptSubject, true); clipper.AddPaths(holes, ClipperLib::ptClip, true); - clipper.Execute(ClipperLib::ctDifference, *retval, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); } // 4) Unscale the output. - scaleClipperPolygons(*retval, 1/scale); + unscaleClipperPolygons(output); + return output; } -Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) +// This is a safe variant of the polygons offset, tailored for multiple ExPolygons. +// It is required, that the input expolygons do not overlap and that the holes of each ExPolygon don't intersect with their respective outer contours. +// Each ExPolygon is offsetted separately, then the offsetted ExPolygons are united. +ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, + ClipperLib::JoinType joinType, double miterLimit) { - // perform offset - ClipperLib::Paths output; - offset(expolygon, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - Slic3r::Polygons retval; - ClipperPaths_to_Slic3rMultiPoints(output, &retval); - return retval; -} - -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // perform offset - ClipperLib::Paths output; - offset(expolygon, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - Slic3r::ExPolygons retval; - ClipperPaths_to_Slic3rExPolygons(output, &retval); - return retval; -} - -// This is a safe variant of the polygon offset, tailored for a single ExPolygon: -// a single polygon with multiple non-overlapping holes. -// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours. -void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ -// printf("new ExPolygon offset\n"); - const float delta_scaled = float(delta * scale); - ClipperLib::Paths contours; - ClipperLib::Paths holes; - contours.reserve(expolygons.size()); - { - size_t n_holes = 0; - for (size_t i = 0; i < expolygons.size(); ++ i) - n_holes += expolygons[i].holes.size(); - holes.reserve(n_holes); - } - + const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); + // Offsetted ExPolygons before they are united. + ClipperLib::Paths contours_cummulative; + contours_cummulative.reserve(expolygons.size()); + // How many non-empty offsetted expolygons were actually collected into contours_cummulative? + // If only one, then there is no need to do a final union. + size_t expolygons_collected = 0; for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) { // 1) Offset the outer contour. + ClipperLib::Paths contours; { - ClipperLib::Path input; - Slic3rMultiPoint_to_ClipperPath(it_expoly->contour, &input); - scaleClipperPolygon(input, scale); + ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath(it_expoly->contour); + scaleClipperPolygon(input); ClipperLib::ClipperOffset co; if (joinType == jtRound) - co.ArcTolerance = miterLimit; + co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); else co.MiterLimit = miterLimit; co.AddPath(input, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out; - co.Execute(out, delta_scaled); - contours.insert(contours.end(), out.begin(), out.end()); + co.Execute(contours, delta_scaled); } + if (contours.empty()) + // No need to try to offset the holes. + continue; - // 2) Offset the holes one by one, collect the results. - { - for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { - ClipperLib::Path input; - Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole, &input); - scaleClipperPolygon(input, scale); - ClipperLib::ClipperOffset co; - if (joinType == jtRound) - co.ArcTolerance = miterLimit; - else - co.MiterLimit = miterLimit; - co.AddPath(input, joinType, ClipperLib::etClosedPolygon); - ClipperLib::Paths out; - co.Execute(out, - delta_scaled); - holes.insert(holes.end(), out.begin(), out.end()); + if (it_expoly->holes.empty()) { + // No need to subtract holes from the offsetted expolygon, we are done. + contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + ++ expolygons_collected; + } else { + // 2) Offset the holes one by one, collect the offsetted holes. + ClipperLib::Paths holes; + { + for (Polygons::const_iterator it_hole = it_expoly->holes.begin(); it_hole != it_expoly->holes.end(); ++ it_hole) { + ClipperLib::Path input = Slic3rMultiPoint_to_ClipperPath_reversed(*it_hole); + scaleClipperPolygon(input); + ClipperLib::ClipperOffset co; + if (joinType == jtRound) + co.ArcTolerance = miterLimit * double(CLIPPER_OFFSET_SCALE); + else + co.MiterLimit = miterLimit; + co.AddPath(input, joinType, ClipperLib::etClosedPolygon); + ClipperLib::Paths out; + co.Execute(out, - delta_scaled); + holes.insert(holes.end(), out.begin(), out.end()); + } + } + + // 3) Subtract holes from the contours. + if (holes.empty()) { + // No hole remaining after an offset. Just copy the outer contour. + contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + ++ expolygons_collected; + } else if (delta < 0) { + // Negative offset. There is a chance, that the offsetted hole intersects the outer contour. + // Subtract the offsetted holes from the offsetted contours. + ClipperLib::Clipper clipper; + clipper.Clear(); + clipper.AddPaths(contours, ClipperLib::ptSubject, true); + clipper.AddPaths(holes, ClipperLib::ptClip, true); + ClipperLib::Paths output; + clipper.Execute(ClipperLib::ctDifference, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + if (! output.empty()) { + contours_cummulative.insert(contours_cummulative.end(), output.begin(), output.end()); + ++ expolygons_collected; + } else { + // The offsetted holes have eaten up the offsetted outer contour. + } + } else { + // Positive offset. As long as the Clipper offset does what one expects it to do, the offsetted hole will have a smaller + // area than the original hole or even disappear, therefore there will be no new intersections. + // Just collect the reversed holes. + contours_cummulative.reserve(contours.size() + holes.size()); + contours_cummulative.insert(contours_cummulative.end(), contours.begin(), contours.end()); + // Reverse the holes in place. + for (size_t i = 0; i < holes.size(); ++ i) + std::reverse(holes[i].begin(), holes[i].end()); + contours_cummulative.insert(contours_cummulative.end(), holes.begin(), holes.end()); + ++ expolygons_collected; } } } - // 3) Subtract holes from the contours. + // 4) Unite the offsetted expolygons. ClipperLib::Paths output; - { + if (expolygons_collected > 1 && delta > 0) { + // There is a chance that the outwards offsetted expolygons may intersect. Perform a union. ClipperLib::Clipper clipper; - clipper.Clear(); - clipper.AddPaths(contours, ClipperLib::ptSubject, true); - clipper.AddPaths(holes, ClipperLib::ptClip, true); - clipper.Execute(ClipperLib::ctDifference, *retval, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + clipper.Clear(); + clipper.AddPaths(contours_cummulative, ClipperLib::ptSubject, true); + clipper.Execute(ClipperLib::ctUnion, output, ClipperLib::pftNonZero, ClipperLib::pftNonZero); + } else { + // Negative offset. The shrunk expolygons shall not mutually intersect. Just copy the output. + output = std::move(contours_cummulative); } // 4) Unscale the output. - scaleClipperPolygons(*retval, 1/scale); + unscaleClipperPolygons(output); + return output; } -Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) +ClipperLib::Paths +_offset2(const Polygons &polygons, const float delta1, const float delta2, + const ClipperLib::JoinType joinType, const double miterLimit) { - // perform offset - ClipperLib::Paths output; - offset(expolygons, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - Slic3r::Polygons retval; - ClipperPaths_to_Slic3rMultiPoints(output, &retval); - return retval; -} - -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - // perform offset - ClipperLib::Paths output; - offset(expolygons, &output, delta, scale, joinType, miterLimit); - - // convert into ExPolygons - Slic3r::ExPolygons retval; - ClipperPaths_to_Slic3rExPolygons(output, &retval); - return retval; -} - -Slic3r::ExPolygons -offset_ex(const Slic3r::Polygons &polygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit) -{ - Slic3r::ExPolygons expp; - offset(polygons, &expp, delta, scale, joinType, miterLimit); - return expp; -} - -void -offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) -{ - if (delta1 * delta2 >= 0) { - // Both deltas are the same signum - offset(polygons, retval, delta1 + delta2, scale, joinType, miterLimit); - return; - } -#ifdef CLIPPER_UTILS_DEBUG - BoundingBox bbox = get_extents(polygons); - coordf_t stroke_width = scale_(0.005); - static int iRun = 0; - ++ iRun; - bool flipY = false; - SVG svg(debug_out_path("offset2-%d.svg", iRun), bbox, scale_(1.), flipY); - for (Slic3r::Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) - svg.draw(it->lines(), "gray", stroke_width); -#endif /* CLIPPER_UTILS_DEBUG */ - // read input - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polygons, &input); + ClipperLib::Paths input = Slic3rMultiPoints_to_ClipperPaths(polygons); // scale input - scaleClipperPolygons(input, scale); + scaleClipperPolygons(input); // prepare ClipperOffset object ClipperLib::ClipperOffset co; @@ -492,75 +421,49 @@ offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float // perform first offset ClipperLib::Paths output1; co.AddPaths(input, joinType, ClipperLib::etClosedPolygon); - co.Execute(output1, (delta1*scale)); -#ifdef CLIPPER_UTILS_DEBUG - svg.draw(output1, 1./CLIPPER_OFFSET_SCALE, "red", stroke_width); -#endif /* CLIPPER_UTILS_DEBUG */ + co.Execute(output1, delta1 * float(CLIPPER_OFFSET_SCALE)); // perform second offset co.Clear(); co.AddPaths(output1, joinType, ClipperLib::etClosedPolygon); - co.Execute(*retval, (delta2*scale)); -#ifdef CLIPPER_UTILS_DEBUG - svg.draw(*retval, 1./CLIPPER_OFFSET_SCALE, "green", stroke_width); -#endif /* CLIPPER_UTILS_DEBUG */ - + ClipperLib::Paths retval; + co.Execute(retval, delta2 * float(CLIPPER_OFFSET_SCALE)); + // unscale output - scaleClipperPolygons(*retval, 1/scale); + unscaleClipperPolygons(retval); + return retval; } -void -offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +Polygons +offset2(const Polygons &polygons, const float delta1, const float delta2, + const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset - ClipperLib::Paths output; - offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit); // convert into ExPolygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + return ClipperPaths_to_Slic3rPolygons(output); } -Slic3r::Polygons -offset2(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) -{ - Slic3r::Polygons pp; - offset2(polygons, &pp, delta1, delta2, scale, joinType, miterLimit); - return pp; -} - -void -offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) +ExPolygons +offset2_ex(const Polygons &polygons, const float delta1, const float delta2, + const ClipperLib::JoinType joinType, const double miterLimit) { // perform offset - ClipperLib::Paths output; - offset2(polygons, &output, delta1, delta2, scale, joinType, miterLimit); + ClipperLib::Paths output = _offset2(polygons, delta1, delta2, joinType, miterLimit); // convert into ExPolygons - ClipperPaths_to_Slic3rExPolygons(output, retval); -} - -Slic3r::ExPolygons -offset2_ex(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, const double scale, const ClipperLib::JoinType joinType, const double miterLimit) -{ - Slic3r::ExPolygons expp; - offset2(polygons, &expp, delta1, delta2, scale, joinType, miterLimit); - return expp; + return ClipperPaths_to_Slic3rExPolygons(output); } template -void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, T* retval, const ClipperLib::PolyFillType fillType, const bool safety_offset_) +T +_clipper_do(const ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { - PROFILE_BLOCK(_clipper_do_polygons); - // read input - ClipperLib::Paths input_subject, input_clip; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); - Slic3rMultiPoints_to_ClipperPaths(clip, &input_clip); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); + ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) { @@ -576,29 +479,23 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polygons &su clipper.Clear(); // add polygons - { - PROFILE_BLOCK(_clipper_do_polygons_AddPaths); - clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); - clipper.AddPaths(input_clip, ClipperLib::ptClip, true); - } + clipper.AddPaths(input_subject, ClipperLib::ptSubject, true); + clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation - { - PROFILE_BLOCK(_clipper_do_polygons_Execute); - clipper.Execute(clipType, *retval, fillType, fillType); - } + T retval; + clipper.Execute(clipType, retval, fillType, fillType); + return retval; } -void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, ClipperLib::PolyTree* retval, const ClipperLib::PolyFillType fillType, +ClipperLib::PolyTree +_clipper_do_pl(const ClipperLib::ClipType clipType, const Polylines &subject, + const Polygons &clip, const ClipperLib::PolyFillType fillType, const bool safety_offset_) { - PROFILE_BLOCK(_clipper_do_polylines); - // read input - ClipperLib::Paths input_subject, input_clip; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); - Slic3rMultiPoints_to_ClipperPaths(clip, &input_clip); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); + ClipperLib::Paths input_clip = Slic3rMultiPoints_to_ClipperPaths(clip); // perform safety offset if (safety_offset_) safety_offset(&input_clip); @@ -608,302 +505,127 @@ void _clipper_do(const ClipperLib::ClipType clipType, const Slic3r::Polylines &s clipper.Clear(); // add polygons - { - PROFILE_BLOCK(_clipper_do_polylines_AddPaths); - clipper.AddPaths(input_subject, ClipperLib::ptSubject, false); - clipper.AddPaths(input_clip, ClipperLib::ptClip, true); - } + clipper.AddPaths(input_subject, ClipperLib::ptSubject, false); + clipper.AddPaths(input_clip, ClipperLib::ptClip, true); // perform operation - { - PROFILE_BLOCK(_clipper_do_polylines_Execute); - clipper.Execute(clipType, *retval, fillType, fillType); - } + ClipperLib::PolyTree retval; + clipper.Execute(clipType, retval, fillType, fillType); + return retval; } -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_) +Polygons +_clipper(ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, bool safety_offset_) +{ + return ClipperPaths_to_Slic3rPolygons(_clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_)); +} + +ExPolygons +_clipper_ex(ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, bool safety_offset_) +{ + ClipperLib::PolyTree polytree = _clipper_do(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_); + return PolyTreeToExPolygons(polytree); +} + +Polylines +_clipper_pl(ClipperLib::ClipType clipType, const Polylines &subject, + const Polygons &clip, bool safety_offset_) { - PROFILE_FUNC(); - // perform operation ClipperLib::Paths output; - _clipper_do(clipType, subject, clip, &output, ClipperLib::pftNonZero, safety_offset_); - - // convert into Polygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + ClipperLib::PolyTreeToPaths(_clipper_do_pl(clipType, subject, clip, ClipperLib::pftNonZero, safety_offset_), output); + return ClipperPaths_to_Slic3rPolylines(output); } -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_) -{ - PROFILE_FUNC(); - // perform operation - ClipperLib::PolyTree polytree; - _clipper_do(clipType, subject, clip, &polytree, ClipperLib::pftNonZero, safety_offset_); - - // convert into ExPolygons - PolyTreeToExPolygons(polytree, retval); -} - -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_) -{ - PROFILE_FUNC(); - // perform operation - ClipperLib::PolyTree polytree; - _clipper_do(clipType, subject, clip, &polytree, ClipperLib::pftNonZero, safety_offset_); - - // convert into Polylines - ClipperLib::Paths output; - ClipperLib::PolyTreeToPaths(polytree, output); - ClipperPaths_to_Slic3rMultiPoints(output, retval); -} - -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, - const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_) -{ - // convert Lines to Polylines - Slic3r::Polylines polylines; - polylines.reserve(subject.size()); - for (Slic3r::Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) - polylines.push_back(*line); - - // perform operation - _clipper(clipType, polylines, clip, &polylines, safety_offset_); - - // convert Polylines to Lines - for (Slic3r::Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) - retval->push_back(*polyline); -} - -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_) +Polylines +_clipper_pl(ClipperLib::ClipType clipType, const Polygons &subject, + const Polygons &clip, bool safety_offset_) { // transform input polygons into polylines - Slic3r::Polylines polylines; + Polylines polylines; polylines.reserve(subject.size()); - for (Slic3r::Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) + for (Polygons::const_iterator polygon = subject.begin(); polygon != subject.end(); ++polygon) polylines.push_back(*polygon); // implicit call to split_at_first_point() // perform clipping - _clipper(clipType, polylines, clip, retval, safety_offset_); + Polylines retval = _clipper_pl(clipType, polylines, clip, safety_offset_); /* If the split_at_first_point() call above happens to split the polygon inside the clipping area we would get two consecutive polylines instead of a single one, so we go through them in order to recombine continuous polylines. */ - for (size_t i = 0; i < retval->size(); ++i) { - for (size_t j = i+1; j < retval->size(); ++j) { - if ((*retval)[i].points.back().coincides_with((*retval)[j].points.front())) { + for (size_t i = 0; i < retval.size(); ++i) { + for (size_t j = i+1; j < retval.size(); ++j) { + if (retval[i].points.back().coincides_with(retval[j].points.front())) { /* If last point of i coincides with first point of j, append points of j to i and delete j */ - (*retval)[i].points.insert((*retval)[i].points.end(), (*retval)[j].points.begin()+1, (*retval)[j].points.end()); - retval->erase(retval->begin() + j); + retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); + retval.erase(retval.begin() + j); --j; - } else if ((*retval)[i].points.front().coincides_with((*retval)[j].points.back())) { + } else if (retval[i].points.front().coincides_with(retval[j].points.back())) { /* If first point of i coincides with last point of j, prepend points of j to i and delete j */ - (*retval)[i].points.insert((*retval)[i].points.begin(), (*retval)[j].points.begin(), (*retval)[j].points.end()-1); - retval->erase(retval->begin() + j); + retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); + retval.erase(retval.begin() + j); --j; - } else if ((*retval)[i].points.front().coincides_with((*retval)[j].points.front())) { + } else if (retval[i].points.front().coincides_with(retval[j].points.front())) { /* Since Clipper does not preserve orientation of polylines, also check the case when first point of i coincides with first point of j. */ - (*retval)[j].reverse(); - (*retval)[i].points.insert((*retval)[i].points.begin(), (*retval)[j].points.begin(), (*retval)[j].points.end()-1); - retval->erase(retval->begin() + j); + retval[j].reverse(); + retval[i].points.insert(retval[i].points.begin(), retval[j].points.begin(), retval[j].points.end()-1); + retval.erase(retval.begin() + j); --j; - } else if ((*retval)[i].points.back().coincides_with((*retval)[j].points.back())) { + } else if (retval[i].points.back().coincides_with(retval[j].points.back())) { /* Since Clipper does not preserve orientation of polylines, also check the case when last point of i coincides with last point of j. */ - (*retval)[j].reverse(); - (*retval)[i].points.insert((*retval)[i].points.end(), (*retval)[j].points.begin()+1, (*retval)[j].points.end()); - retval->erase(retval->begin() + j); + retval[j].reverse(); + retval[i].points.insert(retval[i].points.end(), retval[j].points.begin()+1, retval[j].points.end()); + retval.erase(retval.begin() + j); --j; } } - } -} - -template -void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) -{ - _clipper(ClipperLib::ctDifference, subject, clip, retval, safety_offset_); -} -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -template void diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void diff(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void diff(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); - -template -void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_) -{ - Slic3r::Polygons pp; - for (Slic3r::ExPolygons::const_iterator ex = clip.begin(); ex != clip.end(); ++ex) { - Slic3r::Polygons ppp = *ex; - pp.insert(pp.end(), ppp.begin(), ppp.end()); } - diff(subject, pp, retval, safety_offset_); -} -template void diff(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); - -template -void diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_) -{ - Slic3r::Polygons pp; - for (Slic3r::ExPolygons::const_iterator ex = subject.begin(); ex != subject.end(); ++ex) { - Slic3r::Polygons ppp = *ex; - pp.insert(pp.end(), ppp.begin(), ppp.end()); - } - diff(pp, clip, retval, safety_offset_); -} - -Slic3r::Polygons -diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - Slic3r::Polygons pp; - diff(subject, clip, &pp, safety_offset_); - return pp; -} - -template -Slic3r::ExPolygons -diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_) -{ - Slic3r::ExPolygons expp; - diff(subject, clip, &expp, safety_offset_); - return expp; -} -template Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::ExPolygons diff_ex(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); -template Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); - -template -void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_) -{ - _clipper(ClipperLib::ctIntersection, subject, clip, retval, safety_offset_); -} -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -template void intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); - -template -SubjectType intersection(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - SubjectType pp; - intersection(subject, clip, &pp, safety_offset_); - return pp; -} - -template Slic3r::Polygons intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::Polylines intersection(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::Lines intersection(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_); - -Slic3r::ExPolygons -intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - Slic3r::ExPolygons expp; - intersection(subject, clip, &expp, safety_offset_); - return expp; -} - -template -bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_) -{ - SubjectType retval; - intersection(subject, clip, &retval, safety_offset_); - return !retval.empty(); -} -template bool intersects(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template bool intersects(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template bool intersects(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_); - -void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, - bool safety_offset_) -{ - _clipper(ClipperLib::ctXor, subject, clip, retval, safety_offset_); -} - -template -void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_) -{ - Slic3r::Polygons p; - _clipper(ClipperLib::ctUnion, subject, p, retval, safety_offset_); -} -template void union_(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_); -template void union_(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_); - -Slic3r::Polygons -union_(const Slic3r::Polygons &subject, bool safety_offset) -{ - Polygons pp; - union_(subject, &pp, safety_offset); - return pp; -} - -Slic3r::ExPolygons -union_ex(const Slic3r::Polygons &subject, bool safety_offset) -{ - ExPolygons expp; - union_(subject, &expp, safety_offset); - return expp; -} - -Slic3r::ExPolygons -union_ex(const Slic3r::Surfaces &subject, bool safety_offset) -{ - Polygons pp; - for (Slic3r::Surfaces::const_iterator s = subject.begin(); s != subject.end(); ++s) { - Polygons spp = *s; - pp.insert(pp.end(), spp.begin(), spp.end()); - } - return union_ex(pp, safety_offset); -} - -void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset) -{ - Polygons pp = subject1; - pp.insert(pp.end(), subject2.begin(), subject2.end()); - union_(pp, retval, safety_offset); -} - -Slic3r::Polygons -union_(const Slic3r::ExPolygons &subject1, const Slic3r::ExPolygons &subject2, bool safety_offset) -{ - Polygons pp; - for (Slic3r::ExPolygons::const_iterator it = subject1.begin(); it != subject1.end(); ++it) { - Polygons spp = *it; - pp.insert(pp.end(), spp.begin(), spp.end()); - } - for (Slic3r::ExPolygons::const_iterator it = subject2.begin(); it != subject2.end(); ++it) { - Polygons spp = *it; - pp.insert(pp.end(), spp.begin(), spp.end()); - } - Polygons retval; - union_(pp, &retval, safety_offset); return retval; } -void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_) +Lines +_clipper_ln(ClipperLib::ClipType clipType, const Lines &subject, const Polygons &clip, + bool safety_offset_) { - Slic3r::Polygons clip; - _clipper_do(ClipperLib::ctUnion, subject, clip, retval, ClipperLib::pftEvenOdd, safety_offset_); + // convert Lines to Polylines + Polylines polylines; + polylines.reserve(subject.size()); + for (Lines::const_iterator line = subject.begin(); line != subject.end(); ++line) + polylines.push_back(*line); + + // perform operation + polylines = _clipper_pl(clipType, polylines, clip, safety_offset_); + + // convert Polylines to Lines + Lines retval; + for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) + retval.push_back(*polyline); + return retval; } -void union_pt_chained(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_) +ClipperLib::PolyTree +union_pt(const Polygons &subject, bool safety_offset_) { - ClipperLib::PolyTree pt; - union_pt(subject, &pt, safety_offset_); - if (&subject == retval) - // It is safe to use the same variable for input and output, because this function makes - // a temporary copy of the results. - retval->clear(); - traverse_pt(pt.Childs, retval); + return _clipper_do(ClipperLib::ctUnion, subject, Polygons(), ClipperLib::pftEvenOdd, safety_offset_); } -static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval) +Polygons +union_pt_chained(const Polygons &subject, bool safety_offset_) +{ + ClipperLib::PolyTree polytree = union_pt(subject, safety_offset_); + + Polygons retval; + traverse_pt(polytree.Childs, &retval); + return retval; +} + +void +traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval) { /* use a nearest neighbor search to order these children TODO: supply start_near to chained_path() too? */ @@ -924,22 +646,18 @@ static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval) for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) { // traverse the next depth traverse_pt((*it)->Childs, retval); - - Polygon p; - ClipperPath_to_Slic3rMultiPoint((*it)->Contour, &p); - retval->push_back(p); + retval->push_back(ClipperPath_to_Slic3rPolygon((*it)->Contour)); if ((*it)->IsHole()) retval->back().reverse(); // ccw } } -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool preserve_collinear) +Polygons +simplify_polygons(const Polygons &subject, bool preserve_collinear) { - PROFILE_FUNC(); - // convert into Clipper polygons - ClipperLib::Paths input_subject, output; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); + ClipperLib::Paths output; if (preserve_collinear) { ClipperLib::Clipper c; c.PreserveCollinear(true); @@ -951,23 +669,18 @@ void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval } // convert into Slic3r polygons - ClipperPaths_to_Slic3rMultiPoints(output, retval); + return ClipperPaths_to_Slic3rPolygons(output); } -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool preserve_collinear) +ExPolygons +simplify_polygons_ex(const Polygons &subject, bool preserve_collinear) { - PROFILE_FUNC(); - if (!preserve_collinear) { - Polygons polygons; - simplify_polygons(subject, &polygons, preserve_collinear); - union_(polygons, retval); - return; + return union_ex(simplify_polygons(subject, preserve_collinear)); } // convert into Clipper polygons - ClipperLib::Paths input_subject; - Slic3rMultiPoints_to_ClipperPaths(subject, &input_subject); + ClipperLib::Paths input_subject = Slic3rMultiPoints_to_ClipperPaths(subject); ClipperLib::PolyTree polytree; @@ -978,7 +691,7 @@ void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retv c.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftNonZero, ClipperLib::pftNonZero); // convert into ExPolygons - PolyTreeToExPolygons(polytree, retval); + return PolyTreeToExPolygons(polytree); } void safety_offset(ClipperLib::Paths* paths) @@ -986,22 +699,65 @@ void safety_offset(ClipperLib::Paths* paths) PROFILE_FUNC(); // scale input - scaleClipperPolygons(*paths, CLIPPER_OFFSET_SCALE); + scaleClipperPolygons(*paths); // perform offset (delta = scale 1e-05) ClipperLib::ClipperOffset co; - co.MiterLimit = 2; - { - PROFILE_BLOCK(safety_offset_AddPaths); - co.AddPaths(*paths, ClipperLib::jtMiter, ClipperLib::etClosedPolygon); +#ifdef CLIPPER_UTILS_DEBUG + if (clipper_export_enabled) { + static int iRun = 0; + export_clipper_input_polygons_bin(debug_out_path("safety_offset-polygons-%d", ++iRun).c_str(), *paths, ClipperLib::Paths()); } - { - PROFILE_BLOCK(safety_offset_Execute); - co.Execute(*paths, 10.0 * CLIPPER_OFFSET_SCALE); +#endif /* CLIPPER_UTILS_DEBUG */ + ClipperLib::Paths out; + for (size_t i = 0; i < paths->size(); ++ i) { + ClipperLib::Path &path = (*paths)[i]; + co.Clear(); + co.MiterLimit = 2; + bool ccw = ClipperLib::Orientation(path); + if (! ccw) + std::reverse(path.begin(), path.end()); + { + PROFILE_BLOCK(safety_offset_AddPaths); + co.AddPath((*paths)[i], ClipperLib::jtMiter, ClipperLib::etClosedPolygon); + } + { + PROFILE_BLOCK(safety_offset_Execute); + // offset outside by 10um + ClipperLib::Paths out_this; + co.Execute(out_this, ccw ? 10.f * float(CLIPPER_OFFSET_SCALE) : -10.f * float(CLIPPER_OFFSET_SCALE)); + if (! ccw) { + // Reverse the resulting contours once again. + for (ClipperLib::Paths::iterator it = out_this.begin(); it != out_this.end(); ++ it) + std::reverse(it->begin(), it->end()); + } + if (out.empty()) + out = std::move(out_this); + else + std::move(std::begin(out_this), std::end(out_this), std::back_inserter(out)); + } } + *paths = std::move(out); // unscale output - scaleClipperPolygons(*paths, 1.0/CLIPPER_OFFSET_SCALE); + unscaleClipperPolygons(*paths); } +Polygons top_level_islands(const Slic3r::Polygons &polygons) +{ + // init Clipper + ClipperLib::Clipper clipper; + clipper.Clear(); + // perform union + clipper.AddPaths(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::ptSubject, true); + ClipperLib::PolyTree polytree; + clipper.Execute(ClipperLib::ctUnion, polytree, ClipperLib::pftEvenOdd, ClipperLib::pftEvenOdd); + // Convert only the top level islands to the output. + Polygons out; + out.reserve(polytree.ChildCount()); + for (int i = 0; i < polytree.ChildCount(); ++i) + out.push_back(ClipperPath_to_Slic3rPolygon(polytree.Childs[i]->Contour)); + return out; } + +} \ No newline at end of file diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index 2bb58bdf6..d6060386a 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -14,154 +14,202 @@ using ClipperLib::jtSquare; namespace Slic3r { -// Factor to convert from coord_t (which is int32) to an int64 type used by the Clipper library. -//FIXME Vojtech: Better to use a power of 2 coefficient and to use bit shifts for scaling. -// How about 2^17=131072? -// By the way, is the scalling needed at all? Cura runs all the computation with a fixed point precision of 1um, while Slic3r scales to 1nm, -// further scaling by 10e5 brings us to -#define CLIPPER_OFFSET_SCALE 100000.0 - //----------------------------------------------------------- // legacy code from Clipper documentation void AddOuterPolyNodeToExPolygons(ClipperLib::PolyNode& polynode, Slic3r::ExPolygons& expolygons); void PolyTreeToExPolygons(ClipperLib::PolyTree& polytree, Slic3r::ExPolygons& expolygons); //----------------------------------------------------------- -void Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input, ClipperLib::Path* output); -template -void Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output); -template -void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output); -template -void ClipperPaths_to_Slic3rMultiPoints(const ClipperLib::Paths &input, T* output); -void ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input, Slic3r::ExPolygons* output); - -void scaleClipperPolygons(ClipperLib::Paths &polygons, const double scale); +ClipperLib::Path Slic3rMultiPoint_to_ClipperPath(const Slic3r::MultiPoint &input); +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polygons &input); +ClipperLib::Paths Slic3rMultiPoints_to_ClipperPaths(const Polylines &input); +Slic3r::Polygon ClipperPath_to_Slic3rPolygon(const ClipperLib::Path &input); +Slic3r::Polyline ClipperPath_to_Slic3rPolyline(const ClipperLib::Path &input); +Slic3r::Polygons ClipperPaths_to_Slic3rPolygons(const ClipperLib::Paths &input); +Slic3r::Polylines ClipperPaths_to_Slic3rPolylines(const ClipperLib::Paths &input); +Slic3r::ExPolygons ClipperPaths_to_Slic3rExPolygons(const ClipperLib::Paths &input); // offset Polygons -void offset(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); - -// This is a safe variant of the polygon offset, tailored for a single ExPolygon: -// a single polygon with multiple non-overlapping holes. -// Each contour and hole is offsetted separately, then the holes are subtracted from the outer contours. -void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit); -Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, - double scale, ClipperLib::JoinType joinType, double miterLimit); +ClipperLib::Paths _offset(ClipperLib::Path &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); +ClipperLib::Paths _offset(ClipperLib::Paths &&input, ClipperLib::EndType endType, const float delta, ClipperLib::JoinType joinType, double miterLimit); +inline Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +inline Slic3r::Polygons offset(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } // offset Polylines -void offset(const Slic3r::Polylines &polylines, ClipperLib::Paths* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, - double miterLimit = 3); -void offset(const Slic3r::Polylines &polylines, Slic3r::Polygons* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, - double miterLimit = 3); -void offset(const Slic3r::Surface &surface, Slic3r::Surfaces* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtSquare, - double miterLimit = 3); +inline Slic3r::Polygons offset(const Slic3r::Polyline &polyline, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) + { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polyline), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } +inline Slic3r::Polygons offset(const Slic3r::Polylines &polylines, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtSquare, double miterLimit = 3) + { return ClipperPaths_to_Slic3rPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polylines), ClipperLib::etOpenButt, delta, joinType, miterLimit)); } -void offset(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, - double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); +// offset expolygons and surfaces +ClipperLib::Paths _offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType, double miterLimit); +ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType, double miterLimit); +inline Slic3r::Polygons offset(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rPolygons(_offset(expolygon, delta, joinType, miterLimit)); } +inline Slic3r::Polygons offset(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rPolygons(_offset(expolygons, delta, joinType, miterLimit)); } +inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoint_to_ClipperPath(polygon), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rExPolygons(_offset(Slic3rMultiPoints_to_ClipperPaths(polygons), ClipperLib::etClosedPolygon, delta, joinType, miterLimit)); } +inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygon, delta, joinType, miterLimit)); } +inline Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3) + { return ClipperPaths_to_Slic3rExPolygons(_offset(expolygons, delta, joinType, miterLimit)); } -void offset2(const Slic3r::Polygons &polygons, ClipperLib::Paths* retval, const float delta1, - const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset2(const Slic3r::Polygons &polygons, Slic3r::Polygons* retval, const float delta1, - const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, +ClipperLib::Paths _offset2(const Slic3r::Polygons &polygons, const float delta1, + const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::Polygons offset2(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, - double miterLimit = 3); -void offset2(const Slic3r::Polygons &polygons, Slic3r::ExPolygons* retval, const float delta1, - const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); Slic3r::ExPolygons offset2_ex(const Slic3r::Polygons &polygons, const float delta1, - const float delta2, double scale = CLIPPER_OFFSET_SCALE, ClipperLib::JoinType joinType = ClipperLib::jtMiter, + const float delta2, ClipperLib::JoinType joinType = ClipperLib::jtMiter, double miterLimit = 3); -template -void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, T* retval, bool safety_offset_); -void _clipper_do(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, ClipperLib::Paths* retval, bool safety_offset_); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polygons &subject, - const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Polylines &subject, - const Slic3r::Polygons &clip, Slic3r::Polylines* retval); -void _clipper(ClipperLib::ClipType clipType, const Slic3r::Lines &subject, - const Slic3r::Polygons &clip, Slic3r::Lines* retval); +Slic3r::Polygons _clipper(ClipperLib::ClipType clipType, + const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::ExPolygons _clipper_ex(ClipperLib::ClipType clipType, + const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, + const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Polylines _clipper_pl(ClipperLib::ClipType clipType, + const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +Slic3r::Lines _clipper_ln(ClipperLib::ClipType clipType, + const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); -template -void diff(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +// diff +inline Slic3r::Polygons +diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -void diff(const SubjectType &subject, const Slic3r::ExPolygons &clip, ResultType* retval, bool safety_offset_ = false); +inline Slic3r::ExPolygons +diff_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -Slic3r::Polygons diff(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +inline Slic3r::ExPolygons +diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); +} -template -Slic3r::ExPolygons diff_ex(const SubjectType &subject, const ClipType &clip, bool safety_offset_ = false); +inline Slic3r::Polygons +diff(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctDifference, to_polygons(subject), to_polygons(clip), safety_offset_); +} -template -void intersection(const SubjectType &subject, const Slic3r::Polygons &clip, ResultType* retval, bool safety_offset_ = false); +inline Slic3r::Polylines +diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -SubjectType intersection(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +inline Slic3r::Polylines +diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -Slic3r::ExPolygons -intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +inline Slic3r::Lines +diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ln(ClipperLib::ctDifference, subject, clip, safety_offset_); +} -template -bool intersects(const SubjectType &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false); +// intersection +inline Slic3r::Polygons +intersection(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} -void xor_(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, - bool safety_offset_ = false); +inline Slic3r::ExPolygons +intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} -template -void union_(const Slic3r::Polygons &subject, T* retval, bool safety_offset_ = false); +inline Slic3r::ExPolygons +intersection_ex(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); +} -Slic3r::Polygons union_(const Slic3r::Polygons &subject, bool safety_offset = false); -Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, bool safety_offset = false); -Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject, bool safety_offset = false); +inline Slic3r::Polygons +intersection(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctIntersection, to_polygons(subject), to_polygons(clip), safety_offset_); +} -void union_(const Slic3r::Polygons &subject1, const Slic3r::Polygons &subject2, Slic3r::Polygons* retval, bool safety_offset = false); -Slic3r::Polygons union_(const Slic3r::ExPolygons &subject1, const Slic3r::ExPolygons &subject2, bool safety_offset = false); +inline Slic3r::Polylines +intersection_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} -void union_pt(const Slic3r::Polygons &subject, ClipperLib::PolyTree* retval, bool safety_offset_ = false); -void union_pt_chained(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool safety_offset_ = false); -static void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); +inline Slic3r::Polylines +intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_pl(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::Polygons* retval, bool preserve_collinear = false); -void simplify_polygons(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool preserve_collinear = false); +inline Slic3r::Lines +intersection_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, bool safety_offset_ = false) +{ + return _clipper_ln(ClipperLib::ctIntersection, subject, clip, safety_offset_); +} + +// union +inline Slic3r::Polygons +union_(const Slic3r::Polygons &subject, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); +} + +inline Slic3r::Polygons +union_(const Slic3r::Polygons &subject, const Slic3r::Polygons &subject2, bool safety_offset_ = false) +{ + return _clipper(ClipperLib::ctUnion, subject, subject2, safety_offset_); +} + +inline Slic3r::ExPolygons +union_ex(const Slic3r::Polygons &subject, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctUnion, subject, Slic3r::Polygons(), safety_offset_); +} + +inline Slic3r::ExPolygons +union_ex(const Slic3r::ExPolygons &subject, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); +} + +inline Slic3r::ExPolygons +union_ex(const Slic3r::Surfaces &subject, bool safety_offset_ = false) +{ + return _clipper_ex(ClipperLib::ctUnion, to_polygons(subject), Slic3r::Polygons(), safety_offset_); +} + + +ClipperLib::PolyTree union_pt(const Slic3r::Polygons &subject, bool safety_offset_ = false); +Slic3r::Polygons union_pt_chained(const Slic3r::Polygons &subject, bool safety_offset_ = false); +void traverse_pt(ClipperLib::PolyNodes &nodes, Slic3r::Polygons* retval); + +/* OTHER */ +Slic3r::Polygons simplify_polygons(const Slic3r::Polygons &subject, bool preserve_collinear = false); +Slic3r::ExPolygons simplify_polygons_ex(const Slic3r::Polygons &subject, bool preserve_collinear = false); void safety_offset(ClipperLib::Paths* paths); +Polygons top_level_islands(const Slic3r::Polygons &polygons); + } -#endif +#endif \ No newline at end of file diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 3fbd82060..6c8fa9f27 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_Config_hpp_ #define slic3r_Config_hpp_ +#include #include #include #include @@ -73,11 +74,8 @@ class ConfigOptionVector : public ConfigOptionVectorBase }; T get_at(size_t i) const { - try { - return this->values.at(i); - } catch (const std::out_of_range& oor) { - return this->values.front(); - } + assert(! this->values.empty()); + return (i < this->values.size()) ? this->values[i] : this->values.front(); }; }; diff --git a/xs/src/libslic3r/EdgeGrid.cpp b/xs/src/libslic3r/EdgeGrid.cpp index 67c438f8e..07d08f390 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #ifdef SLIC3R_GUI #include @@ -109,7 +110,6 @@ void EdgeGrid::Grid::create(const ExPolygonCollection &expolygons, coord_t resol void EdgeGrid::Grid::create_from_m_contours(coord_t resolution) { // 1) Measure the bounding box. - m_bbox.defined = false; for (size_t i = 0; i < m_contours.size(); ++ i) { const Slic3r::Points &pts = *m_contours[i]; for (size_t j = 0; j < pts.size(); ++ j) @@ -839,6 +839,8 @@ void EdgeGrid::Grid::calculate_sdf() } #if 0 + static int iRun = 0; + ++ iRun; //#ifdef SLIC3R_GUI { wxImage img(ncols, nrows); @@ -862,7 +864,7 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("unsigned_df.png"), wxBITMAP_TYPE_PNG); + img.SaveFile(debug_out_path("unsigned_df-%d.png", iRun), wxBITMAP_TYPE_PNG); } { wxImage img(ncols, nrows); @@ -895,7 +897,7 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("signed_df.png"), wxBITMAP_TYPE_PNG); + img.SaveFile(debug_out_path("signed_df-%d.png", iRun), wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ @@ -1020,7 +1022,7 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("signed_df-signs.png"), wxBITMAP_TYPE_PNG); + img.SaveFile(debug_out_path("signed_df-signs-%d.png", iRun), wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ @@ -1049,7 +1051,7 @@ void EdgeGrid::Grid::calculate_sdf() } } } - img.SaveFile(debug_out_path("signed_df2.png"), wxBITMAP_TYPE_PNG); + img.SaveFile(debug_out_path("signed_df2-%d.png", iRun), wxBITMAP_TYPE_PNG); } #endif /* SLIC3R_GUI */ } @@ -1118,7 +1120,7 @@ float EdgeGrid::Grid::signed_distance_bilinear(const Point &pt) const return f; } - + bool EdgeGrid::Grid::signed_distance_edges(const Point &pt, coord_t search_radius, coordf_t &result_min_dist, bool *pon_segment) const { BoundingBox bbox; bbox.min = bbox.max = Point(pt.x - m_bbox.min.x, pt.y - m_bbox.min.y); @@ -1222,6 +1224,135 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo return true; } +Polygons EdgeGrid::Grid::contours_simplified(coord_t offset) const +{ + typedef std::unordered_multimap EndPointMapType; + // 0) Prepare a binary grid. + size_t cell_rows = m_rows + 2; + size_t cell_cols = m_cols + 2; + std::vector cell_inside(cell_rows * cell_cols, false); + for (int r = 0; r < int(cell_rows); ++ r) + for (int c = 0; c < int(cell_cols); ++ c) + cell_inside[r * cell_cols + c] = cell_inside_or_crossing(r - 1, c - 1); + // Fill in empty cells, which have a left / right neighbor filled. + // Fill in empty cells, which have the top / bottom neighbor filled. + { + std::vector cell_inside2(cell_inside); + for (int r = 1; r + 1 < int(cell_rows); ++ r) { + for (int c = 1; c + 1 < int(cell_cols); ++ c) { + int addr = r * cell_cols + c; + if ((cell_inside2[addr - 1] && cell_inside2[addr + 1]) || + (cell_inside2[addr - cell_cols] && cell_inside2[addr + cell_cols])) + cell_inside[addr] = true; + } + } + } + + // 1) Collect the lines. + std::vector lines; + EndPointMapType start_point_to_line_idx; + for (int r = 0; r <= int(m_rows); ++ r) { + for (int c = 0; c <= int(m_cols); ++ c) { + int addr = (r + 1) * cell_cols + c + 1; + bool left = cell_inside[addr - 1]; + bool top = cell_inside[addr - cell_cols]; + bool current = cell_inside[addr]; + if (left != current) { + lines.push_back( + left ? + Line(Point(c, r+1), Point(c, r )) : + Line(Point(c, r ), Point(c, r+1))); + start_point_to_line_idx.insert(std::pair(lines.back().a, int(lines.size()) - 1)); + } + if (top != current) { + lines.push_back( + top ? + Line(Point(c , r), Point(c+1, r)) : + Line(Point(c+1, r), Point(c , r))); + start_point_to_line_idx.insert(std::pair(lines.back().a, int(lines.size()) - 1)); + } + } + } + + // 2) Chain the lines. + std::vector line_processed(lines.size(), false); + Polygons out; + for (int i_candidate = 0; i_candidate < int(lines.size()); ++ i_candidate) { + if (line_processed[i_candidate]) + continue; + Polygon poly; + line_processed[i_candidate] = true; + poly.points.push_back(lines[i_candidate].b); + int i_line_current = i_candidate; + for (;;) { + std::pair line_range = + start_point_to_line_idx.equal_range(lines[i_line_current].b); + // The interval has to be non empty, there shall be at least one line continuing the current one. + assert(line_range.first != line_range.second); + int i_next = -1; + for (EndPointMapType::iterator it = line_range.first; it != line_range.second; ++ it) { + if (it->second == i_candidate) { + // closing the loop. + goto end_of_poly; + } + if (line_processed[it->second]) + continue; + if (i_next == -1) { + i_next = it->second; + } else { + // This is a corner, where two lines meet exactly. Pick the line, which encloses a smallest angle with + // the current edge. + const Line &line_current = lines[i_line_current]; + const Line &line_next = lines[it->second]; + const Vector v1 = line_current.vector(); + const Vector v2 = line_next.vector(); + int64_t cross = int64_t(v1.x) * int64_t(v2.y) - int64_t(v2.x) * int64_t(v1.y); + if (cross > 0) { + // This has to be a convex right angle. There is no better next line. + i_next = it->second; + break; + } + } + } + line_processed[i_next] = true; + i_line_current = i_next; + poly.points.push_back(lines[i_line_current].b); + } + end_of_poly: + out.push_back(std::move(poly)); + } + + // 3) Scale the polygons back into world, shrink slightly and remove collinear points. + for (size_t i = 0; i < out.size(); ++ i) { + Polygon &poly = out[i]; + for (size_t j = 0; j < poly.points.size(); ++ j) { + Point &p = poly.points[j]; + p.x *= m_resolution; + p.y *= m_resolution; + p.x += m_bbox.min.x; + p.y += m_bbox.min.y; + } + // Shrink the contour slightly, so if the same contour gets discretized and simplified again, one will get the same result. + // Remove collineaer points. + Points pts; + pts.reserve(poly.points.size()); + for (size_t j = 0; j < poly.points.size(); ++ j) { + size_t j0 = (j == 0) ? poly.points.size() - 1 : j - 1; + size_t j2 = (j + 1 == poly.points.size()) ? 0 : j + 1; + Point v = poly.points[j2] - poly.points[j0]; + if (v.x != 0 && v.y != 0) { + // This is a corner point. Copy it to the output contour. + Point p = poly.points[j]; + p.y += (v.x < 0) ? - offset : offset; + p.x += (v.y > 0) ? - offset : offset; + pts.push_back(p); + } + } + poly.points = std::move(pts); + } + return out; +} + #ifdef SLIC3R_GUI void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coord_t resolution, const char *path) { @@ -1235,17 +1366,18 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo ++iRun; const coord_t search_radius = grid.resolution() * 2; - const coord_t display_blend_radius = grid.resolution() * 5; + const coord_t display_blend_radius = grid.resolution() * 2; for (coord_t r = 0; r < h; ++r) { for (coord_t c = 0; c < w; ++ c) { unsigned char *pxl = data + (((h - r - 1) * w) + c) * 3; Point pt(c * resolution + bbox.min.x, r * resolution + bbox.min.y); coordf_t min_dist; - bool on_segment; -// if (grid.signed_distance_edges(pt, search_radius, min_dist, &on_segment)) { + bool on_segment = true; + #if 0 + if (grid.signed_distance_edges(pt, search_radius, min_dist, &on_segment)) { + #else if (grid.signed_distance(pt, search_radius, min_dist)) { - //FIXME - on_segment = true; + #endif float s = 255 * std::abs(min_dist) / float(display_blend_radius); int is = std::max(0, std::min(255, int(floor(s + 0.5f)))); if (min_dist < 0) { @@ -1254,9 +1386,9 @@ void EdgeGrid::save_png(const EdgeGrid::Grid &grid, const BoundingBox &bbox, coo pxl[1] = 255 - is; pxl[2] = 255 - is; } else { - pxl[0] = 128; - pxl[1] = 128; - pxl[2] = 255 - is; + pxl[0] = 255; + pxl[1] = 0; + pxl[2] = 255 - is; } } else { diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp index 626a430cd..a0b23211e 100644 --- a/xs/src/libslic3r/EdgeGrid.hpp +++ b/xs/src/libslic3r/EdgeGrid.hpp @@ -12,11 +12,14 @@ namespace Slic3r { namespace EdgeGrid { -struct Grid +class Grid { +public: Grid(); ~Grid(); + void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; } + void create(const Polygons &polygons, coord_t resolution); void create(const ExPolygon &expoly, coord_t resolution); void create(const ExPolygons &expolygons, coord_t resolution); @@ -54,6 +57,9 @@ struct Grid const size_t rows() const { return m_rows; } const size_t cols() const { return m_cols; } + // For supports: Contours enclosing the rasterized edges. + Polygons contours_simplified(coord_t offset) const; + protected: struct Cell { Cell() : begin(0), end(0) {} @@ -65,6 +71,18 @@ protected: #if 0 bool line_cell_intersect(const Point &p1, const Point &p2, const Cell &cell); #endif + bool cell_inside_or_crossing(int r, int c) const + { + if (r < 0 || r >= m_rows || + c < 0 || c >= m_cols) + // The cell is outside the domain. Hoping that the contours were correctly oriented, so + // there is a CCW outmost contour so the out of domain cells are outside. + return false; + const Cell &cell = m_cells[r * m_cols + c]; + return + (cell.begin < cell.end) || + (! m_signed_distance_field.empty() && m_signed_distance_field[r * (m_cols + 1) + c] <= 0.f); + } // Bounding box around the contours. BoundingBox m_bbox; diff --git a/xs/src/libslic3r/ExPolygon.cpp b/xs/src/libslic3r/ExPolygon.cpp index fbb616ebb..06ba28ed2 100644 --- a/xs/src/libslic3r/ExPolygon.cpp +++ b/xs/src/libslic3r/ExPolygon.cpp @@ -99,9 +99,7 @@ ExPolygon::contains(const Line &line) const bool ExPolygon::contains(const Polyline &polyline) const { - Polylines pl_out; - diff((Polylines)polyline, *this, &pl_out); - return pl_out.empty(); + return diff_pl((Polylines)polyline, *this).empty(); } bool @@ -115,8 +113,7 @@ ExPolygon::contains(const Polylines &polylines) const svg.draw_outline(*this); svg.draw(polylines, "blue"); #endif - Polylines pl_out; - diff(polylines, *this, &pl_out); + Polylines pl_out = diff_pl(polylines, *this); #if 0 svg.draw(pl_out, "red"); #endif @@ -162,8 +159,7 @@ ExPolygon::overlaps(const ExPolygon &other) const svg.draw_outline(*this); svg.draw_outline(other, "blue"); #endif - Polylines pl_out; - intersection((Polylines)other, *this, &pl_out); + Polylines pl_out = intersection_pl((Polylines)other, *this); #if 0 svg.draw(pl_out, "red"); #endif @@ -396,11 +392,8 @@ ExPolygon::get_trapezoids2(Polygons* polygons) const poly[3].y = bb.max.y; // intersect with this expolygon - Polygons trapezoids; - intersection(poly, *this, &trapezoids); - // append results to return value - polygons->insert(polygons->end(), trapezoids.begin(), trapezoids.end()); + polygons_append(*polygons, intersection(poly, to_polygons(*this))); } } @@ -434,16 +427,13 @@ ExPolygon::triangulate_pp(Polygons* polygons) const // convert polygons std::list input; - Polygons pp = *this; - simplify_polygons(pp, &pp, true); - ExPolygons expp; - union_(pp, &expp); + ExPolygons expp = union_ex(simplify_polygons(to_polygons(*this), true)); for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { // contour { TPPLPoly p; - p.Init(ex->contour.points.size()); + p.Init(int(ex->contour.points.size())); //printf(PRINTF_ZU "\n0\n", ex->contour.points.size()); for (Points::const_iterator point = ex->contour.points.begin(); point != ex->contour.points.end(); ++point) { p[ point-ex->contour.points.begin() ].x = point->x; @@ -480,8 +470,8 @@ ExPolygon::triangulate_pp(Polygons* polygons) const Polygon p; p.points.resize(num_points); for (long i = 0; i < num_points; ++i) { - p.points[i].x = (*poly)[i].x; - p.points[i].y = (*poly)[i].y; + p.points[i].x = coord_t((*poly)[i].x); + p.points[i].y = coord_t((*poly)[i].y); } polygons->push_back(p); } @@ -490,8 +480,7 @@ ExPolygon::triangulate_pp(Polygons* polygons) const void ExPolygon::triangulate_p2t(Polygons* polygons) const { - ExPolygons expp; - simplify_polygons(*this, &expp, true); + ExPolygons expp = simplify_polygons_ex(*this, true); for (ExPolygons::const_iterator ex = expp.begin(); ex != expp.end(); ++ex) { // TODO: prevent duplicate points diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index 0a212668f..636b07cc7 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -13,9 +13,17 @@ typedef std::vector ExPolygons; class ExPolygon { - public: +public: + ExPolygon() {} + ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} + ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {} + + ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; } + ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } + Polygon contour; Polygons holes; + operator Points() const; operator Polygons() const; operator Polylines() const; @@ -253,6 +261,18 @@ inline void polygons_append(Polygons &dst, ExPolygons &&src) } #endif +inline void expolygons_append(ExPolygons &dst, const ExPolygons &src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + +#if SLIC3R_CPPVER >= 11 +inline void expolygons_append(ExPolygons &dst, ExPolygons &&src) +{ + std::move(std::begin(src), std::end(src), std::back_inserter(dst)); +} +#endif + inline void expolygons_rotate(ExPolygons &expolys, double angle) { for (ExPolygons::iterator p = expolys.begin(); p != expolys.end(); ++p) @@ -281,37 +301,37 @@ extern bool remove_sticks(ExPolygon &poly); #include namespace boost { namespace polygon { template <> - struct polygon_traits { + struct polygon_traits { typedef coord_t coordinate_type; - typedef Points::const_iterator iterator_type; - typedef Point point_type; + typedef Slic3r::Points::const_iterator iterator_type; + typedef Slic3r::Point point_type; // Get the begin iterator - static inline iterator_type begin_points(const ExPolygon& t) { + static inline iterator_type begin_points(const Slic3r::ExPolygon& t) { return t.contour.points.begin(); } // Get the end iterator - static inline iterator_type end_points(const ExPolygon& t) { + static inline iterator_type end_points(const Slic3r::ExPolygon& t) { return t.contour.points.end(); } // Get the number of sides of the polygon - static inline std::size_t size(const ExPolygon& t) { + static inline std::size_t size(const Slic3r::ExPolygon& t) { return t.contour.points.size(); } // Get the winding direction of the polygon - static inline winding_direction winding(const ExPolygon& t) { + static inline winding_direction winding(const Slic3r::ExPolygon& t) { return unknown_winding; } }; template <> - struct polygon_mutable_traits { + struct polygon_mutable_traits { //expects stl style iterators template - static inline ExPolygon& set_points(ExPolygon& expolygon, iT input_begin, iT input_end) { + static inline Slic3r::ExPolygon& set_points(Slic3r::ExPolygon& expolygon, iT input_begin, iT input_end) { expolygon.contour.points.assign(input_begin, input_end); // skip last point since Boost will set last point = first point expolygon.contour.points.pop_back(); @@ -321,27 +341,27 @@ namespace boost { namespace polygon { template <> - struct geometry_concept { typedef polygon_with_holes_concept type; }; + struct geometry_concept { typedef polygon_with_holes_concept type; }; template <> - struct polygon_with_holes_traits { - typedef Polygons::const_iterator iterator_holes_type; - typedef Polygon hole_type; - static inline iterator_holes_type begin_holes(const ExPolygon& t) { + struct polygon_with_holes_traits { + typedef Slic3r::Polygons::const_iterator iterator_holes_type; + typedef Slic3r::Polygon hole_type; + static inline iterator_holes_type begin_holes(const Slic3r::ExPolygon& t) { return t.holes.begin(); } - static inline iterator_holes_type end_holes(const ExPolygon& t) { + static inline iterator_holes_type end_holes(const Slic3r::ExPolygon& t) { return t.holes.end(); } - static inline unsigned int size_holes(const ExPolygon& t) { + static inline unsigned int size_holes(const Slic3r::ExPolygon& t) { return (int)t.holes.size(); } }; template <> - struct polygon_with_holes_mutable_traits { + struct polygon_with_holes_mutable_traits { template - static inline ExPolygon& set_holes(ExPolygon& t, iT inputBegin, iT inputEnd) { + static inline Slic3r::ExPolygon& set_holes(Slic3r::ExPolygon& t, iT inputBegin, iT inputEnd) { t.holes.assign(inputBegin, inputEnd); return t; } @@ -349,32 +369,32 @@ namespace boost { namespace polygon { //first we register CPolygonSet as a polygon set template <> - struct geometry_concept { typedef polygon_set_concept type; }; + struct geometry_concept { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> - struct polygon_set_traits { + struct polygon_set_traits { typedef coord_t coordinate_type; - typedef ExPolygons::const_iterator iterator_type; - typedef ExPolygons operator_arg_type; + typedef Slic3r::ExPolygons::const_iterator iterator_type; + typedef Slic3r::ExPolygons operator_arg_type; - static inline iterator_type begin(const ExPolygons& polygon_set) { + static inline iterator_type begin(const Slic3r::ExPolygons& polygon_set) { return polygon_set.begin(); } - static inline iterator_type end(const ExPolygons& polygon_set) { + static inline iterator_type end(const Slic3r::ExPolygons& polygon_set) { return polygon_set.end(); } //don't worry about these, just return false from them - static inline bool clean(const ExPolygons& polygon_set) { return false; } - static inline bool sorted(const ExPolygons& polygon_set) { return false; } + static inline bool clean(const Slic3r::ExPolygons& polygon_set) { return false; } + static inline bool sorted(const Slic3r::ExPolygons& polygon_set) { return false; } }; template <> - struct polygon_set_mutable_traits { + struct polygon_set_mutable_traits { template - static inline void set(ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) { + static inline void set(Slic3r::ExPolygons& expolygons, input_iterator_type input_begin, input_iterator_type input_end) { expolygons.assign(input_begin, input_end); } }; diff --git a/xs/src/libslic3r/ExtrusionEntity.cpp b/xs/src/libslic3r/ExtrusionEntity.cpp index 624cdccbc..9f59ca386 100644 --- a/xs/src/libslic3r/ExtrusionEntity.cpp +++ b/xs/src/libslic3r/ExtrusionEntity.cpp @@ -13,19 +13,13 @@ namespace Slic3r { void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - // perform clipping - Polylines clipped; - intersection(this->polyline, collection, &clipped); - return this->_inflate_collection(clipped, retval); + this->_inflate_collection(intersection_pl(this->polyline, collection), retval); } void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - // perform clipping - Polylines clipped; - diff(this->polyline, collection, &clipped); - return this->_inflate_collection(clipped, retval); + this->_inflate_collection(diff_pl(this->polyline, collection), retval); } void @@ -58,9 +52,7 @@ ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCo void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { - Polygons tmp; - offset(this->polyline, &tmp, scale_(this->width/2) + scaled_epsilon); - polygons_append(out, STDMOVE(tmp)); + polygons_append(out, offset(this->polyline, float(scale_(this->width/2)) + scaled_epsilon)); } void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const @@ -68,9 +60,7 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale // Instantiating the Flow class to get the line spacing. // Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler. Flow flow(this->width, this->height, 0.f, this->is_bridge()); - Polygons tmp; - offset(this->polyline, &tmp, 0.5f * flow.scaled_spacing() + scaled_epsilon); - polygons_append(out, STDMOVE(tmp)); + polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon)); } bool diff --git a/xs/src/libslic3r/ExtrusionEntity.hpp b/xs/src/libslic3r/ExtrusionEntity.hpp index 524ab8c54..dfc28d035 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -69,11 +69,11 @@ class ExtrusionPath : public ExtrusionEntity public: Polyline polyline; ExtrusionRole role; - // Volumetric velocity. mm^3 of plastic per mm of linear head motion + // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. double mm3_per_mm; - // Width of the extrusion. + // Width of the extrusion, used for visualization purposes. float width; - // Height of the extrusion. + // Height of the extrusion, used for visualization purposed. float height; ExtrusionPath(ExtrusionRole role) : role(role), mm3_per_mm(-1), width(-1), height(-1) {}; @@ -194,6 +194,48 @@ class ExtrusionLoop : public ExtrusionEntity Polyline as_polyline() const { return this->polygon().split_at_first_point(); } }; +inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +{ + dst.reserve(dst.size() + polylines.size()); + for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); + dst.back().polyline = *it_polyline; + } +} + +#if SLIC3R_CPPVER >= 11 +inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +{ + dst.reserve(dst.size() + polylines.size()); + for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + dst.push_back(ExtrusionPath(role, mm3_per_mm, width, height)); + dst.back().polyline = std::move(*it_polyline); + } +} +#endif // SLIC3R_CPPVER >= 11 + +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +{ + dst.reserve(dst.size() + polylines.size()); + for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); + dst.push_back(extrusion_path); + extrusion_path->polyline = *it_polyline; + } +} + +#if SLIC3R_CPPVER >= 11 +inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines &&polylines, ExtrusionRole role, double mm3_per_mm, float width, float height) +{ + dst.reserve(dst.size() + polylines.size()); + for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { + ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height); + dst.push_back(extrusion_path); + extrusion_path->polyline = std::move(*it_polyline); + } +} +#endif // SLIC3R_CPPVER >= 11 + } #endif diff --git a/xs/src/libslic3r/ExtrusionSimulator.cpp b/xs/src/libslic3r/ExtrusionSimulator.cpp index 0c86f073e..3752caed4 100644 --- a/xs/src/libslic3r/ExtrusionSimulator.cpp +++ b/xs/src/libslic3r/ExtrusionSimulator.cpp @@ -803,7 +803,7 @@ void gcode_spread_points( const Cell &cell = cells[i]; acc[cell.idx.y()][cell.idx.x()] = (1.f - cell.fraction_covered) * cell.volume + cell.fraction_covered * cell.area * height_avg; } - } else if (simulationType == ExtrusionSimulationSpreadExcess) { + } else if (simulationType == Slic3r::ExtrusionSimulationSpreadExcess) { // The volume under the circle does not fit. // 1) Fill the underfilled cells and remove them from the list. float volume_borrowed_total = 0.; diff --git a/xs/src/libslic3r/ExtrusionSimulator.hpp b/xs/src/libslic3r/ExtrusionSimulator.hpp index 8b956ec11..040406766 100644 --- a/xs/src/libslic3r/ExtrusionSimulator.hpp +++ b/xs/src/libslic3r/ExtrusionSimulator.hpp @@ -13,7 +13,7 @@ enum ExtrusionSimulationType ExtrusionSimulationDontSpread, ExtrisopmSimulationSpreadNotOverfilled, ExtrusionSimulationSpreadFull, - ExtrusionSimulationSpreadExcess, + ExtrusionSimulationSpreadExcess }; // An opaque class, to keep the boost stuff away from the header. diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp index d840d44c0..b4c14bb6c 100644 --- a/xs/src/libslic3r/Fill/Fill.cpp +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -246,22 +246,21 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, h, is_bridge || f->use_bridge_flow()); } - // save into layer - { - ExtrusionRole role = is_bridge ? erBridgeInfill : - (surface.is_solid() ? ((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) : erInternalInfill); - ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); - out.entities.push_back(&collection); - // Only concentric fills are not sorted. - collection.no_sort = f->no_sort(); - for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it) { - ExtrusionPath *path = new ExtrusionPath(role, flow.mm3_per_mm(), flow.width, flow.height); - collection.entities.push_back(path); - path->polyline.points.swap(it->points); - } - } + // Save into layer. + auto *eec = new ExtrusionEntityCollection(); + out.entities.push_back(eec); + // Only concentric fills are not sorted. + eec->no_sort = f->no_sort(); + extrusion_entities_append_paths( + eec->entities, STDMOVE(polylines), + is_bridge ? + erBridgeInfill : + (surface.is_solid() ? + ((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) : + erInternalInfill), + flow.mm3_per_mm(), flow.width, flow.height); } - + // add thin fill regions // thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection // Unpacks the collection, creates multiple collections per path. diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp index c37328c69..c763952d6 100644 --- a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -168,7 +168,7 @@ void Fill3DHoneycomb::_fill_surface_single( it->translate(bb.min.x, bb.min.y); // clip pattern to boundaries - intersection(polylines, (Polygons)expolygon, &polylines); + polylines = intersection_pl(polylines, (Polygons)expolygon); // connect lines if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections diff --git a/xs/src/libslic3r/Fill/FillBase.cpp b/xs/src/libslic3r/Fill/FillBase.cpp index d19615aa9..7258e11fe 100644 --- a/xs/src/libslic3r/Fill/FillBase.cpp +++ b/xs/src/libslic3r/Fill/FillBase.cpp @@ -45,8 +45,7 @@ Fill* Fill::new_from_type(const std::string &type) Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms) { // Perform offset. - Slic3r::ExPolygons expp; - offset(surface->expolygon, &expp, -0.5*scale_(this->spacing)); + Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(-0.5*scale_(this->spacing))); // Create the infills for each of the regions. Polylines polylines_out; for (size_t i = 0; i < expp.size(); ++ i) diff --git a/xs/src/libslic3r/Fill/FillConcentric.cpp b/xs/src/libslic3r/Fill/FillConcentric.cpp index 27914866d..156e3478a 100644 --- a/xs/src/libslic3r/Fill/FillConcentric.cpp +++ b/xs/src/libslic3r/Fill/FillConcentric.cpp @@ -33,7 +33,7 @@ void FillConcentric::_fill_surface_single( // generate paths from the outermost to the innermost, to avoid // adhesion problems of the first central tiny loops - union_pt_chained(loops, &loops, false); + loops = union_pt_chained(loops, false); // split paths using a nearest neighbor search size_t iPathFirst = polylines_out.size(); diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index b264d80cc..22dea85da 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -93,7 +93,7 @@ void FillHoneycomb::_fill_surface_single( Polylines p; for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) p.push_back((Polyline)(*it)); - intersection(p, (Polygons)expolygon, &paths); + paths = intersection_pl(p, to_polygons(expolygon)); } // connect paths @@ -122,7 +122,7 @@ void FillHoneycomb::_fill_surface_single( } // clip paths again to prevent connection segments from crossing the expolygon boundaries - intersection(paths, to_polygons(offset_ex(expolygon, SCALED_EPSILON)), &paths); + paths = intersection_pl(paths, to_polygons(offset_ex(expolygon, SCALED_EPSILON))); // Move the polylines to the output, avoid a deep copy. size_t j = polylines_out.size(); polylines_out.resize(j + paths.size(), Polyline()); diff --git a/xs/src/libslic3r/Fill/FillPlanePath.cpp b/xs/src/libslic3r/Fill/FillPlanePath.cpp index 34e155a06..f71ef95a1 100644 --- a/xs/src/libslic3r/Fill/FillPlanePath.cpp +++ b/xs/src/libslic3r/Fill/FillPlanePath.cpp @@ -44,7 +44,7 @@ void FillPlanePath::_fill_surface_single( coord_t(floor(it->x * distance_between_lines + 0.5)), coord_t(floor(it->y * distance_between_lines + 0.5)))); // intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), &polylines); - intersection(polylines, (Polygons)expolygon, &polylines); + polylines = intersection_pl(polylines, to_polygons(expolygon)); /* if (1) { diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index 5559a5437..991adc0b3 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -63,7 +63,7 @@ void FillRectilinear::_fill_surface_single( pts.push_back(it->a); pts.push_back(it->b); } - Polylines polylines = intersection(polylines_src, offset((Polygons)expolygon, scale_(0.02)), false); + Polylines polylines = intersection_pl(polylines_src, offset(to_polygons(expolygon), scale_(0.02)), false); // FIXME Vojtech: This is only performed for horizontal lines, not for the vertical lines! const float INFILL_OVERLAP_OVER_SPACING = 0.3f; diff --git a/xs/src/libslic3r/Fill/FillRectilinear2.cpp b/xs/src/libslic3r/Fill/FillRectilinear2.cpp index 03d92ce82..2edd6f72d 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear2.cpp @@ -372,11 +372,9 @@ public: bool sticks_removed = remove_sticks(polygons_src); // if (sticks_removed) printf("Sticks removed!\n"); polygons_outer = offset(polygons_src, aoffset1, - CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, mitterLimit); polygons_inner = offset(polygons_outer, aoffset2 - aoffset1, - CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, mitterLimit); // Filter out contours with zero area or small area, contours with 2 points only. @@ -884,7 +882,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP Point refpt = rotate_vector.second.rotated(- rotate_vector.first); // _align_to_grid will not work correctly with positive pattern_shift. coord_t pattern_shift_scaled = coord_t(scale_(pattern_shift)) % line_spacing; - refpt.x -= (pattern_shift_scaled > 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); + refpt.x -= (pattern_shift_scaled >= 0) ? pattern_shift_scaled : (line_spacing + pattern_shift_scaled); bounding_box.merge(_align_to_grid( bounding_box.min, Point(line_spacing, line_spacing), @@ -894,7 +892,9 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP // Intersect a set of euqally spaced vertical lines wiht expolygon. // n_vlines = ceil(bbox_width / line_spacing) size_t n_vlines = (bounding_box.max.x - bounding_box.min.x + line_spacing - 1) / line_spacing; - coord_t x0 = bounding_box.min.x + (line_spacing + SCALED_EPSILON) / 2; + coord_t x0 = bounding_box.min.x; + if (full_infill) + x0 += (line_spacing + SCALED_EPSILON) / 2; #ifdef SLIC3R_DEBUG static int iRun = 0; diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index 49a55a50e..c7e5f8495 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -40,14 +40,17 @@ Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool /* This method returns the centerline spacing between two adjacent extrusions having the same extrusion width (and other properties). */ float -Flow::spacing() const { - if (this->bridge) { +Flow::spacing() const +{ +#ifdef HAS_PERIMETER_LINE_OVERLAP + if (this->bridge) return this->width + BRIDGE_EXTRA_SPACING; - } - // rectangle with semicircles at the ends float min_flow_spacing = this->width - this->height * (1 - PI/4.0); - return this->width - OVERLAP_FACTOR * (this->width - min_flow_spacing); + return this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing); +#else + return this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1 - PI/4.0)); +#endif } /* This method returns the centerline spacing between an extrusion using this @@ -57,23 +60,17 @@ float Flow::spacing(const Flow &other) const { assert(this->height == other.height); assert(this->bridge == other.bridge); - - if (this->bridge) { - return this->width/2 + other.width/2 + BRIDGE_EXTRA_SPACING; - } - - return this->spacing()/2 + other.spacing()/2; + return this->bridge ? + 0.5f * this->width + 0.5f * other.width + BRIDGE_EXTRA_SPACING : + 0.5f * this->spacing() + 0.5f * other.spacing(); } /* This method returns extrusion volume per head move unit. */ -double -Flow::mm3_per_mm() const { - if (this->bridge) { - return (this->width * this->width) * PI/4.0; - } - - // rectangle with semicircles at the ends - return this->width * this->height + (this->height*this->height) / 4.0 * (PI-4.0); +double Flow::mm3_per_mm() const +{ + return this->bridge ? + (this->width * this->width) * PI/4.0 : + this->width * this->height + (this->height * this->height) / 4.0 * (PI-4.0); } /* This static method returns bridge width for a given nozzle diameter. */ @@ -85,8 +82,7 @@ float Flow::_bridge_width(float nozzle_diameter, float bridge_flow_ratio) { } /* This static method returns a sane extrusion width default. */ -float -Flow::_auto_width(FlowRole role, float nozzle_diameter, float height) { +float Flow::_auto_width(FlowRole role, float nozzle_diameter, float height) { // here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate // shape: rectangle with semicircles at the ends float width = ((nozzle_diameter*nozzle_diameter) * PI + (height*height) * (4.0 - PI)) / (4.0 * height); @@ -106,14 +102,15 @@ Flow::_auto_width(FlowRole role, float nozzle_diameter, float height) { } /* This static method returns the extrusion width value corresponding to the supplied centerline spacing. */ -float -Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) { - if (bridge) { - return spacing - BRIDGE_EXTRA_SPACING; - } - - // rectangle with semicircles at the ends - return spacing + OVERLAP_FACTOR * height * (1 - PI/4.0); +float Flow::_width_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge) +{ + return bridge ? + (spacing - BRIDGE_EXTRA_SPACING) : +#ifdef HAS_PERIMETER_LINE_OVERLAP + (spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1 - PI/4.0)); +#else + (spacing + height * (1 - PI/4.0)); +#endif } } diff --git a/xs/src/libslic3r/Flow.hpp b/xs/src/libslic3r/Flow.hpp index 2f041d03c..a3ff4b6b6 100644 --- a/xs/src/libslic3r/Flow.hpp +++ b/xs/src/libslic3r/Flow.hpp @@ -7,8 +7,14 @@ namespace Slic3r { +// Extra spacing of bridge threads, in mm. #define BRIDGE_EXTRA_SPACING 0.05 -#define OVERLAP_FACTOR 1.0 + +// Overlap factor of perimeter lines. Currently no overlap. +// #define HAS_OVERLAP +#ifdef HAS_PERIMETER_LINE_OVERLAP + #define PERIMETER_LINE_OVERLAP_FACTOR 1.0 +#endif enum FlowRole { frExternalPerimeter, @@ -22,9 +28,17 @@ enum FlowRole { class Flow { - public: - float width, height, nozzle_diameter; - bool bridge; +public: + // Non bridging flow: Maximum width of an extrusion with semicircles at the ends. + // Bridging flow: Bridge thread diameter. + float width; + // Non bridging flow: Layer height. + // Bridging flow: Bridge thread diameter = layer height. + float height; + // Nozzle diameter is + float nozzle_diameter; + // Is it a bridge? + bool bridge; Flow(float _w, float _h, float _nd, bool _bridge = false) : width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index ee76e75d9..e23e53465 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -315,8 +315,7 @@ GCode::change_layer(const Layer &layer) // avoid computing islands and overhangs if they're not needed if (this->config.avoid_crossing_perimeters) { - ExPolygons islands; - union_(layer.slices, &islands, true); + ExPolygons islands = union_ex(layer.slices, true); this->avoid_crossing_perimeters.init_layer_mp(islands); } diff --git a/xs/src/libslic3r/Layer.cpp b/xs/src/libslic3r/Layer.cpp index d3399d46e..c40c036e7 100644 --- a/xs/src/libslic3r/Layer.cpp +++ b/xs/src/libslic3r/Layer.cpp @@ -105,7 +105,7 @@ Layer::make_slices() FOREACH_LAYERREGION(this, layerm) { polygons_append(slices_p, to_polygons((*layerm)->slices)); } - union_(slices_p, &slices); + slices = union_ex(slices_p); } this->slices.expolygons.clear(); @@ -132,15 +132,11 @@ Layer::merge_slices() if (this->regions.size() == 1) { // Optimization, also more robust. Don't merge classified pieces of layerm->slices, // but use the non-split islands of a layer. For a single region print, these shall be equal. - this->regions.front()->slices.surfaces.clear(); - surfaces_append(this->regions.front()->slices.surfaces, this->slices.expolygons, stInternal); + this->regions.front()->slices.set(this->slices.expolygons, stInternal); } else { FOREACH_LAYERREGION(this, layerm) { - ExPolygons expp; // without safety offset, artifacts are generated (GH #2494) - union_(to_polygons(STDMOVE((*layerm)->slices.surfaces)), &expp, true); - (*layerm)->slices.surfaces.clear(); - surfaces_append((*layerm)->slices.surfaces, expp, stInternal); + (*layerm)->slices.set(union_ex(to_polygons(STDMOVE((*layerm)->slices.surfaces)), true), stInternal); } } } @@ -223,7 +219,7 @@ Layer::make_perimeters() } // merge the surfaces assigned to each group for (std::map::const_iterator it = slices.begin(); it != slices.end(); ++it) - surfaces_append(new_slices.surfaces, union_ex(it->second, true), it->second.front()); + new_slices.append(union_ex(it->second, true), it->second.front()); } // make perimeters @@ -236,8 +232,7 @@ Layer::make_perimeters() // Separate the fill surfaces. ExPolygons expp = intersection_ex(to_polygons(fill_surfaces), (*l)->slices); (*l)->fill_expolygons = expp; - (*l)->fill_surfaces.surfaces.clear(); - surfaces_append((*l)->fill_surfaces.surfaces, STDMOVE(expp), fill_surfaces.surfaces.front()); + (*l)->fill_surfaces.set(STDMOVE(expp), fill_surfaces.surfaces.front()); } } } @@ -318,9 +313,4 @@ SupportLayer::SupportLayer(size_t id, PrintObject *object, coordf_t height, { } -SupportLayer::~SupportLayer() -{ -} - - } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index e59f0c9e1..02cb19655 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -11,9 +11,6 @@ namespace Slic3r { -typedef std::pair t_layer_height_range; -typedef std::map t_layer_height_ranges; - class Layer; class PrintRegion; class PrintObject; @@ -155,7 +152,7 @@ public: protected: SupportLayer(size_t id, PrintObject *object, coordf_t height, coordf_t print_z, coordf_t slice_z); - virtual ~SupportLayer(); + virtual ~SupportLayer() {} }; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 7c84629e2..5536baf74 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -52,8 +52,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped() Polygons fill_boundaries = to_polygons(this->fill_expolygons); this->fill_surfaces.surfaces.clear(); for (Surfaces::const_iterator surface = this->slices.surfaces.begin(); surface != this->slices.surfaces.end(); ++ surface) - surfaces_append( - this->fill_surfaces.surfaces, + this->fill_surfaces.append( intersection_ex(to_polygons(surface->expolygon), fill_boundaries), surface->surface_type); } @@ -91,9 +90,9 @@ LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* g.process(); } -//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 3. -//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 1.5 -#define EXTERNAL_SURFACES_OFFSET_PARAMETERS CLIPPER_OFFSET_SCALE, ClipperLib::jtSquare, 0. +//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. +//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 +#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. void LayerRegion::process_external_surfaces(const Layer* lower_layer) @@ -194,7 +193,7 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) break; } // Grown by 3mm. - Polygons polys = offset(bridges[i].expolygon, float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS); + Polygons polys = offset(to_polygons(bridges[i].expolygon), float(margin), EXTERNAL_SURFACES_OFFSET_PARAMETERS); if (idx_island == -1) { printf("Bridge did not fall into the source region!\r\n"); } else { @@ -262,9 +261,7 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) BridgeDetector bd( initial, lower_layer->slices, - //FIXME parameters are not correct! - // flow(FlowRole role, bool bridge = false, double width = -1) const; - this->flow(frInfill, true, this->layer()->height).scaled_width() + this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); @@ -305,8 +302,10 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < top.size(); ++ j) { Surface &s2 = top[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) + if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); + s2.clear(); + } } if (s1.surface_type == stTop) // Trim the top surfaces by the bottom surfaces. This gives the priority to the bottom surfaces. @@ -329,8 +328,10 @@ LayerRegion::process_external_surfaces(const Layer* lower_layer) polygons_append(polys, STDMOVE(s1)); for (size_t j = i + 1; j < internal.size(); ++ j) { Surface &s2 = internal[j]; - if (! s2.empty() && surfaces_could_merge(s1, s2)) + if (! s2.empty() && surfaces_could_merge(s1, s2)) { polygons_append(polys, STDMOVE(s2)); + s2.clear(); + } } ExPolygons new_expolys = diff_ex(polys, new_polygons); polygons_append(new_polygons, to_polygons(new_expolys)); diff --git a/xs/src/libslic3r/Line.hpp b/xs/src/libslic3r/Line.hpp index 42e811449..6c40b062f 100644 --- a/xs/src/libslic3r/Line.hpp +++ b/xs/src/libslic3r/Line.hpp @@ -76,20 +76,20 @@ class Linef3 void scale(double factor); }; -} +} // namespace Slic3r // start Boost #include namespace boost { namespace polygon { template <> - struct geometry_concept { typedef segment_concept type; }; + struct geometry_concept { typedef segment_concept type; }; template <> - struct segment_traits { + struct segment_traits { typedef coord_t coordinate_type; - typedef Point point_type; + typedef Slic3r::Point point_type; - static inline point_type get(const Line& line, direction_1d dir) { + static inline point_type get(const Slic3r::Line& line, direction_1d dir) { return dir.to_int() ? line.b : line.a; } }; diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index b2a9d0b53..963bd7c85 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -6,6 +6,7 @@ #include "Layer.hpp" #include "Point.hpp" #include "TriangleMesh.hpp" +#include "Slicing.hpp" #include #include #include diff --git a/xs/src/libslic3r/MotionPlanner.cpp b/xs/src/libslic3r/MotionPlanner.cpp index 21afb37c4..9fc1695c0 100644 --- a/xs/src/libslic3r/MotionPlanner.cpp +++ b/xs/src/libslic3r/MotionPlanner.cpp @@ -142,7 +142,7 @@ MotionPlanner::shortest_path(const Point &from, const Point &to) { // grow our environment slightly in order for simplify_by_visibility() // to work best by considering moves on boundaries valid as well - ExPolygonCollection grown_env(offset_ex(env.env, +SCALED_EPSILON)); + ExPolygonCollection grown_env(offset_ex(env.env.expolygons, +SCALED_EPSILON)); if (island_idx == -1) { /* If 'from' or 'to' are not inside our env, they were connected using the @@ -155,12 +155,12 @@ MotionPlanner::shortest_path(const Point &from, const Point &to) if (!grown_env.contains(from)) { // delete second point while the line connecting first to third crosses the // boundaries as many times as the current first to second - while (polyline.points.size() > 2 && intersection((Lines)Line(from, polyline.points[2]), grown_env).size() == 1) { + while (polyline.points.size() > 2 && intersection_ln((Lines)Line(from, polyline.points[2]), grown_env).size() == 1) { polyline.points.erase(polyline.points.begin() + 1); } } if (!grown_env.contains(to)) { - while (polyline.points.size() > 2 && intersection((Lines)Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) { + while (polyline.points.size() > 2 && intersection_ln((Lines)Line(*(polyline.points.end() - 3), to), grown_env).size() == 1) { polyline.points.erase(polyline.points.end() - 2); } } @@ -294,7 +294,7 @@ MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const size_t result = from.nearest_waypoint_index(pp, to); // as we assume 'from' is outside env, any node will require at least one crossing - if (intersection((Lines)Line(from, pp[result]), this->island).size() > 1) { + if (intersection_ln((Lines)Line(from, pp[result]), this->island).size() > 1) { // discard result pp.erase(pp.begin() + result); } else { diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index acdff127b..730e5e501 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -54,8 +54,7 @@ PerimeterGenerator::process() for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); surface != this->slices->surfaces.end(); ++surface) { // detect how many perimeters must be generated for this island - signed short loop_number = this->config->perimeters + surface->extra_perimeters; - loop_number--; // 0-indexed loops + const int loop_number = this->config->perimeters + surface->extra_perimeters -1; // 0-indexed loops Polygons gaps; @@ -67,7 +66,7 @@ PerimeterGenerator::process() ThickPolylines thin_walls; // we loop one time more than needed in order to find gaps after the last perimeter was applied - for (signed short i = 0; i <= loop_number+1; ++i) { // outer loop is 0 + for (int i = 0; i <= loop_number+1; ++i) { // outer loop is 0 Polygons offsets; if (i == 0) { // the minimum thickness of a single loop is: @@ -170,16 +169,16 @@ PerimeterGenerator::process() } // nest loops: holes first - for (signed short d = 0; d <= loop_number; ++d) { + for (int d = 0; d <= loop_number; ++d) { PerimeterGeneratorLoops &holes_d = holes[d]; // loop through all holes having depth == d - for (signed short i = 0; i < holes_d.size(); ++i) { + for (int i = 0; i < (int)holes_d.size(); ++i) { const PerimeterGeneratorLoop &loop = holes_d[i]; // find the hole loop that contains this one, if any - for (signed short t = d+1; t <= loop_number; ++t) { - for (signed short j = 0; j < holes[t].size(); ++j) { + for (int t = d+1; t <= loop_number; ++t) { + for (int j = 0; j < (int)holes[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -191,8 +190,8 @@ PerimeterGenerator::process() } // if no hole contains this hole, find the contour loop that contains it - for (signed short t = loop_number; t >= 0; --t) { - for (signed short j = 0; j < contours[t].size(); ++j) { + for (int t = loop_number; t >= 0; --t) { + for (int j = 0; j < (int)contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -207,16 +206,16 @@ PerimeterGenerator::process() } // nest contour loops - for (signed short d = loop_number; d >= 1; --d) { + for (int d = loop_number; d >= 1; --d) { PerimeterGeneratorLoops &contours_d = contours[d]; // loop through all contours having depth == d - for (signed short i = 0; i < contours_d.size(); ++i) { + for (int i = 0; i < (int)contours_d.size(); ++i) { const PerimeterGeneratorLoop &loop = contours_d[i]; // find the contour loop that contains it - for (signed short t = d-1; t >= 0; --t) { - for (signed short j = 0; j < contours[t].size(); ++j) { + for (int t = d-1; t >= 0; --t) { + for (int j = 0; j < contours[t].size(); ++j) { PerimeterGeneratorLoop &candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { candidate_parent.children.push_back(loop); @@ -315,8 +314,7 @@ PerimeterGenerator::process() coord_t min_perimeter_infill_spacing = ispacing * (1 - INSET_OVERLAP_TOLERANCE); // append infill areas to fill_surfaces - surfaces_append( - this->fill_surfaces->surfaces, + this->fill_surfaces->append( offset2_ex( pp, -inset -min_perimeter_infill_spacing/2, @@ -354,36 +352,24 @@ PerimeterGenerator::_traverse_loops(const PerimeterGeneratorLoops &loops, if (this->config->overhangs && this->layer_id > 0 && !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) { // get non-overhang paths by intersecting this loop with the grown lower slices - { - Polylines polylines; - intersection((Polygons)loop->polygon, this->_lower_slices_p, &polylines); - - for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { - ExtrusionPath path(role); - path.polyline = *polyline; - path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm; - path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width; - path.height = this->layer_height; - paths.push_back(path); - } - } + extrusion_paths_append( + paths, + intersection_pl(loop->polygon, this->_lower_slices_p), + role, + is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm, + is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width, + this->layer_height); // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter - { - Polylines polylines; - diff((Polygons)loop->polygon, this->_lower_slices_p, &polylines); - - for (Polylines::const_iterator polyline = polylines.begin(); polyline != polylines.end(); ++polyline) { - ExtrusionPath path(erOverhangPerimeter); - path.polyline = *polyline; - path.mm3_per_mm = this->_mm3_per_mm_overhang; - path.width = this->overhang_flow.width; - path.height = this->overhang_flow.height; - paths.push_back(path); - } - } + extrusion_paths_append( + paths, + diff_pl(loop->polygon, this->_lower_slices_p), + erOverhangPerimeter, + this->_mm3_per_mm_overhang, + this->overhang_flow.width, + this->overhang_flow.height); // reapply the nearest point search for starting point // We allow polyline reversal because Clipper may have randomly @@ -459,7 +445,7 @@ PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRo ExtrusionPath path(role); ThickLines lines = p->thicklines(); - for (size_t i = 0; i < lines.size(); ++i) { + for (int i = 0; i < (int)lines.size(); ++i) { const ThickLine& line = lines[i]; const coordf_t line_len = line.length(); diff --git a/xs/src/libslic3r/Point.cpp b/xs/src/libslic3r/Point.cpp index 8b71746e0..2ee5dabdd 100644 --- a/xs/src/libslic3r/Point.cpp +++ b/xs/src/libslic3r/Point.cpp @@ -12,12 +12,6 @@ Point::Point(double x, double y) this->y = lrint(y); } -bool -Point::operator==(const Point& rhs) const -{ - return this->coincides_with(rhs); -} - std::string Point::wkt() const { diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 2cf046133..0405ec078 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -36,7 +36,7 @@ class Point static Point new_scale(coordf_t x, coordf_t y) { return Point(scale_(x), scale_(y)); }; - bool operator==(const Point& rhs) const; + bool operator==(const Point& rhs) const { return this->x == rhs.x && this->y == rhs.y; } std::string wkt() const; std::string dump_perl() const; void scale(double factor); @@ -70,6 +70,12 @@ inline Point operator+(const Point& point1, const Point& point2) { return Point( inline Point operator-(const Point& point1, const Point& point2) { return Point(point1.x - point2.x, point1.y - point2.y); } inline Point operator*(double scalar, const Point& point2) { return Point(scalar * point2.x, scalar * point2.y); } +struct PointHash { + size_t operator()(const Point &pt) const { + return std::hash()(pt.x) ^ std::hash()(pt.y); + } +}; + class Point3 : public Point { public: @@ -105,6 +111,9 @@ class Pointf inline Pointf operator+(const Pointf& point1, const Pointf& point2) { return Pointf(point1.x + point2.x, point1.y + point2.y); } inline Pointf operator-(const Pointf& point1, const Pointf& point2) { return Pointf(point1.x - point2.x, point1.y - point2.y); } inline Pointf operator*(double scalar, const Pointf& point2) { return Pointf(scalar * point2.x, scalar * point2.y); } +inline Pointf operator*(const Pointf& point2, double scalar) { return Pointf(scalar * point2.x, scalar * point2.y); } +inline coordf_t cross(const Pointf &v1, const Pointf &v2) { return v1.x * v2.y - v1.y * v2.x; } +inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v1.y + v2.x * v2.y; } class Pointf3 : public Pointf { @@ -122,7 +131,7 @@ class Pointf3 : public Pointf Vectorf3 vector_to(const Pointf3 &point) const; }; -} +} // namespace Slic3r // start Boost #include @@ -146,28 +155,28 @@ namespace boost { namespace polygon { #endif template <> - struct geometry_concept { typedef point_concept type; }; + struct geometry_concept { typedef point_concept type; }; template <> - struct point_traits { + struct point_traits { typedef coord_t coordinate_type; - static inline coordinate_type get(const Point& point, orientation_2d orient) { + static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { return (orient == HORIZONTAL) ? point.x : point.y; } }; template <> - struct point_mutable_traits { + struct point_mutable_traits { typedef coord_t coordinate_type; - static inline void set(Point& point, orientation_2d orient, coord_t value) { + static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { if (orient == HORIZONTAL) point.x = value; else point.y = value; } - static inline Point construct(coord_t x_value, coord_t y_value) { - Point retval; + static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { + Slic3r::Point retval; retval.x = x_value; retval.y = y_value; return retval; diff --git a/xs/src/libslic3r/Polygon.cpp b/xs/src/libslic3r/Polygon.cpp index 056c285ec..60ccd792d 100644 --- a/xs/src/libslic3r/Polygon.cpp +++ b/xs/src/libslic3r/Polygon.cpp @@ -112,9 +112,7 @@ double Polygon::area() const bool Polygon::is_counter_clockwise() const { - ClipperLib::Path p; - Slic3rMultiPoint_to_ClipperPath(*this, &p); - return ClipperLib::Orientation(p); + return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); } bool @@ -190,8 +188,7 @@ Polygon::simplify(double tolerance) const Polygons pp; pp.push_back(p); - simplify_polygons(pp, &pp); - return pp; + return simplify_polygons(pp); } void diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 966264fb2..b0a7ea40a 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -142,43 +142,43 @@ inline Polylines to_polylines(Polygons &&polys) #include namespace boost { namespace polygon { template <> - struct geometry_concept{ typedef polygon_concept type; }; + struct geometry_concept{ typedef polygon_concept type; }; template <> - struct polygon_traits { + struct polygon_traits { typedef coord_t coordinate_type; - typedef Points::const_iterator iterator_type; - typedef Point point_type; + typedef Slic3r::Points::const_iterator iterator_type; + typedef Slic3r::Point point_type; // Get the begin iterator - static inline iterator_type begin_points(const Polygon& t) { + static inline iterator_type begin_points(const Slic3r::Polygon& t) { return t.points.begin(); } // Get the end iterator - static inline iterator_type end_points(const Polygon& t) { + static inline iterator_type end_points(const Slic3r::Polygon& t) { return t.points.end(); } // Get the number of sides of the polygon - static inline std::size_t size(const Polygon& t) { + static inline std::size_t size(const Slic3r::Polygon& t) { return t.points.size(); } // Get the winding direction of the polygon - static inline winding_direction winding(const Polygon& t) { + static inline winding_direction winding(const Slic3r::Polygon& t) { return unknown_winding; } }; template <> - struct polygon_mutable_traits { + struct polygon_mutable_traits { // expects stl style iterators template - static inline Polygon& set_points(Polygon& polygon, iT input_begin, iT input_end) { + static inline Slic3r::Polygon& set_points(Slic3r::Polygon& polygon, iT input_begin, iT input_end) { polygon.points.clear(); while (input_begin != input_end) { - polygon.points.push_back(Point()); + polygon.points.push_back(Slic3r::Point()); boost::polygon::assign(polygon.points.back(), *input_begin); ++input_begin; } @@ -189,32 +189,32 @@ namespace boost { namespace polygon { }; template <> - struct geometry_concept { typedef polygon_set_concept type; }; + struct geometry_concept { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> - struct polygon_set_traits { + struct polygon_set_traits { typedef coord_t coordinate_type; - typedef Polygons::const_iterator iterator_type; - typedef Polygons operator_arg_type; + typedef Slic3r::Polygons::const_iterator iterator_type; + typedef Slic3r::Polygons operator_arg_type; - static inline iterator_type begin(const Polygons& polygon_set) { + static inline iterator_type begin(const Slic3r::Polygons& polygon_set) { return polygon_set.begin(); } - static inline iterator_type end(const Polygons& polygon_set) { + static inline iterator_type end(const Slic3r::Polygons& polygon_set) { return polygon_set.end(); } //don't worry about these, just return false from them - static inline bool clean(const Polygons& polygon_set) { return false; } - static inline bool sorted(const Polygons& polygon_set) { return false; } + static inline bool clean(const Slic3r::Polygons& polygon_set) { return false; } + static inline bool sorted(const Slic3r::Polygons& polygon_set) { return false; } }; template <> - struct polygon_set_mutable_traits { + struct polygon_set_mutable_traits { template - static inline void set(Polygons& polygons, input_iterator_type input_begin, input_iterator_type input_end) { + static inline void set(Slic3r::Polygons& polygons, input_iterator_type input_begin, input_iterator_type input_end) { polygons.assign(input_begin, input_end); } }; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 3ef964339..e04d4ac79 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -133,12 +133,6 @@ Print::clear_regions() this->delete_region(i); } -PrintRegion* -Print::get_region(size_t idx) -{ - return regions.at(idx); -} - PrintRegion* Print::add_region() { @@ -608,20 +602,15 @@ Print::validate() const object->model_object()->instances.front()->transform_polygon(&convex_hull); // grow convex hull with the clearance margin - { - Polygons grown_hull; - offset(convex_hull, &grown_hull, scale_(this->config.extruder_clearance_radius.value)/2, 1, jtRound, scale_(0.1)); - convex_hull = grown_hull.front(); - } + convex_hull = offset(convex_hull, scale_(this->config.extruder_clearance_radius.value)/2, jtRound, scale_(0.1)).front(); // now we check that no instance of convex_hull intersects any of the previously checked object instances for (Points::const_iterator copy = object->_shifted_copies.begin(); copy != object->_shifted_copies.end(); ++copy) { Polygon p = convex_hull; p.translate(*copy); - if (intersects(a, p)) + if (! intersection(a, p).empty()) return "Some objects are too close; your extruder will collide with them."; - - union_(a, p, &a); + polygons_append(a, p); } } } diff --git a/xs/src/libslic3r/Print.hpp b/xs/src/libslic3r/Print.hpp index 30b1f40cc..a0392290d 100644 --- a/xs/src/libslic3r/Print.hpp +++ b/xs/src/libslic3r/Print.hpp @@ -12,7 +12,7 @@ #include "Layer.hpp" #include "Model.hpp" #include "PlaceholderParser.hpp" - +#include "Slicing.hpp" namespace Slic3r { @@ -78,6 +78,10 @@ public: std::map< size_t,std::vector > region_volumes; PrintObjectConfig config; t_layer_height_ranges layer_height_ranges; + + // Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers. + // The pairs of are packed into a 1D array to simplify handling by the Perl XS. + std::vector layer_height_profile; // this is set to true when LayerRegion->slices is split in top/internal/bottom // so that next call to make_perimeters() performs a union() before computing loops @@ -136,13 +140,27 @@ public: bool invalidate_state_by_config_options(const std::vector &opt_keys); bool invalidate_step(PrintObjectStep step); bool invalidate_all_steps(); - + + // Process layer_height_ranges, the raft layers and first layer thickness into layer_height_profile. + // The layer_height_profile may be later modified interactively by the user to refine layers at sloping surfaces. + void update_layer_height_profile(); + + // Collect the slicing parameters, to be used by variable layer thickness algorithm, + // by the interactive layer height editor and by the printing process itself. + // The slicing parameters are dependent on various configuration values + // (layer height, first layer height, raft settings, print nozzle diameter etc). + SlicingParameters slicing_parameters() const; + + void _slice(); bool has_support_material() const; void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); - + void _make_perimeters(); + void _infill(); + void _generate_support_material(); + private: Print* _print; ModelObject* _model_object; @@ -152,6 +170,8 @@ private: // parameter PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox); ~PrintObject() {} + + std::vector _slice_region(size_t region_id, const std::vector &z, bool modifier); }; typedef std::vector PrintObjectPtrs; @@ -186,7 +206,8 @@ class Print bool reload_model_instances(); // methods for handling regions - PrintRegion* get_region(size_t idx); + PrintRegion* get_region(size_t idx) { return regions.at(idx); } + const PrintRegion* get_region(size_t idx) const { return regions.at(idx); } PrintRegion* add_region(); // methods for handling state diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 59dcea879..8556783fc 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,4 +1,5 @@ #include "PrintConfig.hpp" +#include namespace Slic3r { @@ -1120,6 +1121,17 @@ PrintConfigDef::PrintConfigDef() def->cli = "support-material!"; def->default_value = new ConfigOptionBool(false); + def = this->add("support_material_xy_spacing", coFloatOrPercent); + def->label = "XY separation between an object and its support"; + def->category = "Support material"; + def->tooltip = "XY separation between an object and its support. If expressed as percentage (for example 50%), it will be calculated over external perimeter width."; + def->sidetext = "mm or %"; + def->cli = "support-material-xy-spacing=s"; + def->ratio_over = "external_perimeter_extrusion_width"; + def->min = 0; + // Default is half the external perimeter width. + def->default_value = new ConfigOptionFloatOrPercent(50, true); + def = this->add("support_material_angle", coInt); def->label = "Pattern angle"; def->category = "Support material"; @@ -1177,6 +1189,13 @@ PrintConfigDef::PrintConfigDef() def->cli = "support-material-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); + def = this->add("support_material_interface_contact_loops", coBool); + def->label = "Interface circles"; + def->category = "Support material"; + def->tooltip = "Cover the top most interface layer with contact loops"; + def->cli = "support-material-interface-contact-loops!"; + def->default_value = new ConfigOptionBool(true); + def = this->add("support_material_interface_extruder", coInt); def->label = "Support material/raft interface extruder"; def->category = "Extruders"; @@ -1247,6 +1266,13 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(60); + def = this->add("support_material_synchronize_layers", coBool); + def->label = "Synchronize with object layers"; + def->category = "Support material"; + def->tooltip = "Synchronize support layers with the object print layers. This is useful with multi-material printers, where the extruder switch is expensive."; + def->cli = "support-material-synchronize-layers!"; + def->default_value = new ConfigOptionBool(false); + def = this->add("support_material_threshold", coInt); def->label = "Overhang threshold"; def->category = "Support material"; @@ -1290,9 +1316,11 @@ PrintConfigDef::PrintConfigDef() def->cli = "threads|j=i"; def->readonly = true; def->min = 1; - def->max = 16; - def->default_value = new ConfigOptionInt(2); - + { + unsigned int threads = boost::thread::hardware_concurrency(); + def->default_value = new ConfigOptionInt(threads > 0 ? threads : 2); + } + def = this->add("toolchange_gcode", coString); def->label = "Tool change G-code"; def->tooltip = "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]."; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index f4e6cb8c4..c8933bcd9 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -153,6 +153,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig ConfigOptionInt support_material_enforce_layers; ConfigOptionInt support_material_extruder; ConfigOptionFloatOrPercent support_material_extrusion_width; + ConfigOptionBool support_material_interface_contact_loops; ConfigOptionInt support_material_interface_extruder; ConfigOptionInt support_material_interface_layers; ConfigOptionFloat support_material_interface_spacing; @@ -160,8 +161,10 @@ class PrintObjectConfig : public virtual StaticPrintConfig ConfigOptionEnum support_material_pattern; ConfigOptionFloat support_material_spacing; ConfigOptionFloat support_material_speed; + ConfigOptionBool support_material_synchronize_layers; ConfigOptionInt support_material_threshold; ConfigOptionBool support_material_with_sheath; + ConfigOptionFloatOrPercent support_material_xy_spacing; ConfigOptionFloat xy_size_compensation; PrintObjectConfig(bool initialize = true) : StaticPrintConfig() { @@ -185,6 +188,7 @@ class PrintObjectConfig : public virtual StaticPrintConfig OPT_PTR(support_material_buildplate_only); OPT_PTR(support_material_contact_distance); OPT_PTR(support_material_enforce_layers); + OPT_PTR(support_material_interface_contact_loops); OPT_PTR(support_material_extruder); OPT_PTR(support_material_extrusion_width); OPT_PTR(support_material_interface_extruder); @@ -194,6 +198,8 @@ class PrintObjectConfig : public virtual StaticPrintConfig OPT_PTR(support_material_pattern); OPT_PTR(support_material_spacing); OPT_PTR(support_material_speed); + OPT_PTR(support_material_synchronize_layers); + OPT_PTR(support_material_xy_spacing); OPT_PTR(support_material_threshold); OPT_PTR(support_material_with_sheath); OPT_PTR(xy_size_compensation); diff --git a/xs/src/libslic3r/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 3808a545e..56a45613f 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -2,10 +2,28 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" -#include "SVG.hpp" +#include "SupportMaterial.hpp" + +#include #include +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING +#define SLIC3R_DEBUG +#endif + +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #undef NDEBUG + #define DEBUG + #define _DEBUG + #include "SVG.hpp" + #undef assert + #include +#endif + namespace Slic3r { PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) @@ -115,8 +133,12 @@ PrintObject::layer_count() const void PrintObject::clear_layers() { - for (int i = this->layers.size()-1; i >= 0; --i) - this->delete_layer(i); + for (size_t i = 0; i < this->layers.size(); ++ i) { + Layer *layer = this->layers[i]; + layer->upper_layer = layer->lower_layer = nullptr; + delete layer; + } + this->layers.clear(); } Layer* @@ -144,8 +166,12 @@ PrintObject::support_layer_count() const void PrintObject::clear_support_layers() { - for (int i = this->support_layers.size()-1; i >= 0; --i) - this->delete_support_layer(i); + for (size_t i = 0; i < this->support_layers.size(); ++ i) { + Layer *layer = this->support_layers[i]; + layer->upper_layer = layer->lower_layer = nullptr; + delete layer; + } + this->support_layers.clear(); } SupportLayer* @@ -197,12 +223,15 @@ PrintObject::invalidate_state_by_config_options(const std::vector_print->regions.size(); ++ idx_region) { #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (int idx_layer = 0; idx_layer < int(this->layer_count()); ++ idx_layer) { @@ -435,7 +465,7 @@ void PrintObject::detect_surfaces_type() { Polygons topbottom = to_polygons(top); polygons_append(topbottom, to_polygons(bottom)); - surfaces_append(layerm->slices.surfaces, + layerm->slices.append( #if 0 offset2_ex(diff(layerm_slices_surfaces, topbottom, true), -offset, offset), #else @@ -444,8 +474,8 @@ void PrintObject::detect_surfaces_type() stInternal); } - surfaces_append(layerm->slices.surfaces, STDMOVE(top)); - surfaces_append(layerm->slices.surfaces, STDMOVE(bottom)); + layerm->slices.append(STDMOVE(top)); + layerm->slices.append(STDMOVE(bottom)); // Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", // $layerm->layer->id, scalar(@bottom), scalar(@top), scalar(@internal) if $Slic3r::debug; @@ -469,6 +499,8 @@ void PrintObject::detect_surfaces_type() void PrintObject::process_external_surfaces() { + BOOST_LOG_TRIVIAL(info) << "Processing external surfaces..."; + FOREACH_REGION(this->_print, region) { size_t region_id = region - this->_print->regions.begin(); @@ -497,6 +529,8 @@ PrintObject::discover_vertical_shells() { PROFILE_FUNC(); + BOOST_LOG_TRIVIAL(info) << "Discovering vertical shells..."; + const SurfaceType surfaces_bottom[2] = { stBottom, stBottomBridge }; for (size_t idx_region = 0; idx_region < this->_print->regions.size(); ++ idx_region) { @@ -518,8 +552,19 @@ PrintObject::discover_vertical_shells() { PROFILE_BLOCK(discover_vertical_shells_region_layer); +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + static size_t debug_idx = 0; + ++ debug_idx; +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + Layer *layer = this->layers[idx_layer]; LayerRegion *layerm = layer->get_region(idx_region); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); + layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-initial"); +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + Flow solid_infill_flow = layerm->flow(frSolidInfill); coord_t infill_line_spacing = solid_infill_flow.scaled_spacing(); // Find a union of perimeters below / above this surface to guarantee a minimum shell thickness. @@ -532,16 +577,16 @@ PrintObject::discover_vertical_shells() if (1) { PROFILE_BLOCK(discover_vertical_shells_region_layer_collect); -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING +#if 0 +// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - static size_t idx = 0; - SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", idx), this->bounding_box()); + Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { if (n < 0 || n >= (int)this->layers.size()) continue; ExPolygons &expolys = this->layers[n]->perimeter_expolygons; for (size_t i = 0; i < expolys.size(); ++ i) { - SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", idx, n, i), get_extents(expolys[i])); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i])); svg.draw(expolys[i]); svg.draw_outline(expolys[i].contour, "black", scale_(0.05)); svg.draw_outline(expolys[i].holes, "blue", scale_(0.05)); @@ -552,7 +597,6 @@ PrintObject::discover_vertical_shells() svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05)); } } - ++ idx; } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Reset the top / bottom inflated regions caches of entries, which are out of the moving window. @@ -610,8 +654,7 @@ PrintObject::discover_vertical_shells() } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - static size_t idx = 0; - SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", idx ++), get_extents(shell)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); svg.draw(shell); svg.draw_outline(shell, "black", scale_(0.05)); svg.Close(); @@ -634,8 +677,7 @@ PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - static size_t idx = 0; - SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", idx ++), get_extents(shell)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-after-union-%d.svg", debug_idx), get_extents(shell)); svg.draw(shell_ex); svg.draw_outline(shell_ex, "black", "blue", scale_(0.05)); svg.Close(); @@ -644,8 +686,7 @@ PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - static size_t idx = 0; - SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", idx ++), get_extents(shell)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internal-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces.filter_by_type(stInternal), "yellow", 0.5); svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternal), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); @@ -653,8 +694,7 @@ PrintObject::discover_vertical_shells() svg.Close(); } { - static size_t idx = 0; - SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", idx ++), get_extents(shell)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5); svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); @@ -662,8 +702,7 @@ PrintObject::discover_vertical_shells() svg.Close(); } { - static size_t idx = 0; - SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", idx ++), get_extents(shell)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-internalvoid-wshell-%d.svg", debug_idx), get_extents(shell)); svg.draw(layerm->fill_surfaces.filter_by_type(stInternalVoid), "yellow", 0.5); svg.draw_outline(layerm->fill_surfaces.filter_by_type(stInternalVoid), "black", "blue", scale_(0.05)); svg.draw(shell_ex, "blue", 0.5); @@ -674,7 +713,7 @@ PrintObject::discover_vertical_shells() // Trim the shells region by the internal & internal void surfaces. const SurfaceType surfaceTypesInternal[] = { stInternal, stInternalVoid, stInternalSolid }; - const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 2)); + const Polygons polygonsInternal = to_polygons(layerm->fill_surfaces.filter_by_types(surfaceTypesInternal, 3)); shell = intersection(shell, polygonsInternal, true); polygons_append(shell, diff(polygonsInternal, holes)); if (shell.empty()) @@ -692,8 +731,7 @@ PrintObject::discover_vertical_shells() #if 1 // Intentionally inflate a bit more than how much the region has been shrunk, // so there will be some overlap between this solid infill and the other infill regions (mainly the sparse infill). - shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, - CLIPPER_OFFSET_SCALE, ClipperLib::jtSquare); + shell = offset2(shell, - 0.5f * min_perimeter_infill_spacing, 0.8f * min_perimeter_infill_spacing, ClipperLib::jtSquare); if (shell.empty()) continue; #else @@ -705,7 +743,7 @@ PrintObject::discover_vertical_shells() // get a triangle in $too_narrow; if we grow it below then the shell // would have a different shape from the external surface and we'd still // have the same angle, so the next shell would be grown even more and so on. - Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, CLIPPER_OFFSET_SCALE, ClipperLib::jtMiter, 5.), true); + Polygons too_narrow = diff(shell, offset2(shell, -margin, margin, ClipperLib::jtMiter, 5.), true); if (! too_narrow.empty()) { // grow the collapsing parts and add the extra area to the neighbor layer // as well as to our original surfaces so that we support this @@ -717,8 +755,7 @@ PrintObject::discover_vertical_shells() ExPolygons new_internal_solid = intersection_ex(polygonsInternal, shell, false); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - static size_t idx = 0; - SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", idx ++), get_extents(shell_before)); + Slic3r::SVG svg(debug_out_path("discover_vertical_shells-regularized-%d.svg", debug_idx), get_extents(shell_before)); // Source shell. svg.draw(union_ex(shell_before, true)); // Shell trimmed to the internal surfaces. @@ -743,26 +780,27 @@ PrintObject::discover_vertical_shells() #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { - static size_t idx = 0; - SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal-%d.svg", idx), get_extents(shell), new_internal, "black", "blue", scale_(0.05)); - SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_void-%d.svg", idx), get_extents(shell), new_internal_void, "black", "blue", scale_(0.05)); - SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid-%d.svg", idx), get_extents(shell), new_internal_solid, "black", "blue", scale_(0.05)); - ++ idx; + SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal-%d.svg", debug_idx), get_extents(shell), new_internal, "black", "blue", scale_(0.05)); + SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_void-%d.svg", debug_idx), get_extents(shell), new_internal_void, "black", "blue", scale_(0.05)); + SVG::export_expolygons(debug_out_path("discover_vertical_shells-new_internal_solid-%d.svg", debug_idx), get_extents(shell), new_internal_solid, "black", "blue", scale_(0.05)); } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ // Assign resulting internal surfaces to layer. const SurfaceType surfaceTypesKeep[] = { stTop, stBottom, stBottomBridge }; layerm->fill_surfaces.keep_types(surfaceTypesKeep, sizeof(surfaceTypesKeep)/sizeof(SurfaceType)); - layerm->fill_surfaces.append(stInternal , new_internal); - layerm->fill_surfaces.append(stInternalVoid , new_internal_void); - layerm->fill_surfaces.append(stInternalSolid, new_internal_solid); + layerm->fill_surfaces.append(new_internal, stInternal); + layerm->fill_surfaces.append(new_internal_void, stInternalVoid); + layerm->fill_surfaces.append(new_internal_solid, stInternalSolid); + } // for each layer #ifdef SLIC3R_DEBUG_SLICE_PROCESSING - layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells"); - layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells"); + for (size_t idx_layer = 0; idx_layer < this->layers.size(); ++idx_layer) { + LayerRegion *layerm = this->layers[idx_layer]->get_region(idx_region); + layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-final"); + layerm->export_region_fill_surfaces_to_svg_debug("4_discover_vertical_shells-final"); + } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - } // for each layer } // for each region // Write the profiler measurements to file @@ -775,6 +813,8 @@ PrintObject::discover_vertical_shells() void PrintObject::bridge_over_infill() { + BOOST_LOG_TRIVIAL(info) << "Bridge over infill..."; + FOREACH_REGION(this->_print, region) { size_t region_id = region - this->_print->regions.begin(); @@ -846,7 +886,7 @@ PrintObject::bridge_over_infill() #endif // compute the remaning internal solid surfaces as difference - ExPolygons not_to_bridge = diff_ex(internal_solid, to_bridge, true); + ExPolygons not_to_bridge = diff_ex(internal_solid, to_polygons(to_bridge), true); to_bridge = intersection_ex(to_polygons(to_bridge), internal_solid, true); // build the new collection of fill_surfaces @@ -902,4 +942,332 @@ PrintObject::bridge_over_infill() } } +SlicingParameters PrintObject::slicing_parameters() const +{ + return SlicingParameters::create_from_config( + this->print()->config, this->config, + unscale(this->size.z), this->print()->object_extruders()); } + +void PrintObject::update_layer_height_profile() +{ + if (this->layer_height_profile.empty()) { + if (0) +// if (this->layer_height_profile.empty()) + this->layer_height_profile = layer_height_profile_adaptive(this->slicing_parameters(), this->layer_height_ranges, + this->model_object()->volumes); + else + this->layer_height_profile = layer_height_profile_from_ranges(this->slicing_parameters(), this->layer_height_ranges); + } +} + +// 1) Decides Z positions of the layers, +// 2) Initializes layers and their regions +// 3) Slices the object meshes +// 4) Slices the modifier meshes and reclassifies the slices of the object meshes by the slices of the modifier meshes +// 5) Applies size compensation (offsets the slices in XY plane) +// 6) Replaces bad slices by the slices reconstructed from the upper/lower layer +// Resulting expolygons of layer regions are marked as Internal. +// +// this should be idempotent +void PrintObject::_slice() +{ + SlicingParameters slicing_params = this->slicing_parameters(); + + // 1) Initialize layers and their slice heights. + std::vector slice_zs; + { + this->clear_layers(); + // Object layers (pairs of bottom/top Z coordinate), without the raft. + this->update_layer_height_profile(); + std::vector object_layers = generate_object_layers(slicing_params, this->layer_height_profile); + // Reserve object layers for the raft. Last layer of the raft is the contact layer. + int id = int(slicing_params.raft_layers()); + slice_zs.reserve(object_layers.size()); + Layer *prev = nullptr; + for (size_t i_layer = 0; i_layer < object_layers.size(); i_layer += 2) { + coordf_t lo = object_layers[i_layer]; + coordf_t hi = object_layers[i_layer + 1]; + coordf_t slice_z = 0.5 * (lo + hi); + Layer *layer = this->add_layer(id ++, hi - lo, hi + slicing_params.object_print_z_min, slice_z); + slice_zs.push_back(float(slice_z)); + if (prev != nullptr) { + prev->upper_layer = layer; + layer->lower_layer = prev; + } + // Make sure all layers contain layer region objects for all regions. + for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) + layer->add_region(this->print()->regions[region_id]); + prev = layer; + } + } + + if (this->print()->regions.size() == 1) { + // Optimized for a single region. Slice the single non-modifier mesh. + std::vector expolygons_by_layer = this->_slice_region(0, slice_zs, false); + for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) + this->layers[layer_id]->regions.front()->slices.append(std::move(expolygons_by_layer[layer_id]), stInternal); + } else { + // Slice all non-modifier volumes. + for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { + std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, false); + for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) + this->layers[layer_id]->regions[region_id]->slices.append(std::move(expolygons_by_layer[layer_id]), stInternal); + } + // Slice all modifier volumes. + for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) { + std::vector expolygons_by_layer = this->_slice_region(region_id, slice_zs, true); + // loop through the other regions and 'steal' the slices belonging to this one + for (size_t other_region_id = 0; other_region_id < this->print()->regions.size(); ++ other_region_id) { + if (region_id == other_region_id) + continue; + for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) { + Layer *layer = layers[layer_id]; + LayerRegion *layerm = layer->regions[region_id]; + LayerRegion *other_layerm = layer->regions[other_region_id]; + if (layerm == nullptr || other_layerm == nullptr) + continue; + Polygons other_slices = to_polygons(other_layerm->slices); + ExPolygons my_parts = intersection_ex(other_slices, to_polygons(expolygons_by_layer[layer_id])); + if (my_parts.empty()) + continue; + // Remove such parts from original region. + other_layerm->slices.set(diff_ex(other_slices, to_polygons(my_parts)), stInternal); + // Append new parts to our region. + layerm->slices.append(std::move(my_parts), stInternal); + } + } + } + } + + // remove last layer(s) if empty + while (! this->layers.empty()) { + const Layer *layer = this->layers.back(); + for (size_t region_id = 0; region_id < this->print()->regions.size(); ++ region_id) + if (layer->regions[region_id] != nullptr && ! layer->regions[region_id]->slices.empty()) + // Non empty layer. + goto end; + this->delete_layer(int(this->layers.size()) - 1); + } +end: + ; + + for (size_t layer_id = 0; layer_id < layers.size(); ++ layer_id) { + Layer *layer = this->layers[layer_id]; + // apply size compensation + if (this->config.xy_size_compensation.value != 0.) { + float delta = float(scale_(this->config.xy_size_compensation.value)); + if (layer->regions.size() == 1) { + // single region + LayerRegion *layerm = layer->regions.front(); + layerm->slices.set(offset_ex(to_expolygons(std::move(layerm->slices.surfaces)), delta), stInternal); + } else { + if (delta < 0) { + // multiple regions, shrinking + // we apply the offset to the combined shape, then intersect it + // with the original slices for each region + Polygons region_slices; + for (size_t region_id = 0; region_id < layer->regions.size(); ++ region_id) + polygons_append(region_slices, layer->regions[region_id]->slices.surfaces); + Polygons slices = offset(union_(region_slices), delta); + for (size_t region_id = 0; region_id < layer->regions.size(); ++ region_id) { + LayerRegion *layerm = layer->regions[region_id]; + layerm->slices.set(std::move(intersection_ex(slices, to_polygons(std::move(layerm->slices.surfaces)))), stInternal); + } + } else { + // multiple regions, growing + // this is an ambiguous case, since it's not clear how to grow regions where they are going to overlap + // so we give priority to the first one and so on + Polygons processed; + for (size_t region_id = 0;; ++ region_id) { + LayerRegion *layerm = layer->regions[region_id]; + ExPolygons slices = offset_ex(to_expolygons(layerm->slices.surfaces), delta); + if (region_id > 0) + // Trim by the slices of already processed regions. + slices = diff_ex(to_polygons(std::move(slices)), processed); + if (region_id + 1 == layer->regions.size()) { + layerm->slices.set(std::move(slices), stInternal); + break; + } + polygons_append(processed, slices); + layerm->slices.set(std::move(slices), stInternal); + } + } + } + } + + // Merge all regions' slices to get islands, chain them by a shortest path. + layer->make_slices(); + } +} + +std::vector PrintObject::_slice_region(size_t region_id, const std::vector &z, bool modifier) +{ + std::vector layers; + assert(region_id < this->region_volumes.size()); + std::vector &volumes = this->region_volumes[region_id]; + if (! volumes.empty()) { + // Compose mesh. + //FIXME better to perform slicing over each volume separately and then to use a Boolean operation to merge them. + TriangleMesh mesh; + for (std::vector::const_iterator it_volume = volumes.begin(); it_volume != volumes.end(); ++ it_volume) { + ModelVolume *volume = this->model_object()->volumes[*it_volume]; + if (volume->modifier == modifier) + mesh.merge(volume->mesh); + } + if (mesh.stl.stats.number_of_facets > 0) { + // transform mesh + // we ignore the per-instance transformations currently and only + // consider the first one + this->model_object()->instances.front()->transform_mesh(&mesh, true); + // align mesh to Z = 0 (it should be already aligned actually) and apply XY shift + mesh.translate(- unscale(this->_copies_shift.x), - unscale(this->_copies_shift.y), -this->model_object()->bounding_box().min.z); + // perform actual slicing + TriangleMeshSlicer mslicer(&mesh); + mslicer.slice(z, &layers); + } + } + return layers; +} + +void +PrintObject::_make_perimeters() +{ + if (this->state.is_done(posPerimeters)) return; + this->state.set_started(posPerimeters); + + // merge slices if they were split into types + if (this->typed_slices) { + FOREACH_LAYER(this, layer_it) + (*layer_it)->merge_slices(); + this->typed_slices = false; + this->state.invalidate(posPrepareInfill); + } + + // compare each layer to the one below, and mark those slices needing + // one additional inner perimeter, like the top of domed objects- + + // this algorithm makes sure that at least one perimeter is overlapping + // but we don't generate any extra perimeter if fill density is zero, as they would be floating + // inside the object - infill_only_where_needed should be the method of choice for printing + // hollow objects + FOREACH_REGION(this->_print, region_it) { + size_t region_id = region_it - this->_print->regions.begin(); + const PrintRegion ®ion = **region_it; + + + if (!region.config.extra_perimeters + || region.config.perimeters == 0 + || region.config.fill_density == 0 + || this->layer_count() < 2) continue; + + for (int i = 0; i < int(this->layer_count()) - 1; ++i) { + LayerRegion &layerm = *this->get_layer(i)->get_region(region_id); + const LayerRegion &upper_layerm = *this->get_layer(i+1)->get_region(region_id); + const Polygons upper_layerm_polygons = upper_layerm.slices; + + // Filter upper layer polygons in intersection_ppl by their bounding boxes? + // my $upper_layerm_poly_bboxes= [ map $_->bounding_box, @{$upper_layerm_polygons} ]; + double total_loop_length = 0; + for (Polygons::const_iterator it = upper_layerm_polygons.begin(); it != upper_layerm_polygons.end(); ++it) + total_loop_length += it->length(); + + const coord_t perimeter_spacing = layerm.flow(frPerimeter).scaled_spacing(); + const Flow ext_perimeter_flow = layerm.flow(frExternalPerimeter); + const coord_t ext_perimeter_width = ext_perimeter_flow.scaled_width(); + const coord_t ext_perimeter_spacing = ext_perimeter_flow.scaled_spacing(); + + for (Surfaces::iterator slice = layerm.slices.surfaces.begin(); + slice != layerm.slices.surfaces.end(); ++slice) { + while (true) { + // compute the total thickness of perimeters + const coord_t perimeters_thickness = ext_perimeter_width/2 + ext_perimeter_spacing/2 + + (region.config.perimeters-1 + region.config.extra_perimeters) * perimeter_spacing; + + // define a critical area where we don't want the upper slice to fall into + // (it should either lay over our perimeters or outside this area) + const coord_t critical_area_depth = perimeter_spacing * 1.5; + const Polygons critical_area = diff( + offset(slice->expolygon, -perimeters_thickness), + offset(slice->expolygon, -(perimeters_thickness + critical_area_depth)) + ); + + // check whether a portion of the upper slices falls inside the critical area + const Polylines intersection = intersection_pl( + to_polylines(upper_layerm_polygons), + critical_area + ); + + // only add an additional loop if at least 30% of the slice loop would benefit from it + { + double total_intersection_length = 0; + for (Polylines::const_iterator it = intersection.begin(); it != intersection.end(); ++it) + total_intersection_length += it->length(); + if (total_intersection_length <= total_loop_length*0.3) break; + } + + /* + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "extra.svg", + no_arrows => 1, + expolygons => union_ex($critical_area), + polylines => [ map $_->split_at_first_point, map $_->p, @{$upper_layerm->slices} ], + ); + } + */ + + slice->extra_perimeters++; + } + + #ifdef DEBUG + if (slice->extra_perimeters > 0) + printf(" adding %d more perimeter(s) at layer %zu\n", slice->extra_perimeters, i); + #endif + } + } + } + + parallelize( + std::queue(std::deque(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue + boost::bind(&Slic3r::Layer::make_perimeters, _1), + this->_print->config.threads.value + ); + + /* + simplify slices (both layer and region slices), + we only need the max resolution for perimeters + ### This makes this method not-idempotent, so we keep it disabled for now. + ###$self->_simplify_slices(&Slic3r::SCALED_RESOLUTION); + */ + + this->state.set_done(posPerimeters); +} + +void +PrintObject::_infill() +{ + if (this->state.is_done(posInfill)) return; + this->state.set_started(posInfill); + + parallelize( + std::queue(std::deque(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue + boost::bind(&Slic3r::Layer::make_fills, _1), + this->_print->config.threads.value + ); + + /* we could free memory now, but this would make this step not idempotent + ### $_->fill_surfaces->clear for map @{$_->regions}, @{$object->layers}; + */ + + this->state.set_done(posInfill); +} + +void PrintObject::_generate_support_material() +{ + PrintObjectSupportMaterial support_material(this, PrintObject::slicing_parameters()); + support_material.generate(*this); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/SVG.hpp b/xs/src/libslic3r/SVG.hpp index 83bb586c9..3d459804c 100644 --- a/xs/src/libslic3r/SVG.hpp +++ b/xs/src/libslic3r/SVG.hpp @@ -12,7 +12,7 @@ namespace Slic3r { class SVG { - public: +public: bool arrows; std::string fill, stroke; Point origin; @@ -89,6 +89,10 @@ public: static void export_expolygons(const char *path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); static void export_expolygons(const std::string &path, const BoundingBox &bbox, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0) { export_expolygons(path.c_str(), bbox, expolygons, stroke_outer, stroke_holes, stroke_width); } + static void export_expolygons(const char *path, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0) + { export_expolygons(path, get_extents(expolygons), expolygons, stroke_outer, stroke_holes, stroke_width); } + static void export_expolygons(const std::string &path, const Slic3r::ExPolygons &expolygons, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0) + { export_expolygons(path.c_str(), get_extents(expolygons), expolygons, stroke_outer, stroke_holes, stroke_width); } }; } diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp new file mode 100644 index 000000000..8c3902ba3 --- /dev/null +++ b/xs/src/libslic3r/Slicing.cpp @@ -0,0 +1,631 @@ +#include "Slicing.hpp" +#include "SlicingAdaptive.hpp" +#include "PrintConfig.hpp" +#include "Model.hpp" + +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #undef NDEBUG + #define DEBUG + #define _DEBUG + #include "SVG.hpp" + #undef assert + #include +#endif + +namespace Slic3r +{ + +SlicingParameters SlicingParameters::create_from_config( + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::set &object_extruders) +{ + coordf_t first_layer_height = (object_config.first_layer_height.value <= 0) ? + object_config.layer_height.value : + object_config.first_layer_height.get_abs_value(object_config.layer_height.value); + coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_extruder.value - 1); + coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_material_interface_extruder.value - 1); + bool soluble_interface = object_config.support_material_contact_distance.value == 0.; + + SlicingParameters params; + params.layer_height = object_config.layer_height.value; + params.first_print_layer_height = first_layer_height; + params.first_object_layer_height = first_layer_height; + params.object_print_z_min = 0.; + params.object_print_z_max = object_height; + params.base_raft_layers = object_config.raft_layers.value; + params.soluble_interface = soluble_interface; + + if (! soluble_interface) { + params.gap_raft_object = object_config.support_material_contact_distance.value; + params.gap_object_support = object_config.support_material_contact_distance.value; + params.gap_support_object = object_config.support_material_contact_distance.value; + } + + if (params.base_raft_layers > 0) { + params.interface_raft_layers = (params.base_raft_layers + 1) / 2; + params.base_raft_layers -= params.interface_raft_layers; + // Use as large as possible layer height for the intermediate raft layers. + params.base_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_extruder_dmr); + params.interface_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr); + params.contact_raft_layer_height_bridging = false; + params.first_object_layer_bridging = false; + #if 1 + params.contact_raft_layer_height = std::max(params.layer_height, 0.75 * support_material_interface_extruder_dmr); + if (! soluble_interface) { + // Compute the average of all nozzles used for printing the object over a raft. + //FIXME It is expected, that the 1st layer of the object is printed with a bridging flow over a full raft. Shall it not be vice versa? + coordf_t average_object_extruder_dmr = 0.; + if (! object_extruders.empty()) { + for (std::set::const_iterator it_extruder = object_extruders.begin(); it_extruder != object_extruders.end(); ++ it_extruder) + average_object_extruder_dmr += print_config.nozzle_diameter.get_at(*it_extruder); + average_object_extruder_dmr /= coordf_t(object_extruders.size()); + } + params.first_object_layer_height = average_object_extruder_dmr; + params.first_object_layer_bridging = true; + } + #else + params.contact_raft_layer_height = soluble_interface ? support_material_interface_extruder_dmr : 0.75 * support_material_interface_extruder_dmr; + params.contact_raft_layer_height_bridging = ! soluble_interface; + ... + #endif + } + + if (params.has_raft()) { + // Raise first object layer Z by the thickness of the raft itself plus the extra distance required by the support material logic. + //FIXME The last raft layer is the contact layer, which shall be printed with a bridging flow for ease of separation. Currently it is not the case. + if (params.raft_layers() == 1) { + // There is only the contact layer. + params.contact_raft_layer_height = first_layer_height; + params.raft_contact_top_z = first_layer_height; + } else { + assert(params.base_raft_layers > 0); + assert(params.interface_raft_layers > 0); + // Number of the base raft layers is decreased by the first layer. + params.raft_base_top_z = first_layer_height + coordf_t(params.base_raft_layers - 1) * params.base_raft_layer_height; + // Number of the interface raft layers is decreased by the contact layer. + params.raft_interface_top_z = params.raft_base_top_z + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height; + params.raft_contact_top_z = params.raft_interface_top_z + params.contact_raft_layer_height; + } + coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object; + params.object_print_z_min = print_z; + params.object_print_z_max += print_z; + } + + params.min_layer_height = std::min(params.layer_height, first_layer_height); + params.max_layer_height = std::max(params.layer_height, first_layer_height); + + //FIXME add it to the print configuration + params.min_layer_height = 0.05; + + // Calculate the maximum layer height as 0.75 from the minimum nozzle diameter. + if (! object_extruders.empty()) { + coordf_t min_object_extruder_dmr = 1000000.; + for (std::set::const_iterator it_extruder = object_extruders.begin(); it_extruder != object_extruders.end(); ++ it_extruder) + min_object_extruder_dmr = std::min(min_object_extruder_dmr, print_config.nozzle_diameter.get_at(*it_extruder)); + // Allow excessive maximum layer height higher than 0.75 * min_object_extruder_dmr + params.max_layer_height = std::max(std::max(params.layer_height, first_layer_height), 0.75 * min_object_extruder_dmr); + } + + return params; +} + +// Convert layer_height_ranges to layer_height_profile. Both are referenced to z=0, meaning the raft layers are not accounted for +// in the height profile and the printed object may be lifted by the raft thickness at the time of the G-code generation. +std::vector layer_height_profile_from_ranges( + const SlicingParameters &slicing_params, + const t_layer_height_ranges &layer_height_ranges) +{ + // 1) If there are any height ranges, trim one by the other to make them non-overlapping. Insert the 1st layer if fixed. + std::vector> ranges_non_overlapping; + ranges_non_overlapping.reserve(layer_height_ranges.size() * 4); + if (slicing_params.first_object_layer_height_fixed()) + ranges_non_overlapping.push_back(std::pair( + t_layer_height_range(0., slicing_params.first_object_layer_height), + slicing_params.first_object_layer_height)); + // The height ranges are sorted lexicographically by low / high layer boundaries. + for (t_layer_height_ranges::const_iterator it_range = layer_height_ranges.begin(); it_range != layer_height_ranges.end(); ++ it_range) { + coordf_t lo = it_range->first.first; + coordf_t hi = std::min(it_range->first.second, slicing_params.object_print_z_height()); + coordf_t height = it_range->second; + if (! ranges_non_overlapping.empty()) + // Trim current low with the last high. + lo = std::max(lo, ranges_non_overlapping.back().first.second); + if (lo + EPSILON < hi) + // Ignore too narrow ranges. + ranges_non_overlapping.push_back(std::pair(t_layer_height_range(lo, hi), height)); + } + + // 2) Convert the trimmed ranges to a height profile, fill in the undefined intervals between z=0 and z=slicing_params.object_print_z_max() + // with slicing_params.layer_height + std::vector layer_height_profile; + for (std::vector>::const_iterator it_range = ranges_non_overlapping.begin(); it_range != ranges_non_overlapping.end(); ++ it_range) { + coordf_t lo = it_range->first.first; + coordf_t hi = it_range->first.second; + coordf_t height = it_range->second; + coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; + coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; + if (lo > last_z + EPSILON) { + // Insert a step of normal layer height. + layer_height_profile.push_back(last_z); + layer_height_profile.push_back(slicing_params.layer_height); + layer_height_profile.push_back(lo); + layer_height_profile.push_back(slicing_params.layer_height); + } + // Insert a step of the overriden layer height. + layer_height_profile.push_back(lo); + layer_height_profile.push_back(height); + layer_height_profile.push_back(hi); + layer_height_profile.push_back(height); + } + + coordf_t last_z = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 2]; + coordf_t last_height = layer_height_profile.empty() ? 0. : layer_height_profile[layer_height_profile.size() - 1]; + if (last_z < slicing_params.object_print_z_height()) { + // Insert a step of normal layer height up to the object top. + layer_height_profile.push_back(last_z); + layer_height_profile.push_back(slicing_params.layer_height); + layer_height_profile.push_back(slicing_params.object_print_z_height()); + layer_height_profile.push_back(slicing_params.layer_height); + } + + return layer_height_profile; +} + +// Based on the work of @platsch +// Fill layer_height_profile by heights ensuring a prescribed maximum cusp height. +std::vector layer_height_profile_adaptive( + const SlicingParameters &slicing_params, + const t_layer_height_ranges &layer_height_ranges, + const ModelVolumePtrs &volumes) +{ + // 1) Initialize the SlicingAdaptive class with the object meshes. + SlicingAdaptive as; + as.set_slicing_parameters(slicing_params); + for (ModelVolumePtrs::const_iterator it = volumes.begin(); it != volumes.end(); ++ it) + if (! (*it)->modifier) + as.add_mesh(&(*it)->mesh); + as.prepare(); + + // 2) Generate layers using the algorithm of @platsch + // loop until we have at least one layer and the max slice_z reaches the object height + //FIXME make it configurable + // Cusp value: A maximum allowed distance from a corner of a rectangular extrusion to a chrodal line, in mm. + const coordf_t cusp_value = 0.2; // $self->config->get_value('cusp_value'); + + std::vector layer_height_profile; + layer_height_profile.push_back(0.); + layer_height_profile.push_back(slicing_params.first_object_layer_height); + if (slicing_params.first_object_layer_height_fixed()) { + layer_height_profile.push_back(slicing_params.first_object_layer_height); + layer_height_profile.push_back(slicing_params.first_object_layer_height); + } + coordf_t slice_z = slicing_params.first_object_layer_height; + coordf_t height = slicing_params.first_object_layer_height; + coordf_t cusp_height = 0.; + int current_facet = 0; + while ((slice_z - height) <= slicing_params.object_print_z_height()) { + height = 999; + // Slic3r::debugf "\n Slice layer: %d\n", $id; + // determine next layer height + coordf_t cusp_height = as.cusp_height(slice_z, cusp_value, current_facet); + // check for horizontal features and object size + /* + if($self->config->get_value('match_horizontal_surfaces')) { + my $horizontal_dist = $adaptive_slicing[$region_id]->horizontal_facet_distance(scale $slice_z+$cusp_height, $min_height); + if(($horizontal_dist < $min_height) && ($horizontal_dist > 0)) { + Slic3r::debugf "Horizontal feature ahead, distance: %f\n", $horizontal_dist; + # can we shrink the current layer a bit? + if($cusp_height-($min_height-$horizontal_dist) > $min_height) { + # yes we can + $cusp_height = $cusp_height-($min_height-$horizontal_dist); + Slic3r::debugf "Shrink layer height to %f\n", $cusp_height; + }else{ + # no, current layer would become too thin + $cusp_height = $cusp_height+$horizontal_dist; + Slic3r::debugf "Widen layer height to %f\n", $cusp_height; + } + } + } + */ + height = std::min(cusp_height, height); + + // apply z-gradation + /* + my $gradation = $self->config->get_value('adaptive_slicing_z_gradation'); + if($gradation > 0) { + $height = $height - unscale((scale($height)) % (scale($gradation))); + } + */ + + // look for an applicable custom range + /* + if (my $range = first { $_->[0] <= $slice_z && $_->[1] > $slice_z } @{$self->layer_height_ranges}) { + $height = $range->[2]; + + # if user set custom height to zero we should just skip the range and resume slicing over it + if ($height == 0) { + $slice_z += $range->[1] - $range->[0]; + next; + } + } + */ + + layer_height_profile.push_back(slice_z); + layer_height_profile.push_back(height); + slice_z += height; + layer_height_profile.push_back(slice_z); + layer_height_profile.push_back(height); + } + + coordf_t last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]); + layer_height_profile.push_back(last); + layer_height_profile.push_back(slicing_params.first_object_layer_height); + layer_height_profile.push_back(slicing_params.object_print_z_height()); + layer_height_profile.push_back(slicing_params.first_object_layer_height); + + return layer_height_profile; +} + +template +static inline T clamp(const T low, const T high, const T value) +{ + return std::max(low, std::min(high, value)); +} + +template +static inline T lerp(const T a, const T b, const T t) +{ + assert(t >= T(-EPSILON) && t <= T(1.+EPSILON)); + return (1. - t) * a + t * b; +} + +void adjust_layer_height_profile( + const SlicingParameters &slicing_params, + std::vector &layer_height_profile, + coordf_t z, + coordf_t layer_thickness_delta, + coordf_t band_width, + LayerHeightEditActionType action) +{ + // Constrain the profile variability by the 1st layer height. + std::pair z_span_variable = + std::pair( + slicing_params.first_object_layer_height_fixed() ? slicing_params.first_object_layer_height : 0., + slicing_params.object_print_z_height()); + if (z < z_span_variable.first || z > z_span_variable.second) + return; + + assert(layer_height_profile.size() >= 2); + + // 1) Get the current layer thickness at z. + coordf_t current_layer_height = slicing_params.layer_height; + for (size_t i = 0; i < layer_height_profile.size(); i += 2) { + if (i + 2 == layer_height_profile.size()) { + current_layer_height = layer_height_profile[i + 1]; + break; + } else if (layer_height_profile[i + 2] > z) { + coordf_t z1 = layer_height_profile[i]; + coordf_t h1 = layer_height_profile[i + 1]; + coordf_t z2 = layer_height_profile[i + 2]; + coordf_t h2 = layer_height_profile[i + 3]; + current_layer_height = lerp(h1, h2, (z - z1) / (z2 - z1)); + break; + } + } + + // 2) Is it possible to apply the delta? + switch (action) { + case LAYER_HEIGHT_EDIT_ACTION_DECREASE: + layer_thickness_delta = - layer_thickness_delta; + // fallthrough + case LAYER_HEIGHT_EDIT_ACTION_INCREASE: + if (layer_thickness_delta > 0) { + if (current_layer_height >= slicing_params.max_layer_height - EPSILON) + return; + layer_thickness_delta = std::min(layer_thickness_delta, slicing_params.max_layer_height - current_layer_height); + } else { + if (current_layer_height <= slicing_params.min_layer_height + EPSILON) + return; + layer_thickness_delta = std::max(layer_thickness_delta, slicing_params.min_layer_height - current_layer_height); + } + break; + case LAYER_HEIGHT_EDIT_ACTION_REDUCE: + case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: + layer_thickness_delta = std::abs(layer_thickness_delta); + layer_thickness_delta = std::min(layer_thickness_delta, std::abs(slicing_params.layer_height - current_layer_height)); + if (layer_thickness_delta < EPSILON) + return; + break; + default: + assert(false); + break; + } + + // 3) Densify the profile inside z +- band_width/2, remove duplicate Zs from the height profile inside the band. + coordf_t lo = std::max(z_span_variable.first, z - 0.5 * band_width); + coordf_t hi = std::min(z_span_variable.second, z + 0.5 * band_width); + coordf_t z_step = 0.1; + size_t i = 0; + while (i < layer_height_profile.size() && layer_height_profile[i] < lo) + i += 2; + i -= 2; + + std::vector profile_new; + profile_new.reserve(layer_height_profile.size()); + assert(i >= 0 && i + 1 < layer_height_profile.size()); + profile_new.insert(profile_new.end(), layer_height_profile.begin(), layer_height_profile.begin() + i + 2); + coordf_t zz = lo; + size_t i_resampled_start = profile_new.size(); + while (zz < hi) { + size_t next = i + 2; + coordf_t z1 = layer_height_profile[i]; + coordf_t h1 = layer_height_profile[i + 1]; + coordf_t height = h1; + if (next < layer_height_profile.size()) { + coordf_t z2 = layer_height_profile[next]; + coordf_t h2 = layer_height_profile[next + 1]; + height = lerp(h1, h2, (zz - z1) / (z2 - z1)); + } + // Adjust height by layer_thickness_delta. + coordf_t weight = std::abs(zz - z) < 0.5 * band_width ? (0.5 + 0.5 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; + coordf_t height_new = height; + switch (action) { + case LAYER_HEIGHT_EDIT_ACTION_INCREASE: + case LAYER_HEIGHT_EDIT_ACTION_DECREASE: + height += weight * layer_thickness_delta; + break; + case LAYER_HEIGHT_EDIT_ACTION_REDUCE: + { + coordf_t delta = height - slicing_params.layer_height; + coordf_t step = weight * layer_thickness_delta; + step = (std::abs(delta) > step) ? + (delta > 0) ? -step : step : + -delta; + height += step; + break; + } + case LAYER_HEIGHT_EDIT_ACTION_SMOOTH: + { + // Don't modify the profile during resampling process, do it at the next step. + break; + } + default: + assert(false); + break; + } + // Avoid entering a too short segment. + if (profile_new[profile_new.size() - 2] + EPSILON < zz) { + profile_new.push_back(zz); + profile_new.push_back(clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, height)); + } + zz += z_step; + i = next; + while (i < layer_height_profile.size() && layer_height_profile[i] < zz) + i += 2; + i -= 2; + } + + i += 2; + assert(i > 0); + size_t i_resampled_end = profile_new.size(); + if (i < layer_height_profile.size()) { + assert(zz >= layer_height_profile[i - 2]); + assert(zz <= layer_height_profile[i]); +// profile_new.push_back(zz); +// profile_new.push_back(layer_height_profile[i + 1]); + profile_new.insert(profile_new.end(), layer_height_profile.begin() + i, layer_height_profile.end()); + } + layer_height_profile = std::move(profile_new); + + if (action == LAYER_HEIGHT_EDIT_ACTION_SMOOTH) { + size_t n_rounds = 6; + for (size_t i_round = 0; i_round < n_rounds; ++ i_round) { + profile_new = layer_height_profile; + for (size_t i = i_resampled_start; i < i_resampled_end; i += 2) { + coordf_t zz = profile_new[i]; + coordf_t t = std::abs(zz - z) < 0.5 * band_width ? (0.25 + 0.25 * cos(2. * M_PI * (zz - z) / band_width)) : 0.; + assert(t >= 0. && t <= 0.5000001); + if (i == 0) + layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i + 3]; + else if (i + 1 == profile_new.size()) + layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + t * profile_new[i - 1]; + else + layer_height_profile[i + 1] = (1. - t) * profile_new[i + 1] + 0.5 * t * (profile_new[i - 1] + profile_new[i + 3]); + } + } + } + + assert(layer_height_profile.size() > 2); + assert(layer_height_profile.size() % 2 == 0); + assert(layer_height_profile[0] == 0.); +#ifdef _DEBUG + for (size_t i = 2; i < layer_height_profile.size(); i += 2) + assert(layer_height_profile[i - 2] <= layer_height_profile[i]); + for (size_t i = 1; i < layer_height_profile.size(); i += 2) { + assert(layer_height_profile[i] > slicing_params.min_layer_height - EPSILON); + assert(layer_height_profile[i] < slicing_params.max_layer_height + EPSILON); + } +#endif /* _DEBUG */ +} + +// Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. +std::vector generate_object_layers( + const SlicingParameters &slicing_params, + const std::vector &layer_height_profile) +{ + coordf_t print_z = 0; + coordf_t height = 0; + + std::vector out; + + if (slicing_params.first_object_layer_height_fixed()) { + out.push_back(0); + print_z = slicing_params.first_object_layer_height; + out.push_back(print_z); + } + + size_t idx_layer_height_profile = 0; + // loop until we have at least one layer and the max slice_z reaches the object height + coordf_t slice_z = print_z + 0.5 * slicing_params.min_layer_height; + while (slice_z < slicing_params.object_print_z_height()) { + height = slicing_params.min_layer_height; + if (idx_layer_height_profile < layer_height_profile.size()) { + size_t next = idx_layer_height_profile + 2; + for (;;) { + if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next]) + break; + idx_layer_height_profile = next; + next += 2; + } + coordf_t z1 = layer_height_profile[idx_layer_height_profile]; + coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; + height = h1; + if (next < layer_height_profile.size()) { + coordf_t z2 = layer_height_profile[next]; + coordf_t h2 = layer_height_profile[next + 1]; + height = lerp(h1, h2, (slice_z - z1) / (z2 - z1)); + assert(height >= slicing_params.min_layer_height - EPSILON && height <= slicing_params.max_layer_height + EPSILON); + } + } + slice_z = print_z + 0.5 * height; + if (slice_z >= slicing_params.object_print_z_height()) + break; + assert(height > slicing_params.min_layer_height - EPSILON); + assert(height < slicing_params.max_layer_height + EPSILON); + out.push_back(print_z); + print_z += height; + slice_z = print_z + 0.5 * slicing_params.min_layer_height; + out.push_back(print_z); + } + + //FIXME Adjust the last layer to align with the top object layer exactly? + return out; +} + +int generate_layer_height_texture( + const SlicingParameters &slicing_params, + const std::vector &layers, + void *data, int rows, int cols, bool level_of_detail_2nd_level) +{ +// https://github.com/aschn/gnuplot-colorbrewer + std::vector palette_raw; + palette_raw.push_back(Point3(0x0B2, 0x018, 0x02B)); + palette_raw.push_back(Point3(0x0D6, 0x060, 0x04D)); + palette_raw.push_back(Point3(0x0F4, 0x0A5, 0x082)); + palette_raw.push_back(Point3(0x0FD, 0x0DB, 0x0C7)); + palette_raw.push_back(Point3(0x0D1, 0x0E5, 0x0F0)); + palette_raw.push_back(Point3(0x092, 0x0C5, 0x0DE)); + palette_raw.push_back(Point3(0x043, 0x093, 0x0C3)); + palette_raw.push_back(Point3(0x021, 0x066, 0x0AC)); + + // Clear the main texture and the 2nd LOD level. + memset(data, 0, rows * cols * 5); + // 2nd LOD level data start + unsigned char *data1 = reinterpret_cast(data) + rows * cols * 4; + int ncells = std::min((cols-1) * rows, int(ceil(16. * (slicing_params.object_print_z_height() / slicing_params.min_layer_height)))); + int ncells1 = ncells / 2; + int cols1 = cols / 2; + coordf_t z_to_cell = coordf_t(ncells-1) / slicing_params.object_print_z_height(); + coordf_t cell_to_z = slicing_params.object_print_z_height() / coordf_t(ncells-1); + coordf_t z_to_cell1 = coordf_t(ncells1-1) / slicing_params.object_print_z_height(); + coordf_t cell_to_z1 = slicing_params.object_print_z_height() / coordf_t(ncells1-1); + // for color scaling + coordf_t hscale = 2.f * std::max(slicing_params.max_layer_height - slicing_params.layer_height, slicing_params.layer_height - slicing_params.min_layer_height); + if (hscale == 0) + // All layers have the same height. Provide some height scale to avoid division by zero. + hscale = slicing_params.layer_height; + for (size_t idx_layer = 0; idx_layer < layers.size(); idx_layer += 2) { + coordf_t lo = layers[idx_layer]; + coordf_t hi = layers[idx_layer + 1]; + coordf_t mid = 0.5f * (lo + hi); + assert(mid <= slicing_params.object_print_z_height()); + coordf_t h = hi - lo; + hi = std::min(hi, slicing_params.object_print_z_height()); + int cell_first = clamp(0, ncells-1, int(ceil(lo * z_to_cell))); + int cell_last = clamp(0, ncells-1, int(floor(hi * z_to_cell))); + for (int cell = cell_first; cell <= cell_last; ++ cell) { + coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()) / hscale; + int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf))); + int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); + coordf_t t = idxf - coordf_t(idx1); + const Point3 &color1 = palette_raw[idx1]; + const Point3 &color2 = palette_raw[idx2]; + + coordf_t z = cell_to_z * coordf_t(cell); + assert(z >= lo && z <= hi); + // Intensity profile to visualize the layers. + coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h); + + // Color mapping from layer height to RGB. + Pointf3 color( + intensity * lerp(coordf_t(color1.x), coordf_t(color2.x), t), + intensity * lerp(coordf_t(color1.y), coordf_t(color2.y), t), + intensity * lerp(coordf_t(color1.z), coordf_t(color2.z), t)); + + int row = cell / (cols - 1); + int col = cell - row * (cols - 1); + assert(row >= 0 && row < rows); + assert(col >= 0 && col < cols); + unsigned char *ptr = (unsigned char*)data + (row * cols + col) * 4; + ptr[0] = clamp(0, 255, int(floor(color.x + 0.5))); + ptr[1] = clamp(0, 255, int(floor(color.y + 0.5))); + ptr[2] = clamp(0, 255, int(floor(color.z + 0.5))); + ptr[3] = 255; + if (col == 0 && row > 0) { + // Duplicate the first value in a row as a last value of the preceding row. + ptr[-4] = ptr[0]; + ptr[-3] = ptr[1]; + ptr[-2] = ptr[2]; + ptr[-1] = ptr[3]; + } + } + if (level_of_detail_2nd_level) { + cell_first = clamp(0, ncells1-1, int(ceil(lo * z_to_cell1))); + cell_last = clamp(0, ncells1-1, int(floor(hi * z_to_cell1))); + for (int cell = cell_first; cell <= cell_last; ++ cell) { + coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()) / hscale; + int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf))); + int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1); + coordf_t t = idxf - coordf_t(idx1); + const Point3 &color1 = palette_raw[idx1]; + const Point3 &color2 = palette_raw[idx2]; + + coordf_t z = cell_to_z1 * coordf_t(cell); + assert(z >= lo && z <= hi); + + // Color mapping from layer height to RGB. + Pointf3 color( + lerp(coordf_t(color1.x), coordf_t(color2.x), t), + lerp(coordf_t(color1.y), coordf_t(color2.y), t), + lerp(coordf_t(color1.z), coordf_t(color2.z), t)); + + int row = cell / (cols1 - 1); + int col = cell - row * (cols1 - 1); + assert(row >= 0 && row < rows/2); + assert(col >= 0 && col < cols/2); + unsigned char *ptr = data1 + (row * cols1 + col) * 4; + ptr[0] = clamp(0, 255, int(floor(color.x + 0.5))); + ptr[1] = clamp(0, 255, int(floor(color.y + 0.5))); + ptr[2] = clamp(0, 255, int(floor(color.z + 0.5))); + ptr[3] = 255; + if (col == 0 && row > 0) { + // Duplicate the first value in a row as a last value of the preceding row. + ptr[-4] = ptr[0]; + ptr[-3] = ptr[1]; + ptr[-2] = ptr[2]; + ptr[-1] = ptr[3]; + } + } + } + } + + // Returns number of cells of the 0th LOD level. + return ncells; +} + +}; // namespace Slic3r diff --git a/xs/src/libslic3r/Slicing.hpp b/xs/src/libslic3r/Slicing.hpp new file mode 100644 index 000000000..5105b9518 --- /dev/null +++ b/xs/src/libslic3r/Slicing.hpp @@ -0,0 +1,138 @@ +// Based on implementation by @platsch + +#ifndef slic3r_Slicing_hpp_ +#define slic3r_Slicing_hpp_ + +#include +#include + +#include "libslic3r.h" +namespace Slic3r +{ + +class PrintConfig; +class PrintObjectConfig; +class ModelVolume; +typedef std::vector ModelVolumePtrs; + +// Parameters to guide object slicing and support generation. +// The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow +// (using a normal flow over a soluble support, using a bridging flow over a non-soluble support). +struct SlicingParameters +{ + SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); } + + static SlicingParameters create_from_config( + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::set &object_extruders); + + // Has any raft layers? + bool has_raft() const { return raft_layers() > 0; } + size_t raft_layers() const { return base_raft_layers + interface_raft_layers; } + + // Is the 1st object layer height fixed, or could it be varied? + bool first_object_layer_height_fixed() const { return ! has_raft() || first_object_layer_bridging; } + + // Height of the object to be printed. This value does not contain the raft height. + coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; } + + // Number of raft layers. + size_t base_raft_layers; + // Number of interface layers including the contact layer. + size_t interface_raft_layers; + + // Layer heights of the raft (base, interface and a contact layer). + coordf_t base_raft_layer_height; + coordf_t interface_raft_layer_height; + coordf_t contact_raft_layer_height; + bool contact_raft_layer_height_bridging; + + // The regular layer height, applied for all but the first layer, if not overridden by layer ranges + // or by the variable layer thickness table. + coordf_t layer_height; + + // First layer height of the print, this may be used for the first layer of the raft + // or for the first layer of the print. + coordf_t first_print_layer_height; + + // Thickness of the first layer. This is either the first print layer thickness if printed without a raft, + // or a bridging flow thickness if printed over a non-soluble raft, + // or a normal layer height if printed over a soluble raft. + coordf_t first_object_layer_height; + + // If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow. + bool first_object_layer_bridging; + + // Soluble interface? (PLA soluble in water, HIPS soluble in lemonen) + // otherwise the interface must be broken off. + bool soluble_interface; + // Gap when placing object over raft. + coordf_t gap_raft_object; + // Gap when placing support over object. + coordf_t gap_object_support; + // Gap when placing object over support. + coordf_t gap_support_object; + + // Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm, + // or by an interactive layer height editor. + coordf_t min_layer_height; + coordf_t max_layer_height; + + // Bottom and top of the printed object. + // If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height. + // Otherwise object_print_z_min is equal to the raft height. + coordf_t raft_base_top_z; + coordf_t raft_interface_top_z; + coordf_t raft_contact_top_z; + // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer. + coordf_t object_print_z_min; + coordf_t object_print_z_max; +}; + +typedef std::pair t_layer_height_range; +typedef std::map t_layer_height_ranges; + +extern std::vector layer_height_profile_from_ranges( + const SlicingParameters &slicing_params, + const t_layer_height_ranges &layer_height_ranges); + +extern std::vector layer_height_profile_adaptive( + const SlicingParameters &slicing_params, + const t_layer_height_ranges &layer_height_ranges, + const ModelVolumePtrs &volumes); + + +enum LayerHeightEditActionType { + LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0, + LAYER_HEIGHT_EDIT_ACTION_DECREASE = 1, + LAYER_HEIGHT_EDIT_ACTION_REDUCE = 2, + LAYER_HEIGHT_EDIT_ACTION_SMOOTH = 3 +}; + +extern void adjust_layer_height_profile( + const SlicingParameters &slicing_params, + std::vector &layer_height_profile, + coordf_t z, + coordf_t layer_thickness_delta, + coordf_t band_width, + LayerHeightEditActionType action); + +// Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. +// The object layers are based at z=0, ignoring the raft layers. +extern std::vector generate_object_layers( + const SlicingParameters &slicing_params, + const std::vector &layer_height_profile); + +// Produce a 1D texture packed into a 2D texture describing in the RGBA format +// the planned object layers. +// Returns number of cells used by the texture of the 0th LOD level. +extern int generate_layer_height_texture( + const SlicingParameters &slicing_params, + const std::vector &layers, + void *data, int rows, int cols, bool level_of_detail_2nd_level); + +}; // namespace Slic3r + +#endif /* slic3r_Slicing_hpp_ */ diff --git a/xs/src/libslic3r/SlicingAdaptive.cpp b/xs/src/libslic3r/SlicingAdaptive.cpp new file mode 100644 index 000000000..ff0da7636 --- /dev/null +++ b/xs/src/libslic3r/SlicingAdaptive.cpp @@ -0,0 +1,140 @@ +#include "libslic3r.h" +#include "TriangleMesh.hpp" +#include "SlicingAdaptive.hpp" + +namespace Slic3r +{ + +void SlicingAdaptive::clear() +{ + m_meshes.clear(); + m_faces.clear(); + m_face_normal_z.clear(); +} + +std::pair face_z_span(const stl_facet *f) +{ + return std::pair( + std::min(std::min(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z), + std::max(std::max(f->vertex[0].z, f->vertex[1].z), f->vertex[2].z)); +} + +void SlicingAdaptive::prepare() +{ + // 1) Collect faces of all meshes. + int nfaces_total = 0; + for (std::vector::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) + nfaces_total += (*it_mesh)->stl.stats.number_of_facets; + m_faces.reserve(nfaces_total); + for (std::vector::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh) + for (int i = 0; i < (*it_mesh)->stl.stats.number_of_facets; ++ i) + m_faces.push_back((*it_mesh)->stl.facet_start + i); + + // 2) Sort faces lexicographically by their Z span. + std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) { + std::pair span1 = face_z_span(f1); + std::pair span2 = face_z_span(f2); + return span1 < span2; + }); + + // 3) Generate Z components of the facet normals. + m_face_normal_z.assign(m_faces.size(), 0.f); + for (size_t iface = 0; iface < m_faces.size(); ++ iface) + m_face_normal_z[iface] = m_faces[iface]->normal.z; +} + +float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet) +{ + float height = m_slicing_params.max_layer_height; + bool first_hit = false; + + // find all facets intersecting the slice-layer + int ordered_id = current_facet; + for (; ordered_id < int(m_faces.size()); ++ ordered_id) { + std::pair zspan = face_z_span(m_faces[ordered_id]); + // facet's minimum is higher than slice_z -> end loop + if (zspan.first >= z) + break; + // facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point + if (zspan.second > z) { + // first event? + if (! first_hit) { + first_hit = true; + current_facet = ordered_id; + } + // skip touching facets which could otherwise cause small cusp values + if (zspan.second <= z + EPSILON) + continue; + // compute cusp-height for this facet and store minimum of all heights + float normal_z = m_face_normal_z[ordered_id]; + height = std::min(height, (normal_z == 0.f) ? 9999.f : std::abs(cusp_value / normal_z)); + } + } + + // lower height limit due to printer capabilities + height = std::max(height, float(m_slicing_params.min_layer_height)); + + // check for sloped facets inside the determined layer and correct height if necessary + if (height > m_slicing_params.min_layer_height) { + for (; ordered_id < int(m_faces.size()); ++ ordered_id) { + std::pair zspan = face_z_span(m_faces[ordered_id]); + // facet's minimum is higher than slice_z + height -> end loop + if (zspan.first >= z + height) + break; + + // skip touching facets which could otherwise cause small cusp values + if (zspan.second <= z + EPSILON) + continue; + + // Compute cusp-height for this facet and check against height. + float normal_z = m_face_normal_z[ordered_id]; + float cusp = (normal_z == 0) ? 9999 : abs(cusp_value / normal_z); + + float z_diff = zspan.first - z; + + // handle horizontal facets + if (m_face_normal_z[ordered_id] > 0.999) { + // Slic3r::debugf "cusp computation, height is reduced from %f", $height; + height = z_diff; + // Slic3r::debugf "to %f due to near horizontal facet\n", $height; + } else if (cusp > z_diff) { + if (cusp < height) { + // Slic3r::debugf "cusp computation, height is reduced from %f", $height; + height = cusp; + // Slic3r::debugf "to %f due to new cusp height\n", $height; + } + } else { + // Slic3r::debugf "cusp computation, height is reduced from %f", $height; + height = z_diff; + // Slic3r::debugf "to z-diff: %f\n", $height; + } + } + // lower height limit due to printer capabilities again + height = std::max(height, float(m_slicing_params.min_layer_height)); + } + +// Slic3r::debugf "cusp computation, layer-bottom at z:%f, cusp_value:%f, resulting layer height:%f\n", unscale $z, $cusp_value, $height; + return height; +} + +// Returns the distance to the next horizontal facet in Z-dir +// to consider horizontal object features in slice thickness +float SlicingAdaptive::horizontal_facet_distance(float z) +{ + for (size_t i = 0; i < m_faces.size(); ++ i) { + std::pair zspan = face_z_span(m_faces[i]); + // facet's minimum is higher than max forward distance -> end loop + if (zspan.first > z + m_slicing_params.max_layer_height) + break; + // min_z == max_z -> horizontal facet + if (zspan.first > z && zspan.first == zspan.second) + return zspan.first - z; + } + + // objects maximum? + return (z + m_slicing_params.max_layer_height > m_slicing_params.object_print_z_height()) ? + std::max(m_slicing_params.object_print_z_height() - z, 0.f) : + m_slicing_params.max_layer_height; +} + +}; // namespace Slic3r diff --git a/xs/src/libslic3r/SlicingAdaptive.hpp b/xs/src/libslic3r/SlicingAdaptive.hpp new file mode 100644 index 000000000..bfd081d81 --- /dev/null +++ b/xs/src/libslic3r/SlicingAdaptive.hpp @@ -0,0 +1,36 @@ +// Based on implementation by @platsch + +#ifndef slic3r_SlicingAdaptive_hpp_ +#define slic3r_SlicingAdaptive_hpp_ + +#include "Slicing.hpp" +#include "admesh/stl.h" + +namespace Slic3r +{ + +class TriangleMesh; + +class SlicingAdaptive +{ +public: + void clear(); + void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; } + void add_mesh(const TriangleMesh *mesh) { m_meshes.push_back(mesh); } + void prepare(); + float cusp_height(float z, float cusp_value, int ¤t_facet); + float horizontal_facet_distance(float z); + +protected: + SlicingParameters m_slicing_params; + + std::vector m_meshes; + // Collected faces of all meshes, sorted by raising Z of the bottom most face. + std::vector m_faces; + // Z component of face normals, normalized. + std::vector m_face_normal_z; +}; + +}; // namespace Slic3r + +#endif /* slic3r_SlicingAdaptive_hpp_ */ diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 701e11799..6f9810a33 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -1,17 +1,26 @@ #include "ClipperUtils.hpp" #include "ExtrusionEntityCollection.hpp" #include "PerimeterGenerator.hpp" -#include "Print.hpp" #include "Layer.hpp" +#include "Print.hpp" #include "SupportMaterial.hpp" #include "Fill/FillBase.hpp" -#include "SVG.hpp" +#include "EdgeGrid.hpp" #include -#include #include +#include +#include -#define SLIC3R_DEBUG +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #undef NDEBUG + #include "SVG.hpp" +#endif + +#include namespace Slic3r { @@ -22,149 +31,125 @@ namespace Slic3r { #define PILLAR_SIZE (2.5) #define PILLAR_SPACING 10 -PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object) : +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3. +//#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 +#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. + +#ifdef SLIC3R_DEBUG +const char* support_surface_type_to_color_name(const PrintObjectSupportMaterial::SupporLayerType surface_type) +{ + switch (surface_type) { + case PrintObjectSupportMaterial::sltTopContact: return "rgb(255,0,0)"; // "red"; + case PrintObjectSupportMaterial::sltTopInterface: return "rgb(0,255,0)"; // "green"; + case PrintObjectSupportMaterial::sltBase: return "rgb(0,0,255)"; // "blue"; + case PrintObjectSupportMaterial::sltBottomInterface:return "rgb(255,255,128)"; // yellow + case PrintObjectSupportMaterial::sltBottomContact: return "rgb(255,0,255)"; // magenta + case PrintObjectSupportMaterial::sltRaftInterface: return "rgb(0,255,255)"; + case PrintObjectSupportMaterial::sltRaftBase: return "rgb(128,128,128)"; + case PrintObjectSupportMaterial::sltUnknown: return "rgb(128,0,0)"; // maroon + default: return "rgb(64,64,64)"; + }; +} + +Point export_support_surface_type_legend_to_svg_box_size() +{ + return Point(scale_(1.+10.*8.), scale_(3.)); +} + +void export_support_surface_type_legend_to_svg(SVG &svg, const Point &pos) +{ + // 1st row + coord_t pos_x0 = pos.x + scale_(1.); + coord_t pos_x = pos_x0; + coord_t pos_y = pos.y + scale_(1.5); + coord_t step_x = scale_(10.); + svg.draw_legend(Point(pos_x, pos_y), "top contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopContact)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "top iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltTopInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBase)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "bottom iface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "bottom contact" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltBottomContact)); + // 2nd row + pos_x = pos_x0; + pos_y = pos.y+scale_(2.8); + svg.draw_legend(Point(pos_x, pos_y), "raft interface" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftInterface)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "raft base" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltRaftBase)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "unknown" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltUnknown)); + pos_x += step_x; + svg.draw_legend(Point(pos_x, pos_y), "intermediate" , support_surface_type_to_color_name(PrintObjectSupportMaterial::sltIntermediate)); +} + +void export_print_z_polygons_to_svg(const char *path, PrintObjectSupportMaterial::MyLayer ** const layers, size_t n_layers) +{ + BoundingBox bbox; + for (int i = 0; i < n_layers; ++ i) + bbox.merge(get_extents(layers[i]->polygons)); + Point legend_size = export_support_surface_type_legend_to_svg_box_size(); + Point legend_pos(bbox.min.x, bbox.max.y); + bbox.merge(Point(std::max(bbox.min.x + legend_size.x, bbox.max.x), bbox.max.y + legend_size.y)); + SVG svg(path, bbox); + const float transparency = 0.5f; + for (int i = 0; i < n_layers; ++ i) + svg.draw(union_ex(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type), transparency); + for (int i = 0; i < n_layers; ++ i) + svg.draw(to_lines(layers[i]->polygons), support_surface_type_to_color_name(layers[i]->layer_type)); + export_support_surface_type_legend_to_svg(svg, legend_pos); + svg.Close(); +} +#endif /* SLIC3R_DEBUG */ + +PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : m_object (object), m_print_config (&object->print()->config), m_object_config (&object->config), + m_slicing_params (slicing_params), m_first_layer_flow (Flow::new_from_config_width( frSupportMaterial, + // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width, - object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1), - object->config.get_abs_value("first_layer_height"), - false - )), + float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), + float(slicing_params.first_print_layer_height), + false)), m_support_material_flow (Flow::new_from_config_width( frSupportMaterial, + // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config.support_material_extrusion_width.value > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width, - object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1), - object->config.layer_height.value, + float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), + float(slicing_params.layer_height), false)), m_support_material_interface_flow(Flow::new_from_config_width( frSupportMaterialInterface, - (object->config.support_material_extrusion_width.value > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width, - object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1), - object->config.layer_height.value, + // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. + (object->config.support_material_extrusion_width > 0) ? object->config.support_material_extrusion_width : object->config.extrusion_width, + float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_interface_extruder-1)), + float(slicing_params.layer_height), false)), - m_soluble_interface (object->config.support_material_contact_distance.value == 0), - m_support_material_raft_base_flow(0, 0, 0, false), - m_support_material_raft_interface_flow(0, 0, 0, false), - m_support_material_raft_contact_flow(0, 0, 0, false), - - m_has_raft (object->config.raft_layers.value > 0), - m_num_base_raft_layers (0), - m_num_interface_raft_layers (0), - m_num_contact_raft_layers (0), - - // If set, the raft contact layer is laid with round strings, which are easily detachable - // from both the below and above layes. - // Otherwise a normal flow is used and the strings are squashed against the layer below, - // creating a firm bond with the layer below and making the interface top surface flat. -#if 1 - // This is the standard Slic3r behavior. - m_raft_contact_layer_bridging(false), - m_object_1st_layer_bridging (true), -#else - // This is more akin to what Simplify3D or Zortrax do. - m_raft_contact_layer_bridging(true), - m_object_1st_layer_bridging (false), -#endif - - m_raft_height (0.), - m_raft_base_height (0.), - m_raft_interface_height (0.), - m_raft_contact_height (0.), - // 50 mirons layer m_support_layer_height_min (0.05), - m_support_layer_height_max (0.), - m_support_interface_layer_height_max(0.), - - m_gap_extra_above (0.2), - m_gap_extra_below (0.2), - m_gap_xy (0.2), - - // If enabled, the support layers will be synchronized with object layers. - // This does not prevent the support layers to be combined. - m_synchronize_support_layers_with_object(false), - // If disabled and m_synchronize_support_layers_with_object, - // the support layers will be synchronized with the object layers exactly, no layer will be combined. - m_combine_support_layers (true) + m_support_layer_height_max (0.) { - // Based on the raft style and size, initialize the raft layers and the 1st object layer attributes. - - size_t num_raft_layers = m_object_config->raft_layers.value; - - //FIXME better to draw thin strings, which are easier to remove from the object. - if (m_has_raft) - { - if (m_raft_contact_layer_bridging) - m_support_material_raft_contact_flow = Flow::new_from_spacing( - m_support_material_raft_interface_flow.spacing(), - m_support_material_raft_interface_flow.nozzle_diameter, - m_support_material_raft_interface_flow.height, - true); - - if (m_raft_contact_layer_bridging && num_raft_layers == 1) - // The bridging contact layer will not bond to the bed well on its own. - // Ensure there is at least the 1st layer printed with a firm squash. - ++ num_raft_layers; - - // Split the raft layers into a single contact layer - // and an equal number of interface and base layers, - // with m_num_interface_raft_layers >= m_num_base_raft_layers. - m_num_contact_raft_layers = 1; - m_num_interface_raft_layers = num_raft_layers / 2; - m_num_base_raft_layers = num_raft_layers - m_num_contact_raft_layers - m_num_interface_raft_layers; - assert(m_num_interface_raft_layers >= m_num_base_raft_layers); - assert(m_num_contact_raft_layers + m_num_base_raft_layers + m_num_interface_raft_layers == num_raft_layers); - - m_raft_contact_height = m_num_contact_raft_layers * m_support_material_raft_contact_flow.height; - if (m_num_base_raft_layers > 0) { - m_raft_base_height = first_layer_height() + (m_num_base_raft_layers - 1) * m_support_material_raft_base_flow.height; - m_raft_interface_height = m_num_interface_raft_layers * m_support_material_raft_interface_flow.height; - } else if (m_num_interface_raft_layers > 0) { - m_raft_base_height = 0; - m_raft_interface_height = first_layer_height() + (m_num_interface_raft_layers - 1) * m_support_material_raft_interface_flow.height; - } else { - m_raft_base_height = 0; - m_raft_interface_height = 0; - } - m_raft_height = m_raft_base_height + m_raft_interface_height + m_raft_contact_height; - - // Find the layer height of the 1st object layer. - if (m_object_1st_layer_bridging) { - // Use an average nozzle diameter. - std::set extruders = m_object->print()->object_extruders(); - coordf_t nozzle_dmr = 0; - for (std::set::const_iterator it = extruders.begin(); it != extruders.end(); ++ it) { - nozzle_dmr += m_object->print()->config.nozzle_diameter.get_at(*it); - } - nozzle_dmr /= extruders.size(); - m_object_1st_layer_height = nozzle_dmr; - } else { - m_object_1st_layer_height = m_object->config.layer_height.value; - for (t_layer_height_ranges::const_iterator it = m_object->layer_height_ranges.begin(); it != m_object->layer_height_ranges.end(); ++ it) { - if (m_object_1st_layer_height >= it->first.first && m_object_1st_layer_height <= it->first.second) { - m_object_1st_layer_height = it->second; - break; - } - } - } - - m_object_1st_layer_gap = m_soluble_interface ? 0. : m_object_config->support_material_contact_distance.value; - m_object_1st_layer_print_z = m_raft_height + m_object_1st_layer_gap + m_object_1st_layer_height; + if (m_object_config->support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + m_support_material_interface_flow = m_support_material_flow; } - else - { - // No raft. - m_raft_contact_layer_bridging = false; - m_object_1st_layer_bridging = false; - m_object_1st_layer_height = m_first_layer_flow.height; - m_object_1st_layer_gap = 0; - m_object_1st_layer_print_z = m_object_1st_layer_height; + + // Evaluate the XY gap between the object outer perimeters and the support structures. + coordf_t external_perimeter_width = 0.; + for (std::map>::const_iterator it_region = object->region_volumes.begin(); it_region != object->region_volumes.end(); ++ it_region) { + const PrintRegionConfig &config = object->print()->get_region(it_region->first)->config; + coordf_t width = config.external_perimeter_extrusion_width.get_abs_value(slicing_params.layer_height); + if (width <= 0.) + width = m_print_config->nozzle_diameter.get_at(config.perimeter_extruder-1); + external_perimeter_width = std::max(external_perimeter_width, width); } + m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width); } // Using the std::deque as an allocator. @@ -192,19 +177,22 @@ struct MyLayersPtrCompare void PrintObjectSupportMaterial::generate(PrintObject &object) { + BOOST_LOG_TRIVIAL(info) << "Support generator - Start"; + coordf_t max_object_layer_height = 0.; for (size_t i = 0; i < object.layer_count(); ++ i) - max_object_layer_height = std::max(max_object_layer_height, object.get_layer(i)->height); + max_object_layer_height = std::max(max_object_layer_height, object.layers[i]->height); if (m_support_layer_height_max == 0) m_support_layer_height_max = std::max(max_object_layer_height, 0.75 * m_support_material_flow.nozzle_diameter); - if (m_support_interface_layer_height_max == 0) - m_support_interface_layer_height_max = std::max(max_object_layer_height, 0.75 * m_support_material_interface_flow.nozzle_diameter); +// m_support_interface_layer_height_max = std::max(max_object_layer_height, 0.75 * m_support_material_interface_flow.nozzle_diameter); // Layer instances will be allocated by std::deque and they will be kept until the end of this function call. // The layers will be referenced by various LayersPtr (of type std::vector) MyLayerStorage layer_storage; + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating top contacts"; + // Determine the top contact surfaces of the support, defined as: // contact = overhangs - clearance + margin // This method is responsible for identifying what contact surfaces @@ -219,57 +207,68 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) #ifdef SLIC3R_DEBUG static int iRun = 0; iRun ++; - for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) { - const MyLayer &layer = *(*it); - ::Slic3r::SVG svg(debug_out_path("support-top-contacts-%d-%lf.svg", iRun, layer.print_z), get_extents(layer.polygons)); - Slic3r::ExPolygons expolys = union_ex(layer.polygons, false); - svg.draw(expolys); - } + for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) + Slic3r::SVG::export_expolygons( + debug_out_path("support-top-contacts-%d-%lf.svg", iRun, (*it)->print_z), + union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating bottom contacts"; + // Determine the bottom contact surfaces of the supports over the top surfaces of the object. // Depending on whether the support is soluble or not, the contact layer thickness is decided. - MyLayersPtr bottom_contacts = this->bottom_contact_layers(object, top_contacts, layer_storage); + // layer_support_areas contains the per object layer support areas. These per object layer support areas + // may get merged and trimmed by this->generate_base_layers() if the support layers are not synchronized with object layers. + std::vector layer_support_areas; + MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( + object, top_contacts, layer_storage, + layer_support_areas); #ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = bottom_contacts.begin(); it != bottom_contacts.end(); ++ it) { - const MyLayer &layer = *(*it); - ::Slic3r::SVG svg(debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer.print_z), get_extents(layer.polygons)); - Slic3r::ExPolygons expolys = union_ex(layer.polygons, false); - svg.draw(expolys); - } + for (size_t layer_id = 0; layer_id < object.layers.size(); ++ layer_id) + Slic3r::SVG::export_expolygons( + debug_out_path("support-areas-%d-%lf.svg", iRun, object.layers[layer_id]->print_z), + union_ex(layer_support_areas[layer_id], false)); #endif /* SLIC3R_DEBUG */ + BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; + // Because the top and bottom contacts are thick slabs, they may overlap causing over extrusion // and unwanted strong bonds to the object. - // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding. + // Rather trim the top contacts by their overlapping bottom contacts to leave a gap instead of over extruding + // top contacts over the bottom contacts. this->trim_top_contacts_by_bottom_contacts(object, bottom_contacts, top_contacts); - // Generate empty intermediate layers between the top / bottom support contact layers, + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating intermediate layers - indices"; + + // Allocate empty layers between the top / bottom support contact layers + // as placeholders for the base and intermediate support layers. // The layers may or may not be synchronized with the object layers, depending on the configuration. // For example, a single nozzle multi material printing will need to generate a waste tower, which in turn - // wastes less material, if there are as little layers as possible, therefore minimizing the material swaps. + // wastes less material, if there are as little tool changes as possible. MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers( object, bottom_contacts, top_contacts, layer_storage, max_object_layer_height); - // Fill in intermediate layers between the top / bottom support contact layers, trimmed by the object. - this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers); + this->trim_support_layers_by_object(object, top_contacts, m_support_layer_height_min, 0., m_gap_xy); + + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; + + // Fill in intermediate layers between the top / bottom support contact layers, trimm them by the object. + this->generate_base_layers(object, bottom_contacts, top_contacts, intermediate_layers, layer_support_areas); #ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) { - const MyLayer &layer = *(*it); - ::Slic3r::SVG svg(debug_out_path("support-base-layers-%d-%lf.svg", iRun, layer.print_z), get_extents(layer.polygons)); - Slic3r::ExPolygons expolys = union_ex(layer.polygons, false); - svg.draw(expolys); - } + for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++ it) + Slic3r::SVG::export_expolygons( + debug_out_path("support-base-layers-%d-%lf.svg", iRun, (*it)->print_z), + union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ - // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette without holes. - // Add the bottom contacts to the raft, inflate the support bases. - // There is a contact layer below the 1st object layer in the bottom contacts. + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating raft"; + + // If raft is to be generated, the 1st top_contact layer will contain the 1st object layer silhouette with holes filled. // There is also a 1st intermediate layer containing bases of support columns. - // Extend the bases of the support columns and create the raft base. - Polygons raft = this->generate_raft_base(object, bottom_contacts, intermediate_layers); + // Inflate the bases of the support columns and create the raft base under the object. + MyLayersPtr raft_layers = this->generate_raft_base(object, top_contacts, intermediate_layers, layer_storage); /* // If we wanted to apply some special logic to the first support layers lying on @@ -279,17 +278,17 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) shape = this->generate_pillars_shape(contact, support_z); */ + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating interfaces"; + // Propagate top / bottom contact layers to generate interface layers. MyLayersPtr interface_layers = this->generate_interface_layers( object, bottom_contacts, top_contacts, intermediate_layers, layer_storage); #ifdef SLIC3R_DEBUG - for (MyLayersPtr::const_iterator it = interface_layers.begin(); it != interface_layers.end(); ++ it) { - const MyLayer &layer = *(*it); - ::Slic3r::SVG svg(debug_out_path("support-interface-layers-%d-%lf.svg", iRun, layer.print_z), get_extents(layer.polygons)); - Slic3r::ExPolygons expolys = union_ex(layer.polygons, false); - svg.draw(expolys); - } + for (MyLayersPtr::const_iterator it = interface_layers.begin(); it != interface_layers.end(); ++ it) + Slic3r::SVG::export_expolygons( + debug_out_path("support-interface-layers-%d-%lf.svg", iRun, (*it)->print_z), + union_ex((*it)->polygons, false)); #endif /* SLIC3R_DEBUG */ /* @@ -300,38 +299,66 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) } */ + BOOST_LOG_TRIVIAL(info) << "Support generator - Creating layers"; + +// raft_layers.clear(); +// bottom_contacts.clear(); +// top_contacts.clear(); +// intermediate_layers.clear(); +// interface_layers.clear(); + // Install support layers into the object. + // A support layer installed on a PrintObject has a unique print_z. MyLayersPtr layers_sorted; - layers_sorted.reserve(bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size()); + layers_sorted.reserve(raft_layers.size() + bottom_contacts.size() + top_contacts.size() + intermediate_layers.size() + interface_layers.size()); + layers_append(layers_sorted, raft_layers); layers_append(layers_sorted, bottom_contacts); layers_append(layers_sorted, top_contacts); layers_append(layers_sorted, intermediate_layers); layers_append(layers_sorted, interface_layers); + // Sort the layers lexicographically by a raising print_z and a decreasing height. std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare()); - int layer_id = 0; + assert(object.support_layers.empty()); for (int i = 0; i < int(layers_sorted.size());) { - // Find the last layer with the same print_z, find the minimum layer height of all. + // Find the last layer with roughly the same print_z, find the minimum layer height of all. + // Due to the floating point inaccuracies, the print_z may not be the same even if in theory they should. int j = i + 1; - coordf_t height_min = layers_sorted[i]->height; - for (; j < layers_sorted.size() && layers_sorted[i]->print_z == layers_sorted[j]->print_z; ++ j) - height_min = std::min(height_min, layers_sorted[j]->height); - object.add_support_layer(layer_id, height_min, layers_sorted[i]->print_z); + coordf_t zmax = layers_sorted[i]->print_z + EPSILON; + for (; j < layers_sorted.size() && layers_sorted[j]->print_z <= zmax; ++j) ; + // Assign an average print_z to the set of layers with nearly equal print_z. + coordf_t zavg = 0.5 * (layers_sorted[i]->print_z + layers_sorted[j - 1]->print_z); + coordf_t height_min = layers_sorted[i]->height; + for (int u = i; u < j; ++u) { + MyLayer &layer = *layers_sorted[u]; + layer.print_z = zavg; + height_min = std::min(height_min, layer.height); + } + object.add_support_layer(layer_id, height_min, zavg); if (layer_id > 0) { + // Inter-link the support layers into a linked list. SupportLayer *sl1 = object.support_layers[object.support_layer_count()-2]; SupportLayer *sl2 = object.support_layers.back(); sl1->upper_layer = sl2; sl2->lower_layer = sl1; } +#ifdef SLIC3R_DEBUG + export_print_z_polygons_to_svg(debug_out_path("support-%d-%lf.svg", iRun, zavg).c_str(), layers_sorted.data() + i, j - i); +#endif i = j; ++ layer_id; } + BOOST_LOG_TRIVIAL(info) << "Support generator - Generating tool paths"; + // Generate the actual toolpaths and save them into each layer. - this->generate_toolpaths(object, raft, bottom_contacts, top_contacts, intermediate_layers, interface_layers); + this->generate_toolpaths(object, raft_layers, bottom_contacts, top_contacts, intermediate_layers, interface_layers); + + BOOST_LOG_TRIVIAL(info) << "Support generator - End"; } -void collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type, Polygons &out) +// Collect all polygons of all regions in a layer with a given surface type. +Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type) { // 1) Count the new polygons first. size_t n_polygons_new = 0; @@ -346,7 +373,8 @@ void collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type, } // 2) Collect the new polygons. - out.reserve(out.size() + n_polygons_new); + Polygons out; + out.reserve(n_polygons_new); for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { const LayerRegion ®ion = *(*it_region); const SurfaceCollection &slices = region.slices; @@ -356,116 +384,63 @@ void collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type, polygons_append(out, surface.expolygon); } } -} -Polygons collect_region_slices_by_type(const Layer &layer, SurfaceType surface_type) -{ - Polygons out; - collect_region_slices_by_type(layer, surface_type, out); return out; } -// Collect outer contours of all expolygons in all layer region slices. -void collect_region_slices_outer(const Layer &layer, Polygons &out) -{ - // 1) Count the new polygons first. - size_t n_polygons_new = 0; - for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { - const LayerRegion ®ion = *(*it_region); - n_polygons_new += region.slices.surfaces.size(); - } - - // 2) Collect the new polygons. - out.reserve(out.size() + n_polygons_new); - for (LayerRegionPtrs::const_iterator it_region = layer.regions.begin(); it_region != layer.regions.end(); ++ it_region) { - const LayerRegion ®ion = *(*it_region); - for (Surfaces::const_iterator it = region.slices.surfaces.begin(); it != region.slices.surfaces.end(); ++ it) - out.push_back(it->expolygon.contour); - } -} - -// Collect outer contours of all expolygons in all layer region slices. -Polygons collect_region_slices_outer(const Layer &layer) -{ - Polygons out; - collect_region_slices_outer(layer, out); - return out; -} - -// Collect outer contours of all expolygons in all layer region slices. -void collect_slices_outer(const Layer &layer, Polygons &out) -{ - out.reserve(out.size() + layer.slices.expolygons.size()); - for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) - out.push_back(it->contour); -} - -// Collect outer contours of all expolygons in all layer region slices. +// Collect outer contours of all slices of this layer. +// This is useful for calculating the support base with holes filled. Polygons collect_slices_outer(const Layer &layer) { Polygons out; - collect_slices_outer(layer, out); + out.reserve(out.size() + layer.slices.expolygons.size()); + for (ExPolygons::const_iterator it = layer.slices.expolygons.begin(); it != layer.slices.expolygons.end(); ++ it) + out.push_back(it->contour); return out; } -// Find the top contact surfaces of the support or the raft. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const +// Generate top contact layers supporting overhangs. +// For a soluble interface material synchronize the layer heights with the object, otherwise leave the layer height undefined. +// If supports over bed surface only are requested, don't generate contact layers over an object. +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( + const PrintObject &object, MyLayerStorage &layer_storage) const { #ifdef SLIC3R_DEBUG static int iRun = 0; ++ iRun; #endif /* SLIC3R_DEBUG */ - // Output layers, sorte by top Z. + // Output layers, sorted by top Z. MyLayersPtr contact_out; // If user specified a custom angle threshold, convert it to radians. - double threshold_rad = 0.; - if (m_object_config->support_material_threshold.value > 0) { - threshold_rad = M_PI * double(m_object_config->support_material_threshold.value + 1) / 180.; // +1 makes the threshold inclusive - // Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); - } + // Zero means automatic overhang detection. + double threshold_rad = (m_object_config->support_material_threshold.value > 0) ? + M_PI * double(m_object_config->support_material_threshold.value + 1) / 180. : // +1 makes the threshold inclusive + 0.; - // Build support on a build plate only? If so, then collect top surfaces into $buildplate_only_top_surfaces - // and subtract $buildplate_only_top_surfaces from the contact surfaces, so + // Build support on a build plate only? If so, then collect top surfaces into buildplate_only_top_surfaces + // and subtract buildplate_only_top_surfaces from the contact surfaces, so // there is no contact surface supported by a top surface. - bool buildplate_only = m_object_config->support_material.value && m_object_config->support_material_buildplate_only.value; + bool buildplate_only = this->build_plate_only(); Polygons buildplate_only_top_surfaces; // Determine top contact areas. - for (size_t layer_id = 0; layer_id < object.layer_count(); ++ layer_id) { - // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. - // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. - if (this->has_raft()) { - if (! this->has_support() && layer_id > 0) - // If we are only going to generate raft. Just check for the 'overhangs' of the first object layer. - break; - // Check for the overhangs at any object layer including the 1st layer. - } else if (layer_id == 0) { - // No raft, 1st object layer cannot be supported by a support contact layer as it sticks directly to print bed. - continue; - } - - const Layer &layer = *object.get_layer(layer_id); - - if (buildplate_only) { - // Collect the top surfaces up to this layer and merge them. - Polygons projection_new = collect_region_slices_by_type(layer, stTop); - if (! projection_new.empty()) { - // Merge the new top surfaces with the preceding top surfaces. - // Apply the safety offset to the newly added polygons, so they will connect - // with the polygons collected before, - // but don't apply the safety offset during the union operation as it would - // inflate the polygons over and over. - projection_new = offset(projection_new, scale_(0.01)); - polygons_append(buildplate_only_top_surfaces, projection_new); - buildplate_only_top_surfaces = union_(buildplate_only_top_surfaces, false); // don't apply the safety offset. - } - } + // If generating raft only (no support), only calculate top contact areas for the 0th layer. + size_t num_layers = this->has_support() ? object.layer_count() : 1; + // If having a raft, start with 0th layer, otherwise with 1st layer. + // Note that layer_id < layer->id when raft_layers > 0 as the layer->id incorporates the raft layers. + // So layer_id == 0 means first object layer and layer->id == 0 means first print layer if there are no explicit raft layers. + for (size_t layer_id = this->has_raft() ? 0 : 1; layer_id < num_layers; ++ layer_id) + { + const Layer &layer = *object.layers[layer_id]; // Detect overhangs and contact areas needed to support them. + // Collect overhangs and contacts of all regions of this layer supported by the layer immediately below. Polygons overhang_polygons; Polygons contact_polygons; + Polygons slices_margin_cached; + float slices_margin_cached_offset = -1.; if (layer_id == 0) { // This is the first object layer, so the object is being printed on a raft and // we're here just to get the object footprint for the raft. @@ -475,39 +450,51 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ contact_polygons = offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN)); } else { // Generate overhang / contact_polygons for non-raft layers. - const Layer &lower_layer = *object.get_layer(int(layer_id)-1); + const Layer &lower_layer = *object.layers[layer_id-1]; + if (buildplate_only) { + // Merge the new slices with the preceding slices. + // Apply the safety offset to the newly added polygons, so they will connect + // with the polygons collected before, + // but don't apply the safety offset during the union operation as it would + // inflate the polygons over and over. + polygons_append(buildplate_only_top_surfaces, offset(lower_layer.slices.expolygons, scale_(0.01))); + buildplate_only_top_surfaces = union_(buildplate_only_top_surfaces, false); // don't apply the safety offset. + } + for (LayerRegionPtrs::const_iterator it_layerm = layer.regions.begin(); it_layerm != layer.regions.end(); ++ it_layerm) { const LayerRegion &layerm = *(*it_layerm); // Extrusion width accounts for the roundings of the extrudates. // It is the maximum widh of the extrudate. - coord_t fw = layerm.flow(frExternalPerimeter).scaled_width(); - coordf_t lower_layer_offset = + float fw = float(layerm.flow(frExternalPerimeter).scaled_width()); + float lower_layer_offset = (layer_id < m_object_config->support_material_enforce_layers.value) ? // Enforce a full possible support, ignore the overhang angle. - 0 : + 0.f : (threshold_rad > 0. ? // Overhang defined by an angle. - scale_(lower_layer.height * cos(threshold_rad) / sin(threshold_rad)) : + float(scale_(lower_layer.height / tan(threshold_rad))) : // Overhang defined by half the extrusion width. - 0.5 * fw); + 0.5f * fw); // Overhang polygons for this layer and region. Polygons diff_polygons; - if (lower_layer_offset == 0.) { + Polygons layerm_polygons = to_polygons(layerm.slices); + Polygons lower_layer_polygons = to_polygons(lower_layer.slices.expolygons); + if (lower_layer_offset == 0.f) { // Support everything. - diff_polygons = diff( - (Polygons)layerm.slices, - (Polygons)lower_layer.slices); + diff_polygons = diff(layerm_polygons, lower_layer_polygons); } else { - // Get the regions needing a suport. - diff_polygons = diff( - (Polygons)layerm.slices, - offset((Polygons)lower_layer.slices, lower_layer_offset)); - // Collapse very tiny spots. - diff_polygons = offset2(diff_polygons, -0.1*fw, +0.1*fw); + // Get the regions needing a suport, collapse very tiny spots. + //FIXME cache the lower layer offset if this layer has multiple regions. + diff_polygons = offset2( + diff(layerm_polygons, + offset(lower_layer_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS)), + -0.1f*fw, +0.1f*fw); if (diff_polygons.empty()) continue; // Offset the support regions back to a full overhang, restrict them to the full overhang. - diff_polygons = diff(intersection(offset(diff_polygons, lower_layer_offset), (Polygons)layerm.slices), (Polygons)lower_layer.slices); + diff_polygons = diff( + intersection(offset(diff_polygons, lower_layer_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS), layerm_polygons), + lower_layer_polygons); } if (diff_polygons.empty()) continue; @@ -523,76 +510,65 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ if (m_object_config->dont_support_bridges) { // compute the area of bridging perimeters // Note: this is duplicate code from GCode.pm, we need to refactor - - Polygons bridged_perimeters; - { - Flow bridge_flow = layerm.flow(frPerimeter, true); - - coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at( - layerm.region()->config.perimeter_extruder-1); - Polygons lower_grown_slices = offset((Polygons)lower_layer.slices, 0.5f*scale_(nozzle_diameter)); - - // TODO: split_at_first_point() could split a bridge mid-way - Polylines overhang_perimeters; - for (ExtrusionEntitiesPtr::const_iterator it_island = layerm.perimeters.entities.begin(); it_island != layerm.perimeters.entities.end(); ++ it_island) { - const ExtrusionEntityCollection *island = dynamic_cast(*it_island); - assert(island != NULL); - for (size_t i = 0; i < island->entities.size(); ++ i) { - ExtrusionEntity *entity = island->entities[i]; - ExtrusionLoop *loop = dynamic_cast(entity); - overhang_perimeters.push_back(loop ? - loop->as_polyline() : - dynamic_cast(entity)->polyline); - } - } - - // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() - for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) - it->points[0].x += 1; - diff(overhang_perimeters, lower_grown_slices, &overhang_perimeters); - - // only consider straight overhangs - // only consider overhangs having endpoints inside layer's slices - // convert bridging polylines into polygons by inflating them with their thickness - // since we're dealing with bridges, we can't assume width is larger than spacing, - // so we take the largest value and also apply safety offset to be ensure no gaps - // are left in between - coordf_t w = std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()); - for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) { - if (it->is_straight()) { - it->extend_start(fw); - it->extend_end(fw); - if (layer.slices.contains(it->first_point()) && layer.slices.contains(it->last_point())) { - // Offset a polyline into a polygon. - Polylines tmp; tmp.push_back(*it); - Polygons out; - offset(tmp, &out, 0.5f * w + 10.f); - polygons_append(bridged_perimeters, out); + if (true) { + Polygons bridged_perimeters; + { + Flow bridge_flow = layerm.flow(frPerimeter, true); + coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(layerm.region()->config.perimeter_extruder-1); + Polygons lower_grown_slices = offset(lower_layer_polygons, 0.5f*float(scale_(nozzle_diameter)), SUPPORT_SURFACES_OFFSET_PARAMETERS); + + // Collect perimeters of this layer. + // TODO: split_at_first_point() could split a bridge mid-way + Polylines overhang_perimeters; + for (ExtrusionEntitiesPtr::const_iterator it_island = layerm.perimeters.entities.begin(); it_island != layerm.perimeters.entities.end(); ++ it_island) { + const ExtrusionEntityCollection *island = dynamic_cast(*it_island); + assert(island != NULL); + for (size_t i = 0; i < island->entities.size(); ++ i) { + ExtrusionEntity *entity = island->entities[i]; + ExtrusionLoop *loop = dynamic_cast(entity); + overhang_perimeters.push_back(loop ? + loop->as_polyline() : + dynamic_cast(entity)->polyline); } } + + // workaround for Clipper bug, see Slic3r::Polygon::clip_as_polyline() + for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) + it->points[0].x += 1; + // Trim the perimeters of this layer by the lower layer to get the unsupported pieces of perimeters. + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); + + // only consider straight overhangs + // only consider overhangs having endpoints inside layer's slices + // convert bridging polylines into polygons by inflating them with their thickness + // since we're dealing with bridges, we can't assume width is larger than spacing, + // so we take the largest value and also apply safety offset to be ensure no gaps + // are left in between + float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing())); + for (Polylines::iterator it = overhang_perimeters.begin(); it != overhang_perimeters.end(); ++ it) { + if (it->is_straight()) { + // This is a bridge + it->extend_start(fw); + it->extend_end(fw); + if (layer.slices.contains(it->first_point()) && layer.slices.contains(it->last_point())) + // Offset a polyline into a polygon. + polygons_append(bridged_perimeters, offset(*it, 0.5f * w + 10.f)); + } + } + bridged_perimeters = union_(bridged_perimeters); } - bridged_perimeters = union_(bridged_perimeters); - } - - if (1) { // remove the entire bridges and only support the unsupported edges Polygons bridges; for (Surfaces::const_iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++ it) if (it->surface_type == stBottomBridge && it->bridge_angle != -1) polygons_append(bridges, it->expolygon); - polygons_append(bridged_perimeters, bridges); - diff_polygons = diff(diff_polygons, bridged_perimeters, true); - - Polygons unsupported_bridge_polygons; - for (Polylines::const_iterator it = layerm.unsupported_bridge_edges.polylines.begin(); - it != layerm.unsupported_bridge_edges.polylines.end(); ++ it) { - // Offset a polyline into a polygon. - Polylines tmp; tmp.push_back(*it); - Polygons out; - offset(tmp, &out, scale_(SUPPORT_MATERIAL_MARGIN)); - polygons_append(unsupported_bridge_polygons, out); - } - polygons_append(diff_polygons, intersection(unsupported_bridge_polygons, bridges)); + diff_polygons = diff(diff_polygons, bridges, true); + polygons_append(bridges, bridged_perimeters); + polygons_append(diff_polygons, + intersection( + // Offset unsupported edges into polygons. + offset(layerm.unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS), + bridges)); } else { // just remove bridged areas diff_polygons = diff(diff_polygons, layerm.bridged, true); @@ -609,14 +585,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ continue; #ifdef SLIC3R_DEBUG - { - ::Slic3r::SVG svg(debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d.svg", iRun, layer_id, it_layerm - layer.regions.begin()), get_extents(diff_polygons)); - Slic3r::ExPolygons expolys = union_ex(diff_polygons, false); - svg.draw(expolys); - } + Slic3r::SVG::export_expolygons( + debug_out_path("support-top-contacts-filtered-run%d-layer%d-region%d.svg", iRun, layer_id, it_layerm - layer.regions.begin()), + union_ex(diff_polygons, false)); #endif /* SLIC3R_DEBUG */ - polygons_append(overhang_polygons, diff_polygons); + if (this->has_contact_loops()) + polygons_append(overhang_polygons, diff_polygons); // Let's define the required contact area by using a max gap of half the upper // extrusion width and extending the area according to the configured margin. @@ -624,11 +599,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // on the other side of the object (if it's very thin). { //FIMXE 1) Make the offset configurable, 2) Make the Z span configurable. - Polygons slices_margin = offset((Polygons)lower_layer.slices, float(0.5*fw)); - if (buildplate_only) { - // Trim the inflated contact surfaces by the top surfaces as well. - polygons_append(slices_margin, buildplate_only_top_surfaces); - slices_margin = union_(slices_margin); + float slices_margin_offset = float(0.5*fw); + if (slices_margin_cached_offset != slices_margin_offset) { + slices_margin_cached_offset = slices_margin_offset; + slices_margin_cached = offset(lower_layer.slices.expolygons, slices_margin_offset, SUPPORT_SURFACES_OFFSET_PARAMETERS); + if (buildplate_only) { + // Trim the inflated contact surfaces by the top surfaces as well. + polygons_append(slices_margin_cached, buildplate_only_top_surfaces); + slices_margin_cached = union_(slices_margin_cached); + } } // Offset the contact polygons outside. for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) { @@ -636,11 +615,10 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ offset( diff_polygons, SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS, - CLIPPER_OFFSET_SCALE, ClipperLib::jtRound, // round mitter limit - scale_(0.05) * CLIPPER_OFFSET_SCALE), - slices_margin); + scale_(0.05)), + slices_margin_cached); } } polygons_append(contact_polygons, diff_polygons); @@ -650,13 +628,14 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // now apply the contact areas to the layer were they need to be made if (! contact_polygons.empty()) { // get the average nozzle diameter used on this layer - MyLayer &new_layer = layer_allocate(layer_storage, sltTopContact); + MyLayer &new_layer = layer_allocate(layer_storage, sltTopContact); + const Layer *layer_below = (layer_id > 0) ? object.layers[layer_id - 1] : NULL; new_layer.idx_object_layer_above = layer_id; - if (m_soluble_interface) { + if (m_slicing_params.soluble_interface) { // Align the contact surface height with a layer immediately below the supported layer. - new_layer.height = (layer_id > 0) ? + new_layer.height = layer_below ? // Interface layer will be synchronized with the object. - object.get_layer(layer_id - 1)->height : + object.layers[layer_id-1]->height : // Don't know the thickness of the raft layer yet. 0.; new_layer.print_z = layer.print_z - layer.height; @@ -677,11 +656,36 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ n_nozzle_dmrs += 3; } nozzle_dmr /= coordf_t(n_nozzle_dmrs); - new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance; - // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and - // its height will be set adaptively later on. - new_layer.height = 0.; + new_layer.print_z = layer.print_z - nozzle_dmr - m_object_config->support_material_contact_distance; new_layer.bottom_z = new_layer.print_z; + new_layer.height = 0.; + if (this->synchronize_layers()) { + // Align bottom of this layer with a top of the closest object layer + // while not trespassing into the 1st layer and keeping the support layer thickness bounded. + int layer_id_below = int(layer_id) - 1; + for (; layer_id_below >= 0; -- layer_id_below) { + layer_below = object.layers[layer_id_below]; + if (layer_below->print_z <= new_layer.print_z - m_support_layer_height_min) { + // This is a feasible support layer height. + new_layer.bottom_z = layer_below->print_z; + new_layer.height = new_layer.print_z - new_layer.bottom_z; + assert(new_layer.height <= m_support_layer_height_max); + break; + } + } + if (layer_id_below == -1) { + // Could not align with any of the top surfaces of object layers. + if (this->has_raft()) { + // If having a raft, all the other layers will be aligned one with the other. + } else { + // Give up, ignore this layer. + continue; + } + } + } else { + // Don't know the height of the top contact layer yet. The top contact layer is printed with a normal flow and + // its height will be set adaptively later on. + } } // Ignore this contact area if it's too low. @@ -691,14 +695,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ if (new_layer.print_z < this->first_layer_height() + m_support_layer_height_min) continue; - new_layer.polygons.swap(contact_polygons); +#if 1 + { + // Create an EdgeGrid, initialize it with projection, initialize signed distance field. + Slic3r::EdgeGrid::Grid grid; + coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); + coord_t grid_resolution = scale_(support_spacing); // scale_(1.5f); + BoundingBox bbox = get_extents(contact_polygons); + bbox.offset(20); + bbox.align_to_grid(grid_resolution); + grid.set_bbox(bbox); + grid.create(contact_polygons, grid_resolution); + grid.calculate_sdf(); + // Extract a bounding contour from the grid, trim by the object. + contact_polygons = diff( + grid.contours_simplified(m_support_material_flow.scaled_spacing()/2 + 5), + slices_margin_cached, + true); + } +#endif + + new_layer.polygons = std::move(contact_polygons); // Store the overhang polygons as the aux_polygons. - // The overhang polygons are used in the path generator for planning of the contact circles. - new_layer.aux_polygons = new Polygons(); - new_layer.aux_polygons->swap(overhang_polygons); + // The overhang polygons are used in the path generator for planning of the contact loops. + // if (this->has_contact_loops()) + new_layer.aux_polygons = new Polygons(std::move(overhang_polygons)); contact_out.push_back(&new_layer); - if (0) { + if (0) { // Slic3r::SVG::output("out\\contact_" . $contact_z . ".svg", // green_expolygons => union_ex($buildplate_only_top_surfaces), // blue_expolygons => union_ex(\@contact), @@ -711,55 +735,147 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ return contact_out; } -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers( - const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage) const +// Generate bottom contact layers supporting the top contact layers. +// For a soluble interface material synchronize the layer heights with the object, +// otherwise set the layer height to a bridging flow of a support interface nozzle. +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, + std::vector &layer_support_areas) const { +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; +#endif /* SLIC3R_DEBUG */ + + // Allocate empty surface areas, one per object layer. + layer_support_areas.assign(object.total_layer_count(), Polygons()); + // find object top surfaces // we'll use them to clip our support and detect where does it stick MyLayersPtr bottom_contacts; - if (! m_object_config->support_material_buildplate_only.value && ! top_contacts.empty()) + + if (! top_contacts.empty()) { + // There is some support to be built, if there are non-empty top surfaces detected. // Sum of unsupported contact areas above the current layer.print_z. Polygons projection; // Last top contact layer visited when collecting the projection of contact areas. int contact_idx = int(top_contacts.size()) - 1; for (int layer_id = int(object.total_layer_count()) - 2; layer_id >= 0; -- layer_id) { + BOOST_LOG_TRIVIAL(trace) << "Support generator - bottom_contact_layers - layer " << layer_id; const Layer &layer = *object.get_layer(layer_id); - Polygons top = collect_region_slices_by_type(layer, stTop); - if (top.empty()) - continue; + // Top surfaces of this layer, to be used to stop the surface volume from growing down. + Polygons top; + if (! m_object_config->support_material_buildplate_only) + top = collect_region_slices_by_type(layer, stTop); // Collect projections of all contact areas above or at the same level as this top surface. - for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) - polygons_append(projection, top_contacts[contact_idx]->polygons); + for (; contact_idx >= 0 && top_contacts[contact_idx]->print_z >= layer.print_z; -- contact_idx) { + Polygons polygons_new; + // Contact surfaces are expanded away from the object, trimmed by the object. + // Use a slight positive offset to overlap the touching regions. + polygons_append(polygons_new, offset(top_contacts[contact_idx]->polygons, SCALED_EPSILON)); + // These are the overhang surfaces. They are touching the object and they are not expanded away from the object. + // Use a slight positive offset to overlap the touching regions. + polygons_append(polygons_new, offset(*top_contacts[contact_idx]->aux_polygons, SCALED_EPSILON)); + polygons_append(projection, union_(polygons_new)); + } + if (projection.empty()) + continue; + projection = union_(projection); + #ifdef SLIC3R_DEBUG + { + BoundingBox bbox = get_extents(projection); + bbox.merge(get_extents(top)); + ::Slic3r::SVG svg(debug_out_path("support-bottom-layers-raw-%d-%lf.svg", iRun, layer.print_z), bbox); + svg.draw(union_ex(top, false), "blue", 0.5f); + svg.draw(union_ex(projection, true), "red", 0.5f); + svg.draw_outline(union_ex(projection, true), "red", "blue", scale_(0.1f)); + svg.draw(layer.slices.expolygons, "green", 0.5f); + } + #endif /* SLIC3R_DEBUG */ + // Now find whether any projection of the contact surfaces above layer.print_z not yet supported by any // top surfaces above layer.print_z falls onto this top surface. - // touching are the contact surfaces supported exclusively by this top surfaaces. - Polygons touching = intersection(projection, top); - if (touching.empty()) - continue; - // Allocate a new bottom contact layer. - MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact); - bottom_contacts.push_back(&layer_new); - // Grow top surfaces so that interface and support generation are generated - // with some spacing from object - it looks we don't need the actual - // top shapes so this can be done here - layer_new.height = m_soluble_interface ? - // Align the interface layer with the object's layer height. - object.get_layer(layer_id + 1)->height : - // Place a bridge flow interface layer over the top surface. - m_support_material_interface_flow.nozzle_diameter; - layer_new.print_z = layer.print_z + layer_new.height + - (m_soluble_interface ? 0. : m_object_config->support_material_contact_distance.value); - layer_new.bottom_z = layer.print_z; - layer_new.idx_object_layer_below = layer_id; - layer_new.bridging = ! m_soluble_interface; - //FIXME how much to inflate the top surface? - Polygons poly_new = offset(touching, float(m_support_material_flow.scaled_width())); - layer_new.polygons.swap(poly_new); + // Touching are the contact surfaces supported exclusively by this top surfaces. + // Don't use a safety offset as it has been applied during insertion of polygons. + if (! top.empty()) { + Polygons touching = intersection(top, projection, false); + if (! touching.empty()) { + // Allocate a new bottom contact layer. + MyLayer &layer_new = layer_allocate(layer_storage, sltBottomContact); + bottom_contacts.push_back(&layer_new); + // Grow top surfaces so that interface and support generation are generated + // with some spacing from object - it looks we don't need the actual + // top shapes so this can be done here + layer_new.height = m_slicing_params.soluble_interface ? + // Align the interface layer with the object's layer height. + object.get_layer(layer_id + 1)->height : + // Place a bridge flow interface layer over the top surface. + m_support_material_interface_flow.nozzle_diameter; + layer_new.print_z = layer.print_z + layer_new.height + + (m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value); + layer_new.bottom_z = layer.print_z; + layer_new.idx_object_layer_below = layer_id; + layer_new.bridging = ! m_slicing_params.soluble_interface; + //FIXME how much to inflate the top surface? + layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS); + #ifdef SLIC3R_DEBUG + Slic3r::SVG::export_expolygons( + debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), + union_ex(layer_new.polygons, false)); + #endif /* SLIC3R_DEBUG */ + } + } // ! top.empty() + + remove_sticks(projection); + remove_degenerate(projection); + + // Create an EdgeGrid, initialize it with projection, initialize signed distance field. + Slic3r::EdgeGrid::Grid grid; + coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); + coord_t grid_resolution = scale_(support_spacing); // scale_(1.5f); + BoundingBox bbox = get_extents(projection); + bbox.offset(20); + bbox.align_to_grid(grid_resolution); + grid.set_bbox(bbox); + grid.create(projection, grid_resolution); + grid.calculate_sdf(); + + // Extract a bounding contour from the grid. + Polygons projection_simplified = grid.contours_simplified(-5); +#ifdef SLIC3R_DEBUG + { + BoundingBox bbox = get_extents(projection); + bbox.merge(get_extents(projection_simplified)); + ::Slic3r::SVG svg(debug_out_path("support-bottom-contacts-simplified-%d-%d.svg", iRun, layer_id), bbox); + svg.draw(union_ex(projection, false), "blue", 0.5); + svg.draw(union_ex(projection_simplified, false), "red", 0.5); + #ifdef SLIC3R_GUI + bbox.min.x -= scale_(5.f); + bbox.min.y -= scale_(5.f); + bbox.max.x += scale_(5.f); + bbox.max.y += scale_(5.f); + EdgeGrid::save_png(grid, bbox, scale_(0.1f), debug_out_path("support-bottom-contacts-df-%d-%d.png", iRun, layer_id).c_str()); + #endif /* SLIC3R_GUI */ + } +#endif /* SLIC3R_DEBUG */ + // Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing + // to allow a placement of suppot zig-zag snake along the grid lines. + layer_support_areas[layer_id] = diff( + grid.contours_simplified(m_support_material_flow.scaled_spacing()/2 + 5), + to_polygons(layer.slices.expolygons), + true); + // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. - projection = diff(projection, touching); + // projection = diff(projection, touching); + projection = diff(projection_simplified, to_polygons(layer.slices.expolygons), true); +// layer_support_areas[layer_id] = projection; } - } + std::reverse(bottom_contacts.begin(), bottom_contacts.end()); + } // ! top_contacts.empty() + + trim_support_layers_by_object(object, bottom_contacts, m_support_layer_height_min, 0., m_gap_xy); + return bottom_contacts; } @@ -777,9 +893,11 @@ void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( // For all top contact layers overlapping with the thick bottom contact layer: for (size_t idx_top = idx_top_first; idx_top < top_contacts.size(); ++ idx_top) { MyLayer &layer_top = *top_contacts[idx_top]; - coordf_t interface_z = m_soluble_interface ? - (layer_top.bottom_z + EPSILON) : - (layer_top.bottom_z - m_support_layer_height_min); + coordf_t interface_z = (layer_top.print_z == layer_top.bottom_z) ? + // Layer height has not been decided yet. + (layer_top.bottom_z - m_support_layer_height_min) : + // Layer height has already been assigned. + (layer_top.bottom_z + EPSILON); if (interface_z < layer_bottom.print_z) { // Layers overlap. Trim layer_top with layer_bottom. layer_top.polygons = diff(layer_top.polygons, layer_bottom.polygons); @@ -789,6 +907,21 @@ void PrintObjectSupportMaterial::trim_top_contacts_by_bottom_contacts( } } +// A helper for sorting the top / bottom contact layers by their contact with the touching support layer: +// Top contact surfaces (those supporting overhangs) are sorted by their bottom print Z, +// bottom contact surfaces (those supported by top object surfaces) are sorted by their top print Z. +struct LayerExtreme +{ + LayerExtreme(PrintObjectSupportMaterial::MyLayer *alayer, bool ais_top) : layer(alayer), is_top(ais_top) {} + PrintObjectSupportMaterial::MyLayer *layer; + // top or bottom extreme + bool is_top; + + coordf_t z() const { return is_top ? layer->print_z : layer->print_z - layer->height; } + + bool operator<(const LayerExtreme &other) const { return z() < other.z(); } +}; + PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_intermediate_support_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, @@ -812,33 +945,34 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int return intermediate_layers; std::sort(extremes.begin(), extremes.end()); - // Top of the 0th layer. - coordf_t top_z_0th = this->raft_base_height() + this->raft_interface_height(); - assert(extremes.front().z() > top_z_0th && extremes.front().z() >= this->first_layer_height()); + assert(extremes.front().z() > m_slicing_params.raft_interface_top_z && extremes.front().z() >= m_slicing_params.first_print_layer_height); + +// bool synchronize = m_slicing_params.soluble_interface || this->synchronize_layers(); + bool synchronize = this->synchronize_layers(); // Generate intermediate layers. // The first intermediate layer is the same as the 1st layer if there is no raft, // or the bottom of the first intermediate layer is aligned with the bottom of the raft contact layer. // Intermediate layers are always printed with a normal etrusion flow (non-bridging). + size_t idx_layer_object = 0; for (size_t idx_extreme = 0; idx_extreme < extremes.size(); ++ idx_extreme) { LayerExtreme *extr1 = (idx_extreme == 0) ? NULL : &extremes[idx_extreme-1]; - coordf_t extr1z = (extr1 == NULL) ? top_z_0th : extr1->z(); + coordf_t extr1z = (extr1 == NULL) ? m_slicing_params.raft_interface_top_z : extr1->z(); LayerExtreme &extr2 = extremes[idx_extreme]; coordf_t extr2z = extr2.z(); coordf_t dist = extr2z - extr1z; - assert(dist > 0.); + assert(dist >= 0.); + if (dist == 0.) + continue; // Insert intermediate layers. size_t n_layers_extra = size_t(ceil(dist / m_support_layer_height_max)); + assert(n_layers_extra > 0); coordf_t step = dist / coordf_t(n_layers_extra); - if (! m_soluble_interface && extr2.layer->layer_type == sltTopContact) { - assert(extr2.layer->height == 0.); + if (! synchronize && extr2.layer->layer_type == sltTopContact) { // This is a top interface layer, which does not have a height assigned yet. Do it now. - if (m_synchronize_support_layers_with_object) { - //FIXME - // Find the - } + assert(extr2.layer->height == 0.); extr2.layer->height = step; - extr2.layer->bottom_z = extr2.layer->print_z - step; + extr2.layer->bottom_z = extr2z = extr2.layer->print_z - step; -- n_layers_extra; if (extr2.layer->bottom_z < this->first_layer_height()) { // Split the span into two layers: the top layer up to the first layer height, @@ -848,16 +982,15 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int extr2.layer->bottom_z = extr2z = this->first_layer_height(); extr2.layer->height = extr2.layer->print_z - extr2.layer->bottom_z; // 2) Insert a new intermediate layer. - MyLayer &layer_new = layer_allocate(layer_storage, stlIntermediate); + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = extr1z; layer_new.print_z = this->first_layer_height(); layer_new.height = layer_new.print_z - layer_new.bottom_z; intermediate_layers.push_back(&layer_new); continue; } - } - if (n_layers_extra > 0 && extr1z + step < this->first_layer_height()) { - MyLayer &layer_new = layer_allocate(layer_storage, stlIntermediate); + } else if (extr1z + step > this->first_layer_height()) { + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); layer_new.bottom_z = extr1z; layer_new.print_z = extr1z = this->first_layer_height(); layer_new.height = layer_new.print_z - layer_new.bottom_z; @@ -865,92 +998,141 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::raft_and_int dist = extr2z - extr1z; assert(dist >= 0.); n_layers_extra = size_t(ceil(dist / m_support_layer_height_max)); - coordf_t step = dist / coordf_t(n_layers_extra); + step = dist / coordf_t(n_layers_extra); } + coordf_t extr2z_large_steps = extr2z; + if (synchronize) { + // Synchronize support layers with the object layers. + if (object.layers.front()->print_z - extr1z > m_support_layer_height_max) { + // Generate the initial couple of layers before reaching the 1st object layer print_z level. + extr2z_large_steps = object.layers.front()->print_z; + dist = extr2z_large_steps - extr1z; + assert(dist >= 0.); + n_layers_extra = size_t(ceil(dist / m_support_layer_height_max)); + step = dist / coordf_t(n_layers_extra); + } + } + // Take the largest allowed step in the Z axis until extr2z_large_steps is reached. for (size_t i = 0; i < n_layers_extra; ++ i) { - MyLayer &layer_new = layer_allocate(layer_storage, stlIntermediate); - layer_new.height = step; - layer_new.bottom_z = (i + 1 == n_layers_extra) ? extr2z : extr1z + i * step; - layer_new.print_z = layer_new.bottom_z + step; + MyLayer &layer_new = layer_allocate(layer_storage, sltIntermediate); + if (i + 1 == n_layers_extra) { + // Last intermediate layer added. Align the last entered layer with extr2z_large_steps exactly. + layer_new.bottom_z = (i == 0) ? extr1z : intermediate_layers.back()->print_z; + layer_new.print_z = extr2z_large_steps; + layer_new.height = layer_new.print_z - layer_new.bottom_z; + } + else { + // Intermediate layer, not the last added. + layer_new.height = step; + layer_new.bottom_z = extr1z + i * step; + layer_new.print_z = layer_new.bottom_z + step; + } intermediate_layers.push_back(&layer_new); } + if (synchronize) { + // Emit support layers synchronized with object layers. + extr1z = extr2z_large_steps; + while (extr1z < extr2z) { + //while (idx_layer_object < object.layers.size() && object.layers[idx_layer_object].print_z < extr1z) + // idx_layer_object + } + } } +#ifdef _DEBUG + for (size_t i = 0; i < top_contacts.size(); ++i) + assert(top_contacts[i]->height > 0.); +#endif /* _DEBUG */ + return intermediate_layers; } // At this stage there shall be intermediate_layers allocated between bottom_contacts and top_contacts, but they have no polygons assigned. -// Also the bottom/top_contacts shall have a thickness assigned already. +// Also the bottom/top_contacts shall have a layer thickness assigned already. void PrintObjectSupportMaterial::generate_base_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers) const + MyLayersPtr &intermediate_layers, + std::vector &layer_support_areas) const { +#ifdef SLIC3R_DEBUG + static int iRun = 0; +#endif /* SLIC3R_DEBUG */ + if (top_contacts.empty()) // No top contacts -> no intermediate layers will be produced. return; // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); - //FIXME make configurable: - coordf_t overlap_extra_above = 0.01; - coordf_t overlap_extra_below = 0.01; - int idx_top_contact_above = int(top_contacts.size()) - 1; - int idx_top_contact_overlapping = int(top_contacts.size()) - 1; int idx_bottom_contact_overlapping = int(bottom_contacts.size()) - 1; + int idx_object_layer_above = int(object.total_layer_count()) - 1; for (int idx_intermediate = int(intermediate_layers.size()) - 1; idx_intermediate >= 0; -- idx_intermediate) { + BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - creating layer " << + idx_intermediate << " of " << intermediate_layers.size(); MyLayer &layer_intermediate = *intermediate_layers[idx_intermediate]; - // New polygons for layer_intermediate. - Polygons polygons_new; - // Find a top_contact layer touching the layer_intermediate from above, if any, and collect its polygons into polygons_new. while (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above]->bottom_z > layer_intermediate.print_z + EPSILON) -- idx_top_contact_above; - if (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above]->print_z > layer_intermediate.print_z) - polygons_append(polygons_new, top_contacts[idx_top_contact_above]->polygons); - // Add polygons from the intermediate layer above. - if (idx_intermediate + 1 < int(intermediate_layers.size())) - polygons_append(polygons_new, intermediate_layers[idx_intermediate+1]->polygons); + // New polygons for layer_intermediate. + Polygons polygons_new; + + // Use the precomputed layer_support_areas. + while (idx_object_layer_above > 0 && object.get_layer(idx_object_layer_above - 1)->print_z > layer_intermediate.print_z - EPSILON) + -- idx_object_layer_above; + polygons_new = layer_support_areas[idx_object_layer_above]; // Polygons to trim polygons_new. - Polygons polygons_trimming; + Polygons polygons_trimming; // Find the first top_contact layer intersecting with this layer. + int idx_top_contact_overlapping = idx_top_contact_above; while (idx_top_contact_overlapping >= 0 && - top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z + overlap_extra_above - EPSILON) + top_contacts[idx_top_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) -- idx_top_contact_overlapping; // Collect all the top_contact layer intersecting with this layer. - for (int i = idx_top_contact_overlapping; i >= 0; -- i) { - MyLayer &layer_top_overlapping = *top_contacts[i]; - if (layer_top_overlapping.print_z < layer_intermediate.bottom_z - overlap_extra_below) + for (; idx_top_contact_overlapping >= 0; -- idx_top_contact_overlapping) { + MyLayer &layer_top_overlapping = *top_contacts[idx_top_contact_overlapping]; + if (layer_top_overlapping.print_z < layer_intermediate.bottom_z + EPSILON) break; polygons_append(polygons_trimming, layer_top_overlapping.polygons); } // Find the first bottom_contact layer intersecting with this layer. while (idx_bottom_contact_overlapping >= 0 && - bottom_contacts[idx_bottom_contact_overlapping]->bottom_z > layer_intermediate.print_z + overlap_extra_above - EPSILON) + bottom_contacts[idx_bottom_contact_overlapping]->bottom_z > layer_intermediate.print_z - EPSILON) -- idx_bottom_contact_overlapping; // Collect all the top_contact layer intersecting with this layer. for (int i = idx_bottom_contact_overlapping; i >= 0; -- i) { - MyLayer &layer_bottom_overlapping = *bottom_contacts[idx_bottom_contact_overlapping]; - if (layer_bottom_overlapping.print_z < layer_intermediate.print_z - layer_intermediate.height - overlap_extra_below) - break; + MyLayer &layer_bottom_overlapping = *bottom_contacts[i]; + if (layer_bottom_overlapping.print_z < layer_intermediate.print_z - layer_intermediate.height + EPSILON) + break; polygons_append(polygons_trimming, layer_bottom_overlapping.polygons); } +#ifdef SLIC3R_DEBUG + { + BoundingBox bbox = get_extents(polygons_new); + bbox.merge(get_extents(polygons_trimming)); + ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-raw-%d-%lf.svg", iRun, layer_intermediate.print_z), bbox); + svg.draw(union_ex(polygons_new, false), "blue", 0.5f); + svg.draw(union_ex(polygons_trimming, true), "red", 0.5f); + } +#endif /* SLIC3R_DEBUG */ + // Trim the polygons, store them. if (polygons_trimming.empty()) - layer_intermediate.polygons.swap(polygons_new); + layer_intermediate.polygons = std::move(polygons_new); else - layer_intermediate.polygons = diff( + layer_intermediate.polygons = diff( polygons_new, polygons_trimming, true); // safety offset to merge the touching source polygons + layer_intermediate.layer_type = sltBase; /* if (0) { @@ -961,45 +1143,53 @@ void PrintObjectSupportMaterial::generate_base_layers( $fillet_radius_scaled, -$fillet_radius_scaled, # Use a geometric offsetting for filleting. - CLIPPER_OFFSET_SCALE, JT_ROUND, - 0.2*$fillet_radius_scaled*CLIPPER_OFFSET_SCALE), + 0.2*$fillet_radius_scaled), $trim_polygons, false); // don't apply the safety offset. } */ } - + #ifdef SLIC3R_DEBUG - static int iRun = 0; - iRun ++; - for (MyLayersPtr::const_iterator it = top_contacts.begin(); it != top_contacts.end(); ++ it) { - const MyLayer &layer = *(*it); - ::Slic3r::SVG svg(debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, layer.print_z), get_extents(layer.polygons)); - Slic3r::ExPolygons expolys = union_ex(layer.polygons, false); - svg.draw(expolys); - } + for (MyLayersPtr::const_iterator it = intermediate_layers.begin(); it != intermediate_layers.end(); ++it) + ::Slic3r::SVG::export_expolygons( + debug_out_path("support-intermediate-layers-untrimmed-%d-%lf.svg", iRun, (*it)->print_z), + union_ex((*it)->polygons, false)); + ++ iRun; #endif /* SLIC3R_DEBUG */ - //FIXME This could be parallelized. - const coordf_t gap_extra_above = 0.1f; - const coordf_t gap_extra_below = 0.1f; - const coord_t gap_xy_scaled = m_support_material_flow.scaled_width(); + trim_support_layers_by_object(object, intermediate_layers, m_support_layer_height_min, m_support_layer_height_min, m_gap_xy); +} + +void PrintObjectSupportMaterial::trim_support_layers_by_object( + const PrintObject &object, + MyLayersPtr &support_layers, + const coordf_t gap_extra_above, + const coordf_t gap_extra_below, + const coordf_t gap_xy) const +{ + //FIXME This could be trivially parallelized. + const coord_t gap_xy_scaled = scale_(gap_xy); size_t idx_object_layer_overlapping = 0; - // For all intermediate layers: - for (MyLayersPtr::iterator it_layer = intermediate_layers.begin(); it_layer != intermediate_layers.end(); ++ it_layer) { - MyLayer &layer_intermediate = *(*it_layer); - if (layer_intermediate.polygons.empty()) + // For all intermediate support layers: + for (MyLayersPtr::iterator it_layer = support_layers.begin(); it_layer != support_layers.end(); ++ it_layer) { + BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming layer " << + (it_layer - support_layers.begin()) << " of " << support_layers.size(); + + MyLayer &support_layer = *(*it_layer); + if (support_layer.polygons.empty()) + // Empty support layer, nothing to trim. continue; // Find the overlapping object layers including the extra above / below gap. while (idx_object_layer_overlapping < object.layer_count() && - object.get_layer(idx_object_layer_overlapping)->print_z < layer_intermediate.print_z - layer_intermediate.height - gap_extra_below + EPSILON) + object.get_layer(idx_object_layer_overlapping)->print_z < support_layer.print_z - support_layer.height - gap_extra_below + EPSILON) ++ idx_object_layer_overlapping; // Collect all the object layers intersecting with this layer. Polygons polygons_trimming; for (int i = idx_object_layer_overlapping; i < object.layer_count(); ++ i) { const Layer &object_layer = *object.get_layer(i); - if (object_layer.print_z > layer_intermediate.print_z + gap_extra_above - EPSILON) + if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON) break; polygons_append(polygons_trimming, (Polygons)object_layer.slices); } @@ -1008,49 +1198,80 @@ void PrintObjectSupportMaterial::generate_base_layers( // perimeter's width. $support contains the full shape of support // material, thus including the width of its foremost extrusion. // We leave a gap equal to a full extrusion width. - layer_intermediate.polygons = diff( - layer_intermediate.polygons, - offset(polygons_trimming, gap_xy_scaled)); + support_layer.polygons = diff( + support_layer.polygons, + offset(polygons_trimming, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } } -Polygons PrintObjectSupportMaterial::generate_raft_base( +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_raft_base( const PrintObject &object, - const MyLayersPtr &bottom_contacts, - MyLayersPtr &intermediate_layers) const + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, + MyLayerStorage &layer_storage) const { - assert(! bottom_contacts.empty()); - + // Areas covered by the raft, supporting the raft interface and the support columns. Polygons raft_polygons; - #if 0 + // How much to inflate the support columns to be stable. This also applies to the 1st layer, if no raft layers are to be printed. const float inflate_factor = scale_(3.); - if (this->has_raft()) { - MyLayer &contacts = *bottom_contacts.front(); - MyLayer &columns_base = *intermediate_layers.front(); - if (m_num_base_raft_layers == 0 && m_num_interface_raft_layers == 0 && m_num_contact_raft_layers == 1) { - // Having only the contact layer, which has the height of the 1st layer. - // We are free to merge the contacts with the columns_base, they will be printed the same way. - polygons_append(contacts.polygons, offset(columns_base.polygons, inflate_factor)); - contacts.polygons = union_(contacts.polygons); - } else { - // Having multiple raft layers. - assert(m_num_interface_raft_layers > 0); - // Extend the raft base by the bases of the support columns, add the raft contacts. - raft_polygons = raft_interface_polygons; - //FIXME make the offset configurable. - polygons_append(raft_polygons, offset(columns_base.polygons, inflate_factor)); - raft_polygons = union_(raft_polygons); - } - } else { - // No raft. The 1st intermediate layer contains the bases of the support columns. - // Expand the polygons, but trim with the object. - MyLayer &columns_base = *intermediate_layers.front(); - columns_base.polygons = diff( - offset(columns_base.polygons, inflate_factor), - offset(m_object->get_layer(0), safety_factor); + MyLayer *contacts = top_contacts.empty() ? nullptr : top_contacts.front(); + MyLayer *columns_base = intermediate_layers.empty() ? nullptr : intermediate_layers.front(); + if (contacts != nullptr && contacts->print_z > m_slicing_params.raft_contact_top_z + EPSILON) + // This is not the raft contact layer. + contacts = nullptr; + + // Output vector. + MyLayersPtr raft_layers; + + // Expand the 1st intermediate layer, which contains the bases of the support columns. + Polygons base; + if (columns_base != nullptr) { + base = offset(columns_base->polygons, inflate_factor); + // Modify the 1st intermediate layer with the expanded support columns. + columns_base->polygons = diff( + base, + offset(m_object->layers.front()->slices.expolygons, scale_(m_gap_xy), SUPPORT_SURFACES_OFFSET_PARAMETERS)); + if (contacts != nullptr) + columns_base->polygons = diff(columns_base->polygons, contacts->polygons); + } + if (m_slicing_params.has_raft() && contacts != nullptr) { + // Merge the untrimmed columns base with the expanded raft interface, to be used for the support base and interface. + base = union_(base, offset(contacts->polygons, inflate_factor, SUPPORT_SURFACES_OFFSET_PARAMETERS)); } - #endif - return raft_polygons; + if (m_slicing_params.has_raft() && m_slicing_params.raft_layers() > 1 && ! base.empty()) { + // Do not add the raft contact layer, only add the raft layers below the contact layer. + // Insert the 1st layer. + { + MyLayer &new_layer = layer_allocate(layer_storage, (m_slicing_params.base_raft_layers > 0) ? sltRaftBase : sltRaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = m_slicing_params.first_print_layer_height; + new_layer.height = m_slicing_params.first_print_layer_height; + new_layer.bottom_z = 0.; + new_layer.polygons = base; + } + // Insert the base layers. + for (size_t i = 1; i < m_slicing_params.base_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + MyLayer &new_layer = layer_allocate(layer_storage, sltRaftBase); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + m_slicing_params.base_raft_layer_height; + new_layer.height = m_slicing_params.base_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = base; + } + // Insert the interface layers. + for (size_t i = 1; i < m_slicing_params.interface_raft_layers; ++ i) { + coordf_t print_z = raft_layers.back()->print_z; + MyLayer &new_layer = layer_allocate(layer_storage, sltRaftInterface); + raft_layers.push_back(&new_layer); + new_layer.print_z = print_z + m_slicing_params.interface_raft_layer_height; + new_layer.height = m_slicing_params.interface_raft_layer_height; + new_layer.bottom_z = print_z; + new_layer.polygons = base; + } + } + + return raft_layers; } // Convert some of the intermediate layers into top/bottom interface layers. @@ -1073,7 +1294,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int MyLayersPtr interface_layers; // Contact layer is considered an interface layer, therefore run the following block only if support_material_interface_layers > 1. - if (! intermediate_layers.empty() && m_object_config->support_material_interface_layers > 1) { + if (! intermediate_layers.empty() && m_object_config->support_material_interface_layers.value > 1) { // Index of the first top contact layer intersecting the current intermediate layer. size_t idx_top_contact_first = 0; // Index of the first bottom contact layer intersecting the current intermediate layer. @@ -1132,26 +1353,192 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::generate_int return interface_layers; } +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + const ExPolygons &expolygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow) +{ + FillParams fill_params; + fill_params.density = density; + fill_params.complete = true; + fill_params.dont_adjust = true; + for (ExPolygons::const_iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { + Surface surface(stInternal, *it_expolygon); + extrusion_entities_append_paths( + dst, + filler->fill_surface(&surface, fill_params), + role, + flow.mm3_per_mm(), flow.width, flow.height); + } +} + +static inline void fill_expolygons_generate_paths( + ExtrusionEntitiesPtr &dst, + ExPolygons &&expolygons, + Fill *filler, + float density, + ExtrusionRole role, + const Flow &flow) +{ + FillParams fill_params; + fill_params.density = density; + fill_params.complete = true; + fill_params.dont_adjust = true; + for (ExPolygons::iterator it_expolygon = expolygons.begin(); it_expolygon != expolygons.end(); ++ it_expolygon) { + Surface surface(stInternal, std::move(*it_expolygon)); + extrusion_entities_append_paths( + dst, + filler->fill_surface(&surface, fill_params), + role, + flow.mm3_per_mm(), flow.width, flow.height); + } +} + +// Support layers, partially processed. +struct MyLayerExtruded +{ + MyLayerExtruded() : layer(nullptr) {} + + bool empty() const { + return layer == nullptr || layer->polygons.empty(); + } + + bool could_merge(const MyLayerExtruded &other) const { + return ! this->empty() && ! other.empty() && + this->layer->height == other.layer->height && + this->layer->bridging == other.layer->bridging; + } + + // Merge regions, perform boolean union over the merged polygons. + void merge(MyLayerExtruded &&other) { + assert(could_merge(other)); + Slic3r::polygons_append(layer->polygons, std::move(other.layer->polygons)); + layer->polygons = union_(layer->polygons, true); + other.layer->polygons.clear(); + } + + void polygons_append(Polygons &dst) const { + if (layer != NULL && ! layer->polygons.empty()) + Slic3r::polygons_append(dst, layer->polygons); + } + + // The source layer. It carries the height and extrusion type (bridging / non bridging, extrusion height). + PrintObjectSupportMaterial::MyLayer *layer; + // Collect extrusions. They will be exported sorted by the bottom height. + ExtrusionEntitiesPtr extrusions; +}; + +typedef std::vector MyLayerExtrudedPtrs; + +struct LoopInterfaceProcessor +{ + LoopInterfaceProcessor(coordf_t circle_r) : + n_contact_loops(0), + circle_radius(circle_r), + circle_distance(circle_r * 3.) + { + // Shape of the top contact area. + circle.points.reserve(6); + for (size_t i = 0; i < 6; ++ i) { + double angle = double(i) * M_PI / 3.; + circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); + } + } + + // Generate loop contacts at the top_contact_layer, + // trim the top_contact_layer->polygons with the areas covered by the loops. + void generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src); + + int n_contact_loops; + coordf_t circle_radius; + coordf_t circle_distance; + Polygon circle; +}; + +void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const Flow &interface_flow_src) +{ + if (n_contact_loops == 0 || top_contact_layer.empty()) + return; + + Flow flow = interface_flow_src; + flow.height = float(top_contact_layer.layer->height); + + Polygons overhang_polygons; + // if (top_contact_layer.layer->aux_polygons != nullptr) + overhang_polygons = std::move(*top_contact_layer.layer->aux_polygons); + + // Generate the outermost loop. + // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) + Polygons top_contact_polygons = offset(top_contact_layer.layer->polygons, - 0.5f * flow.scaled_width()); + + Polygons loops0; + { + // find centerline of the external loop of the contours + // only consider the loops facing the overhang + Polygons external_loops; + // Positions of the loop centers. + Polygons circles; + Polygons overhang_with_margin = offset(overhang_polygons, 0.5f * flow.scaled_width()); + for (Polygons::const_iterator it_contact = top_contact_polygons.begin(); it_contact != top_contact_polygons.end(); ++ it_contact) + if (! intersection_pl(it_contact->split_at_first_point(), overhang_with_margin).empty()) { + external_loops.push_back(*it_contact); + Points positions_new = it_contact->equally_spaced_points(circle_distance); + for (Points::const_iterator it_center = positions_new.begin(); it_center != positions_new.end(); ++ it_center) { + circles.push_back(circle); + Polygon &circle_new = circles.back(); + for (size_t i = 0; i < circle_new.points.size(); ++ i) + circle_new.points[i].translate(*it_center); + } + } + // Apply a pattern to the loop. + loops0 = diff(external_loops, circles); + } + + Polylines loop_lines; + { + // make more loops + Polygons loop_polygons = loops0; + for (size_t i = 1; i < n_contact_loops; ++ i) + polygons_append(loop_polygons, + offset2( + loops0, + - int(i) * flow.scaled_spacing() - 0.5f * flow.scaled_spacing(), + 0.5f * flow.scaled_spacing())); + // clip such loops to the side oriented towards the object + loop_lines.reserve(loop_polygons.size()); + for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) + loop_lines.push_back(it->split_at_first_point()); + loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); + } + + // add the contact infill area to the interface area + // note that growing loops by $circle_radius ensures no tiny + // extrusions are left inside the circles; however it creates + // a very large gap between loops and contact_infill_polygons, so maybe another + // solution should be found to achieve both goals + top_contact_layer.layer->polygons = diff(top_contact_layer.layer->polygons, offset(loop_lines, float(circle_radius * 1.1))); + + // Transform loops into ExtrusionPath objects. + extrusion_entities_append_paths( + top_contact_layer.extrusions, + STDMOVE(loop_lines), + erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height); +} + void PrintObjectSupportMaterial::generate_toolpaths( const PrintObject &object, - const Polygons &raft, + const MyLayersPtr &raft_layers, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, const MyLayersPtr &intermediate_layers, const MyLayersPtr &interface_layers) const { - // Shape of the top contact area. - int n_contact_loops = 1; - coordf_t circle_radius = 1.5 * m_support_material_interface_flow.scaled_width(); - coordf_t circle_distance = 3. * circle_radius; - Polygon circle; - circle.points.reserve(6); - for (size_t i = 0; i < 6; ++ i) { - double angle = double(i) * M_PI / 3.; - circle.points.push_back(Point(circle_radius * cos(angle), circle_radius * sin(angle))); - } - // Slic3r::debugf "Generating patterns\n"; + // loop_interface_processor with a given circle radius. + LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); // Prepare fillers. SupportMaterialPattern support_pattern = m_object_config->support_material_pattern; @@ -1171,25 +1558,26 @@ void PrintObjectSupportMaterial::generate_toolpaths( infill_pattern = ipHoneycomb; break; } -#if SLIC3R_CPPVER >= 11 std::unique_ptr filler_interface = std::unique_ptr(Fill::new_from_type(ipRectilinear)); std::unique_ptr filler_support = std::unique_ptr(Fill::new_from_type(infill_pattern)); -#else - std::auto_ptr filler_interface = std::auto_ptr(Fill::new_from_type(ipRectilinear)); - std::auto_ptr filler_support = std::auto_ptr(Fill::new_from_type(infill_pattern)); -#endif { - BoundingBox bbox_object = object.bounding_box(); +// BoundingBox bbox_object = object.bounding_box(); + BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.))); filler_interface->set_bounding_box(bbox_object); filler_support->set_bounding_box(bbox_object); } coordf_t interface_angle = m_object_config->support_material_angle + 90.; coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing(); - coordf_t interface_density = (interface_spacing == 0.) ? 1. : (m_support_material_interface_flow.spacing() / interface_spacing); + coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); - coordf_t support_density = (support_spacing == 0.) ? 1. : (m_support_material_flow.spacing() / support_spacing); - + coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing); + if (m_object_config->support_material_interface_layers.value == 0) { + // No interface layers allowed, print everything with the base support pattern. + interface_spacing = support_spacing; + interface_density = support_density; + } + //FIXME Parallelize the support generator: /* Slic3r::parallelize( @@ -1206,20 +1594,76 @@ void PrintObjectSupportMaterial::generate_toolpaths( }, ); */ + // Insert the raft base layers. + size_t support_layer_id = 0; + for (; support_layer_id < size_t(std::max(0, int(m_slicing_params.raft_layers()) - 1)); ++ support_layer_id) { + assert(support_layer_id < raft_layers.size()); + SupportLayer &support_layer = *object.support_layers[support_layer_id]; + assert(support_layer.support_fills.entities.empty()); + assert(support_layer.support_interface_fills.entities.empty()); + assert(support_layer.support_islands.expolygons.empty()); + MyLayer &raft_layer = *raft_layers[support_layer_id]; + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + Fill *filler = filler_support.get(); + filler->angle = 0.; + // We don't use $base_flow->spacing because we need a constant spacing + // value that guarantees that all layers are correctly aligned. + Flow flow(m_support_material_flow.width, raft_layer.height, m_support_material_flow.nozzle_diameter, raft_layer.bridging); + filler->spacing = m_support_material_flow.spacing(); + float density = support_density; + // find centerline of the external loop/extrusions + ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ? + // union_ex(base_polygons, true) : + offset2_ex(raft_layer.polygons, SCALED_EPSILON, - SCALED_EPSILON) : + offset2_ex(raft_layer.polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width()); + if (support_layer_id == 0) { + // Base flange. + filler = filler_interface.get(); + filler->angle = m_object_config->support_material_angle + 90.; + density = 0.5f; + flow = m_first_layer_flow; + // use the proper spacing for first layer as we don't need to align + // its pattern to the other layers + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + filler->spacing = flow.spacing(); + } else if (with_sheath) { + // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove. + // TODO: use brim ordering algorithm + Polygons to_infill_polygons = to_polygons(to_infill); + // TODO: use offset2_ex() + to_infill = offset_ex(to_infill, - flow.scaled_spacing()); + extrusion_entities_append_paths( + support_layer.support_fills.entities, + to_polylines(STDMOVE(to_infill_polygons)), + erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); + } + fill_expolygons_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + STDMOVE(to_infill), + // Filler and its parameters + filler, density, + // Extrusion parameters + erSupportMaterial, flow); + + } + // Indices of the 1st layer in their respective container at the support layer height. size_t idx_layer_bottom_contact = 0; size_t idx_layer_top_contact = 0; size_t idx_layer_intermediate = 0; size_t idx_layer_inteface = 0; - for (size_t support_layer_id = 0; support_layer_id < object.support_layers.size(); ++ support_layer_id) + for (; support_layer_id < object.support_layers.size(); ++ support_layer_id) { SupportLayer &support_layer = *object.support_layers[support_layer_id]; // Find polygons with the same print_z. - Polygons bottom_contact_polygons; - Polygons top_contact_polygons; - Polygons base_polygons; - Polygons interface_polygons; + MyLayerExtruded bottom_contact_layer; + MyLayerExtruded top_contact_layer; + MyLayerExtruded base_layer; + MyLayerExtruded interface_layer; + MyLayerExtrudedPtrs mylayers; // Increment the layer indices to find a layer at support_layer.print_z. for (; idx_layer_bottom_contact < bottom_contacts .size() && bottom_contacts [idx_layer_bottom_contact]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_bottom_contact) ; @@ -1227,321 +1671,195 @@ void PrintObjectSupportMaterial::generate_toolpaths( for (; idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate ]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_intermediate ) ; for (; idx_layer_inteface < interface_layers .size() && interface_layers [idx_layer_inteface ]->print_z < support_layer.print_z - EPSILON; ++ idx_layer_inteface ) ; // Copy polygons from the layers. - if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) - bottom_contact_polygons = bottom_contacts[idx_layer_bottom_contact]->polygons; - if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) - top_contact_polygons = top_contacts[idx_layer_top_contact]->polygons; - if (idx_layer_inteface < interface_layers.size() && interface_layers[idx_layer_inteface]->print_z < support_layer.print_z + EPSILON) - interface_polygons = interface_layers[idx_layer_inteface]->polygons; - if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) - base_polygons = intermediate_layers[idx_layer_intermediate]->polygons; - - // We redefine flows locally by applying this layer's height. - Flow flow = m_support_material_flow; - Flow interface_flow = m_support_material_interface_flow; - flow.height = support_layer.height; - interface_flow.height = support_layer.height; - - /* - if (1) { + mylayers.reserve(4); + if (idx_layer_bottom_contact < bottom_contacts.size() && bottom_contacts[idx_layer_bottom_contact]->print_z < support_layer.print_z + EPSILON) { + bottom_contact_layer.layer = bottom_contacts[idx_layer_bottom_contact]; + mylayers.push_back(&bottom_contact_layer); + } + if (idx_layer_top_contact < top_contacts.size() && top_contacts[idx_layer_top_contact]->print_z < support_layer.print_z + EPSILON) { + top_contact_layer.layer = top_contacts[idx_layer_top_contact]; + mylayers.push_back(&top_contact_layer); + } + if (idx_layer_inteface < interface_layers.size() && interface_layers[idx_layer_inteface]->print_z < support_layer.print_z + EPSILON) { + interface_layer.layer = interface_layers[idx_layer_inteface]; + mylayers.push_back(&interface_layer); + } + if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) { + base_layer.layer = intermediate_layers[idx_layer_intermediate]; + mylayers.push_back(&base_layer); + } + // Sort the layers with the same print_z coordinate by their heights, thickest first. + std::sort(mylayers.begin(), mylayers.end(), [](const MyLayerExtruded *p1, const MyLayerExtruded *p2) { return p1->layer->height > p2->layer->height; }); + + /* { require "Slic3r/SVG.pm"; Slic3r::SVG::output("out\\layer_" . $z . ".svg", blue_expolygons => union_ex($base), red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), ); - } - */ + } */ - // Store inslands, over which the retract will be disabled. - { - Polygons polys(bottom_contact_polygons); - polygons_append(polys, interface_polygons); - polygons_append(polys, base_polygons); - polygons_append(polys, top_contact_polygons); - ExPolygons islands = union_ex(polys); - support_layer.support_islands.expolygons.insert(support_layer.support_islands.expolygons.end(), islands.begin(), islands.end()); - } - - Polygons contact_infill_polygons; - if (! top_contact_polygons.empty()) - { - // Having a top interface layer. - if (m_object_config->support_material_interface_layers == 0) - // If no interface layers were requested, we treat the contact layer exactly as a generic base layer. - polygons_append(base_polygons, top_contact_polygons); - else if (n_contact_loops == 0) - // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. - polygons_append(interface_polygons, top_contact_polygons); - else if (! top_contact_polygons.empty()) - { - // Create loop paths and - Polygons overhang_polygons = (top_contacts[idx_layer_top_contact]->aux_polygons == NULL) ? - Polygons() : - *top_contacts[idx_layer_top_contact]->aux_polygons; + if (m_object_config->support_material_interface_layers == 0) { + // If no interface layers were requested, we treat the contact layer exactly as a generic base layer. + if (base_layer.could_merge(top_contact_layer)) + base_layer.merge(std::move(top_contact_layer)); + else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging) + std::swap(base_layer, top_contact_layer); + if (base_layer.could_merge(bottom_contact_layer)) + base_layer.merge(std::move(bottom_contact_layer)); + else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging) + std::swap(base_layer, bottom_contact_layer); + } else { + loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow); + // If no loops are allowed, we treat the contact layer exactly as a generic interface layer. + if (interface_layer.could_merge(top_contact_layer)) + interface_layer.merge(std::move(top_contact_layer)); + } - // Generate the outermost loop. - // Find centerline of the external loop (or any other kind of extrusions should the loop be skipped) - top_contact_polygons = offset(top_contact_polygons, - 0.5 * interface_flow.scaled_width()); - - Polygons loops0; - { - // find centerline of the external loop of the contours - // only consider the loops facing the overhang - Polygons external_loops; - // Positions of the loop centers. - Polygons circles; - { - Polygons overhang_with_margin = offset(overhang_polygons, 0.5 * interface_flow.scaled_width()); - for (Polygons::const_iterator it_contact = top_contact_polygons.begin(); it_contact != top_contact_polygons.end(); ++ it_contact) { - Polylines tmp; - tmp.push_back(it_contact->split_at_first_point()); - if (! intersection(tmp, overhang_with_margin).empty()) { - external_loops.push_back(*it_contact); - Points positions_new = it_contact->equally_spaced_points(circle_distance); - for (Points::const_iterator it_center = positions_new.begin(); it_center != positions_new.end(); ++ it_center) { - circles.push_back(circle); - Polygon &circle_new = circles.back(); - for (size_t i = 0; i < circle_new.points.size(); ++ i) - circle_new.points[i].translate(*it_center); - } - } - } - } - // Apply a pattern to the loop. - loops0 = diff(external_loops, circles); - } - - Polylines loop_lines; - { - // make more loops - Polygons loop_polygons = loops0; - for (size_t i = 1; i < n_contact_loops; ++ i) - polygons_append(loop_polygons, - offset2( - loops0, - - int(i) * interface_flow.scaled_spacing() - 0.5 * interface_flow.scaled_spacing(), - 0.5 * interface_flow.scaled_spacing())); - // clip such loops to the side oriented towards the object - loop_lines.reserve(loop_polygons.size()); - for (Polygons::const_iterator it = loop_polygons.begin(); it != loop_polygons.end(); ++ it) - loop_lines.push_back(it->split_at_first_point()); - loop_lines = intersection(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); - } - - // add the contact infill area to the interface area - // note that growing loops by $circle_radius ensures no tiny - // extrusions are left inside the circles; however it creates - // a very large gap between loops and contact_infill_polygons, so maybe another - // solution should be found to achieve both goals - { - Polygons loop_polygons; - offset(loop_lines, &loop_polygons, circle_radius * 1.1); - contact_infill_polygons = diff(top_contact_polygons, loop_polygons); - } - - // Transform loops into ExtrusionPath objects. - for (Polylines::const_iterator it_polyline = loop_lines.begin(); it_polyline != loop_lines.end(); ++ it_polyline) { - ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterialInterface); - support_layer.support_interface_fills.entities.push_back(extrusion_path); - extrusion_path->polyline = *it_polyline; - extrusion_path->mm3_per_mm = interface_flow.mm3_per_mm(); - extrusion_path->width = interface_flow.width; - extrusion_path->height = support_layer.height; - } - } - } - - // interface and contact infill - if (! interface_polygons.empty() || ! contact_infill_polygons.empty()) { - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler_interface->angle = interface_angle; - filler_interface->spacing = interface_flow.spacing(); - - // find centerline of the external loop - interface_polygons = offset2(interface_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5 * interface_flow.scaled_width()); - // join regions by offsetting them to ensure they're merged - polygons_append(interface_polygons, contact_infill_polygons); - interface_polygons = offset(interface_polygons, SCALED_EPSILON); - + if (! interface_layer.empty() && ! base_layer.empty()) { // turn base support into interface when it's contained in our holes // (this way we get wider interface anchoring) - { - Polygons interface_polygons_new; - interface_polygons_new.reserve(interface_polygons.size()); - for (Polygons::iterator it_polygon = interface_polygons.begin(); it_polygon != interface_polygons.end(); ++ it_polygon) { - if (it_polygon->is_clockwise()) { - Polygons hole; - hole.push_back(*it_polygon); - hole.back().make_counter_clockwise(); - if (diff(hole, base_polygons, true).empty()) - continue; - } - interface_polygons_new.push_back(Polygon()); - interface_polygons_new.back().points.swap(it_polygon->points); - } - interface_polygons.swap(interface_polygons_new); - } - base_polygons = diff(base_polygons, interface_polygons); - - ExPolygons to_fill = union_ex(interface_polygons); - for (ExPolygons::const_iterator it_expolygon = to_fill.begin(); it_expolygon != to_fill.end(); ++ it_expolygon) { - Surface surface(stInternal, *it_expolygon); - FillParams fill_params; - fill_params.density = interface_density; - fill_params.complete = true; - Polylines polylines = filler_interface->fill_surface(&surface, fill_params); - for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { - ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterialInterface); - support_layer.support_interface_fills.entities.push_back(extrusion_path); - extrusion_path->polyline = *it_polyline; - extrusion_path->mm3_per_mm = interface_flow.mm3_per_mm(); - extrusion_path->width = interface_flow.width; - extrusion_path->height = support_layer.height; - } - } + //FIXME one wants to fill in the inner most holes of the interfaces, not all the holes. + Polygons islands = top_level_islands(interface_layer.layer->polygons); + polygons_append(interface_layer.layer->polygons, intersection(base_layer.layer->polygons, islands)); + base_layer.layer->polygons = diff(base_layer.layer->polygons, islands); } - + + // interface and contact infill + if (! top_contact_layer.empty()) { + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + Flow interface_flow( + top_contact_layer.layer->bridging ? top_contact_layer.layer->height : m_support_material_interface_flow.width, + top_contact_layer.layer->height, + m_support_material_interface_flow.nozzle_diameter, + top_contact_layer.layer->bridging); + filler_interface->angle = interface_angle; + filler_interface->spacing = m_support_material_interface_flow.spacing(); + fill_expolygons_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + union_ex(top_contact_layer.layer->polygons, true), + // Filler and its parameters + filler_interface.get(), interface_density, + // Extrusion parameters + erSupportMaterialInterface, interface_flow); + } + + // interface and contact infill + if (! interface_layer.empty()) { + //FIXME When paralellizing, each thread shall have its own copy of the fillers. + Flow interface_flow( + interface_layer.layer->bridging ? interface_layer.layer->height : m_support_material_interface_flow.width, + interface_layer.layer->height, + m_support_material_interface_flow.nozzle_diameter, + interface_layer.layer->bridging); + filler_interface->angle = interface_angle; + filler_interface->spacing = m_support_material_interface_flow.spacing(); + fill_expolygons_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + union_ex(interface_layer.layer->polygons, true), + // Filler and its parameters + filler_interface.get(), interface_density, + // Extrusion parameters + erSupportMaterialInterface, interface_flow); + } + // support or flange - if (! base_polygons.empty()) { + if (! base_layer.empty()) { //FIXME When paralellizing, each thread shall have its own copy of the fillers. Fill *filler = filler_support.get(); filler->angle = angles[support_layer_id % angles.size()]; // We don't use $base_flow->spacing because we need a constant spacing // value that guarantees that all layers are correctly aligned. - filler->spacing = flow.spacing(); - - coordf_t density = support_density; - Flow base_flow = flow; - + Flow flow(m_support_material_flow.width, base_layer.layer->height, m_support_material_flow.nozzle_diameter, base_layer.layer->bridging); + filler->spacing = m_support_material_flow.spacing(); + float density = support_density; // find centerline of the external loop/extrusions - ExPolygons to_infill = offset2_ex(base_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width()); - - /* - if (1) { + ExPolygons to_infill = (support_layer_id == 0 || ! with_sheath) ? + // union_ex(base_polygons, true) : + offset2_ex(base_layer.layer->polygons, SCALED_EPSILON, - SCALED_EPSILON) : + offset2_ex(base_layer.layer->polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width()); + /* { require "Slic3r/SVG.pm"; Slic3r::SVG::output("out\\to_infill_base" . $z . ".svg", red_expolygons => union_ex($contact), green_expolygons => union_ex($interface), blue_expolygons => $to_infill, ); - } - */ - - if (support_layer_id == 0) { + } */ + if (base_layer.layer->bottom_z < EPSILON) { // Base flange. filler = filler_interface.get(); filler->angle = m_object_config->support_material_angle + 90.; - density = 0.5; - base_flow = m_first_layer_flow; + density = 0.5f; + flow = m_first_layer_flow; // use the proper spacing for first layer as we don't need to align // its pattern to the other layers //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = base_flow.spacing(); + filler->spacing = flow.spacing(); } else if (with_sheath) { // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove. // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); - for (Polygons::const_iterator it_polyline = to_infill_polygons.begin(); it_polyline != to_infill_polygons.end(); ++ it_polyline) { - ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial); - support_layer.support_fills.entities.push_back(extrusion_path); - extrusion_path->polyline = *it_polyline; - extrusion_path->mm3_per_mm = flow.mm3_per_mm(); - extrusion_path->width = flow.width; - extrusion_path->height = support_layer.height; - } // TODO: use offset2_ex() - to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing()); - } - - for (ExPolygons::const_iterator it_expolygon = to_infill.begin(); it_expolygon != to_infill.end(); ++ it_expolygon) { - Surface surface(stInternal, *it_expolygon); - FillParams fill_params; - fill_params.density = density; - fill_params.complete = true; - Polylines polylines = filler->fill_surface(&surface, fill_params); - for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { - ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial); - support_layer.support_fills.entities.push_back(extrusion_path); - extrusion_path->polyline = *it_polyline; - extrusion_path->mm3_per_mm = base_flow.mm3_per_mm(); - extrusion_path->width = base_flow.width; - extrusion_path->height = support_layer.height; - } + to_infill = offset_ex(to_infill, - flow.scaled_spacing()); + extrusion_entities_append_paths( + support_layer.support_fills.entities, + to_polylines(STDMOVE(to_infill_polygons)), + erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height); } + fill_expolygons_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + STDMOVE(to_infill), + // Filler and its parameters + filler, density, + // Extrusion parameters + erSupportMaterial, flow); } // support or flange - if (! bottom_contact_polygons.empty()) { + if (! bottom_contact_layer.empty()) { //FIXME When paralellizing, each thread shall have its own copy of the fillers. - Fill *filler = filler_support.get(); - filler->angle = angles[support_layer_id % angles.size()]; - // We don't use $base_flow->spacing because we need a constant spacing - // value that guarantees that all layers are correctly aligned. - filler->spacing = flow.spacing(); - - coordf_t density = support_density; - Flow base_flow = flow; - - // find centerline of the external loop/extrusions - ExPolygons to_infill = offset2_ex(base_polygons, SCALED_EPSILON, - SCALED_EPSILON - 0.5*flow.scaled_width()); - - /* - if (1) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("out\\to_infill_base" . $z . ".svg", - red_expolygons => union_ex($contact), - green_expolygons => union_ex($interface), - blue_expolygons => $to_infill, - ); - } - */ - - if (support_layer_id == 0) { - // Base flange. - filler = filler_interface.get(); - filler->angle = m_object_config->support_material_angle + 90.; - density = 0.5; - base_flow = m_first_layer_flow; - // use the proper spacing for first layer as we don't need to align - // its pattern to the other layers - //FIXME When paralellizing, each thread shall have its own copy of the fillers. - filler->spacing = base_flow.spacing(); - } else if (with_sheath) { - // Draw a perimeter all around the support infill. This makes the support stable, but difficult to remove. - // TODO: use brim ordering algorithm - Polygons to_infill_polygons = to_polygons(to_infill); - for (Polygons::const_iterator it_polyline = to_infill_polygons.begin(); it_polyline != to_infill_polygons.end(); ++ it_polyline) { - ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial); - support_layer.support_fills.entities.push_back(extrusion_path); - extrusion_path->polyline = *it_polyline; - extrusion_path->mm3_per_mm = flow.mm3_per_mm(); - extrusion_path->width = flow.width; - extrusion_path->height = support_layer.height; - } - // TODO: use offset2_ex() - to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing()); - } - - for (ExPolygons::const_iterator it_expolygon = to_infill.begin(); it_expolygon != to_infill.end(); ++ it_expolygon) { - Surface surface(stInternal, *it_expolygon); - FillParams fill_params; - fill_params.density = density; - fill_params.complete = true; - Polylines polylines = filler->fill_surface(&surface, fill_params); - for (Polylines::const_iterator it_polyline = polylines.begin(); it_polyline != polylines.end(); ++ it_polyline) { - ExtrusionPath *extrusion_path = new ExtrusionPath(erSupportMaterial); - support_layer.support_fills.entities.push_back(extrusion_path); - extrusion_path->polyline = *it_polyline; - extrusion_path->mm3_per_mm = base_flow.mm3_per_mm(); - extrusion_path->width = base_flow.width; - extrusion_path->height = support_layer.height; - } - } + Flow interface_flow( + bottom_contact_layer.layer->bridging ? bottom_contact_layer.layer->height : m_support_material_interface_flow.width, + bottom_contact_layer.layer->height, + m_support_material_interface_flow.nozzle_diameter, + bottom_contact_layer.layer->bridging); + filler_interface->angle = (m_object_config->support_material_interface_layers.value == 0) ? + // If zero interface layers are configured, use the same angle as for the base layers. + angles[support_layer_id % angles.size()] : + // Use interface angle for the interface layers. + interface_angle; + filler_interface->spacing = m_support_material_interface_flow.spacing(); + fill_expolygons_generate_paths( + // Destination + support_layer.support_fills.entities, + // Regions to fill + union_ex(bottom_contact_layer.layer->polygons, true), + // Filler and its parameters + filler_interface.get(), interface_density, + // Extrusion parameters + erSupportMaterial, interface_flow); } - /* - if (0) { + // Collect the support areas with this print_z into islands, as there is no need + // for retraction over these islands. + Polygons polys; + // Collect the extrusions, sorted by the bottom extrusion height. + for (MyLayerExtrudedPtrs::iterator it = mylayers.begin(); it != mylayers.end(); ++ it) { + (*it)->polygons_append(polys); + std::move(std::begin((*it)->extrusions), std::end((*it)->extrusions), + std::back_inserter(support_layer.support_fills.entities)); + } + if (! polys.empty()) + expolygons_append(support_layer.support_islands.expolygons, union_ex(polys)); + /* { require "Slic3r/SVG.pm"; Slic3r::SVG::output("islands_" . $z . ".svg", red_expolygons => union_ex($contact), @@ -1549,8 +1867,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ], polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ], ); - } - */ + } */ } // for each support_layer_id } diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index 31a7899ce..48c5fc3bd 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -3,6 +3,7 @@ #include "Flow.hpp" #include "PrintConfig.hpp" +#include "Slicing.hpp" namespace Slic3r { @@ -20,19 +21,33 @@ class PrintObjectConfig; class PrintObjectSupportMaterial { public: + // Support layer type to be used by MyLayer. This type carries a much more detailed information + // about the support layer type than the final support layers stored in a PrintObject. enum SupporLayerType { sltUnknown = 0, - sltRaft, - stlFirstLayer, + // Ratft base layer, to be printed with the support material. + sltRaftBase, + // Raft interface layer, to be printed with the support interface material. + sltRaftInterface, + // Bottom contact layer placed over a top surface of an object. To be printed with a support interface material. sltBottomContact, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object by an sltBottomContact layer. sltBottomInterface, + // Sparse base support layer, to be printed with a support material. sltBase, + // Dense interface layer, to be printed with the support interface material. + // This layer is separated from an object with sltTopContact layer. sltTopInterface, + // Top contact layer directly supporting an overhang. To be printed with a support interface material. sltTopContact, - // Some undecided type yet. It will turn into stlBase first, then it may turn into stlBottomInterface or stlTopInterface. - stlIntermediate, + // Some undecided type yet. It will turn into sltBase first, then it may turn into sltBottomInterface or sltTopInterface. + sltIntermediate, }; + // A support layer type used internally by the SupportMaterial class. This class carries a much more detailed + // information about the support layer than the layers stored in the PrintObject, mainly + // the MyLayer is aware of the bridging flow and the interface gaps between the object and the support. class MyLayer { public: @@ -57,6 +72,7 @@ public: return print_z == layer2.print_z && height == layer2.height && bridging == layer2.bridging; } + // Order the layers by lexicographically by an increasing print_z and a decreasing layer height. bool operator<(const MyLayer &layer2) const { if (print_z < layer2.print_z) { return true; @@ -72,12 +88,12 @@ public: } SupporLayerType layer_type; - // Z used for printing in unscaled coordinates + // Z used for printing, in unscaled coordinates. coordf_t print_z; - // Bottom height of this layer. For soluble layers, bottom_z + height = print_z, + // Bottom Z of this layer. For soluble layers, bottom_z + height = print_z, // otherwise bottom_z + gap + height = print_z. coordf_t bottom_z; - // layer height in unscaled coordinates + // Layer height in unscaled coordinates. coordf_t height; // Index of a PrintObject layer_id supported by this layer. This will be set for top contact layers. // If this is not a contact layer, it will be set to size_t(-1). @@ -91,63 +107,31 @@ public: // Polygons to be filled by the support pattern. Polygons polygons; // Currently for the contact layers only: Overhangs are stored here. + // MyLayer owns the aux_polygons, they are freed by the destructor. Polygons *aux_polygons; }; - struct LayerExtreme - { - LayerExtreme(MyLayer *alayer, bool ais_top) : layer(alayer), is_top(ais_top) {} - MyLayer *layer; - // top or bottom extreme - bool is_top; - - coordf_t z() const { return is_top ? layer->print_z : layer->print_z - layer->height; } - - bool operator<(const LayerExtreme &other) const { return z() < other.z(); } - }; - -/* - struct LayerPrintZ_Hash { - size_t operator()(const MyLayer &layer) const { - return std::hash()(layer.print_z)^std::hash()(layer.height)^size_t(layer.bridging); - } - }; -*/ - - typedef std::vector MyLayersPtr; + // Layers are allocated and owned by a deque. Once a layer is allocated, it is maintained + // up to the end of a generate() method. The layer storage may be replaced by an allocator class in the future, + // which would allocate layers by multiple chunks. typedef std::deque MyLayerStorage; + typedef std::vector MyLayersPtr; public: - PrintObjectSupportMaterial(const PrintObject *object); + PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params); // Height of the 1st layer is user configured as it is important for the print // to stick to he print bed. coordf_t first_layer_height() const { return m_object_config->first_layer_height.value; } // Is raft enabled? - bool has_raft() const { return m_has_raft; } + bool has_raft() const { return m_slicing_params.has_raft(); } // Has any support? bool has_support() const { return m_object_config->support_material.value; } + bool build_plate_only() const { return this->has_support() && m_object_config->support_material_buildplate_only.value; } - // How many raft layers are there below the 1st object layer? - // The 1st object layer_id will be offsetted by this number. - size_t num_raft_layers() const { return m_object_config->raft_layers.value; } - // num_raft_layers() == num_raft_base_layers() + num_raft_interface_layers() + num_raft_contact_layers(). - size_t num_raft_base_layers() const { return m_num_base_raft_layers; } - size_t num_raft_interface_layers() const { return m_num_interface_raft_layers; } - size_t num_raft_contact_layers() const { return m_num_contact_raft_layers; } - - coordf_t raft_height() const { return m_raft_height; } - coordf_t raft_base_height() const { return m_raft_base_height; } - coordf_t raft_interface_height() const { return m_raft_interface_height; } - coordf_t raft_contact_height() const { return m_raft_contact_height; } - bool raft_bridging() const { return m_raft_contact_layer_bridging; } - - // 1st layer of the object will be printed depeding on the raft settings. - coordf_t first_object_layer_print_z() const { return m_object_1st_layer_print_z; } - coordf_t first_object_layer_height() const { return m_object_1st_layer_height; } - coordf_t first_object_layer_gap() const { return m_object_1st_layer_gap; } - bool first_object_layer_bridging() const { return m_object_1st_layer_bridging; } + bool synchronize_layers() const { return m_object_config->support_material_synchronize_layers.value; } + bool has_contact_loops() const { return m_object_config->support_material_interface_contact_loops.value; } // Generate support material for the object. // New support layers will be added to the object, @@ -163,7 +147,9 @@ private: // Generate bottom contact layers supporting the top contact layers. // For a soluble interface material synchronize the layer heights with the object, // otherwise set the layer height to a bridging flow of a support interface nozzle. - MyLayersPtr bottom_contact_layers(const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage) const; + MyLayersPtr bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, + std::vector &layer_support_areas) const; // Trim the top_contacts layers with the bottom_contacts layers if they overlap, so there would not be enough vertical space for both of them. void trim_top_contacts_by_bottom_contacts(const PrintObject &object, const MyLayersPtr &bottom_contacts, MyLayersPtr &top_contacts) const; @@ -176,17 +162,23 @@ private: MyLayerStorage &layer_storage, const coordf_t max_object_layer_height) const; + // Fill in the base layers with polygons. void generate_base_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers) const; + MyLayersPtr &intermediate_layers, + std::vector &layer_support_areas) const; - Polygons generate_raft_base( + // Generate raft layers, also expand the 1st support layer + // in case there is no raft layer to improve support adhesion. + MyLayersPtr generate_raft_base( const PrintObject &object, - const MyLayersPtr &bottom_contacts, - MyLayersPtr &intermediate_layers) const; + const MyLayersPtr &top_contacts, + MyLayersPtr &intermediate_layers, + MyLayerStorage &layer_storage) const; + // Turn some of the base layers into interface layers. MyLayersPtr generate_interface_layers( const PrintObject &object, const MyLayersPtr &bottom_contacts, @@ -194,6 +186,15 @@ private: MyLayersPtr &intermediate_layers, MyLayerStorage &layer_storage) const; + // Trim support layers by an object to leave a defined gap between + // the support volume and the object. + void trim_support_layers_by_object( + const PrintObject &object, + MyLayersPtr &support_layers, + const coordf_t gap_extra_above, + const coordf_t gap_extra_below, + const coordf_t gap_xy) const; + /* void generate_pillars_shape(); void clip_with_shape(); @@ -202,59 +203,28 @@ private: // Produce the actual G-code. void generate_toolpaths( const PrintObject &object, - const Polygons &raft, + const MyLayersPtr &raft_layers, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, const MyLayersPtr &intermediate_layers, const MyLayersPtr &interface_layers) const; + // Following objects are not owned by SupportMaterial class. const PrintObject *m_object; const PrintConfig *m_print_config; const PrintObjectConfig *m_object_config; + // Pre-calculated parameters shared between the object slicer and the support generator, + // carrying information on a raft, 1st layer height, 1st object layer height, gap between the raft and object etc. + SlicingParameters m_slicing_params; Flow m_first_layer_flow; Flow m_support_material_flow; Flow m_support_material_interface_flow; - bool m_soluble_interface; - Flow m_support_material_raft_base_flow; - Flow m_support_material_raft_interface_flow; - Flow m_support_material_raft_contact_flow; - - bool m_has_raft; - size_t m_num_base_raft_layers; - size_t m_num_interface_raft_layers; - size_t m_num_contact_raft_layers; - // If set, the raft contact layer is laid with round strings, which are easily detachable - // from both the below and above layes. - // Otherwise a normal flow is used and the strings are squashed against the layer below, - // creating a firm bond with the layer below and making the interface top surface flat. - coordf_t m_raft_height; - coordf_t m_raft_base_height; - coordf_t m_raft_interface_height; - coordf_t m_raft_contact_height; - bool m_raft_contact_layer_bridging; - - coordf_t m_object_1st_layer_print_z; - coordf_t m_object_1st_layer_height; - coordf_t m_object_1st_layer_gap; - bool m_object_1st_layer_bridging; - - coordf_t m_object_layer_height_max; coordf_t m_support_layer_height_min; coordf_t m_support_layer_height_max; - coordf_t m_support_interface_layer_height_max; - coordf_t m_gap_extra_above; - coordf_t m_gap_extra_below; - coordf_t m_gap_xy; - - // If enabled, the support layers will be synchronized with object layers. - // This does not prevent the support layers to be combined. - bool m_synchronize_support_layers_with_object; - // If disabled and m_synchronize_support_layers_with_object, - // the support layers will be synchronized with the object layers exactly, no layer will be combined. - bool m_combine_support_layers; + coordf_t m_gap_xy; }; } // namespace Slic3r diff --git a/xs/src/libslic3r/Surface.hpp b/xs/src/libslic3r/Surface.hpp index adf71cc06..9ad86d0ed 100644 --- a/xs/src/libslic3r/Surface.hpp +++ b/xs/src/libslic3r/Surface.hpp @@ -57,6 +57,7 @@ public: operator Polygons() const; double area() const; bool empty() const { return expolygon.empty(); } + void clear() { expolygon.clear(); } bool is_solid() const; bool is_external() const; bool is_internal() const; diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 804190548..70272fede 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -8,22 +8,12 @@ namespace Slic3r { SurfaceCollection::operator Polygons() const { - Polygons polygons; - for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - Polygons surface_p = surface->expolygon; - polygons.insert(polygons.end(), surface_p.begin(), surface_p.end()); - } - return polygons; + return to_polygons(surfaces); } SurfaceCollection::operator ExPolygons() const { - ExPolygons expp; - expp.reserve(this->surfaces.size()); - for (Surfaces::const_iterator surface = this->surfaces.begin(); surface != this->surfaces.end(); ++surface) { - expp.push_back(surface->expolygon); - } - return expp; + return to_expolygons(surfaces); } void @@ -196,19 +186,6 @@ SurfaceCollection::remove_types(const SurfaceType *types, int ntypes) surfaces.erase(surfaces.begin() + j, surfaces.end()); } -void -SurfaceCollection::append(const SurfaceCollection &coll) -{ - this->surfaces.insert(this->surfaces.end(), coll.surfaces.begin(), coll.surfaces.end()); -} - -void -SurfaceCollection::append(const SurfaceType surfaceType, const Slic3r::ExPolygons &expoly) -{ - for (Slic3r::ExPolygons::const_iterator it = expoly.begin(); it != expoly.end(); ++ it) - this->surfaces.push_back(Slic3r::Surface(surfaceType, *it)); -} - void SurfaceCollection::export_to_svg(const char *path, bool show_labels) { BoundingBox bbox; diff --git a/xs/src/libslic3r/SurfaceCollection.hpp b/xs/src/libslic3r/SurfaceCollection.hpp index e013bf891..22c26936e 100644 --- a/xs/src/libslic3r/SurfaceCollection.hpp +++ b/xs/src/libslic3r/SurfaceCollection.hpp @@ -28,8 +28,27 @@ class SurfaceCollection void remove_type(const SurfaceType type); void remove_types(const SurfaceType *types, int ntypes); void filter_by_type(SurfaceType type, Polygons* polygons); - void append(const SurfaceCollection &coll); - void append(const SurfaceType surfaceType, const ExPolygons &expoly); + + void clear() { surfaces.clear(); } + bool empty() const { return surfaces.empty(); } + + void set(const SurfaceCollection &coll) { surfaces = coll.surfaces; } + void set(SurfaceCollection &&coll) { surfaces = std::move(coll.surfaces); } + void set(const ExPolygons &src, SurfaceType surfaceType) { clear(); this->append(src, surfaceType); } + void set(const ExPolygons &src, const Surface &surfaceTempl) { clear(); this->append(src, surfaceTempl); } + void set(const Surfaces &src) { clear(); this->append(src); } + void set(ExPolygons &&src, SurfaceType surfaceType) { clear(); this->append(std::move(src), surfaceType); } + void set(ExPolygons &&src, const Surface &surfaceTempl) { clear(); this->append(std::move(src), surfaceTempl); } + void set(Surfaces &&src) { clear(); this->append(std::move(src)); } + + void append(const SurfaceCollection &coll) { this->append(coll.surfaces); } + void append(SurfaceCollection &&coll) { this->append(std::move(coll.surfaces)); } + void append(const ExPolygons &src, SurfaceType surfaceType) { surfaces_append(this->surfaces, src, surfaceType); } + void append(const ExPolygons &src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, src, surfaceTempl); } + void append(const Surfaces &src) { surfaces_append(this->surfaces, src); } + void append(ExPolygons &&src, SurfaceType surfaceType) { surfaces_append(this->surfaces, std::move(src), surfaceType); } + void append(ExPolygons &&src, const Surface &surfaceTempl) { surfaces_append(this->surfaces, std::move(src), surfaceTempl); } + void append(Surfaces &&src) { surfaces_append(this->surfaces, std::move(src)); } // For debugging purposes: void export_to_svg(const char *path, bool show_labels); diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 5737747a2..71e34f3b8 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -2,8 +2,8 @@ #include "ClipperUtils.hpp" #include "Geometry.hpp" #include -#include #include +#include #include #include #include @@ -193,12 +193,17 @@ void TriangleMesh::scale(const Pointf3 &versor) void TriangleMesh::translate(float x, float y, float z) { + if (x == 0.f && y == 0.f && z == 0.f) + return; stl_translate_relative(&(this->stl), x, y, z); stl_invalidate_shared_vertices(&this->stl); } void TriangleMesh::rotate(float angle, const Axis &axis) { + if (angle == 0.f) + return; + // admesh uses degrees angle = Slic3r::Geometry::rad2deg(angle); @@ -265,6 +270,8 @@ void TriangleMesh::align_to_origin() void TriangleMesh::rotate(double angle, Point* center) { + if (angle == 0.) + return; this->translate(-center->x, -center->y, 0); stl_rotate_z(&(this->stl), (float)angle); this->translate(+center->x, +center->y, 0); @@ -363,10 +370,7 @@ TriangleMesh::horizontal_projection() const } // the offset factor was tuned using groovemount.stl - offset(pp, &pp, 0.01 / SCALING_FACTOR); - ExPolygons retval; - union_(pp, &retval, true); - return retval; + return union_ex(offset(pp, 0.01 / SCALING_FACTOR), true); } Polygon @@ -403,7 +407,7 @@ TriangleMesh::require_shared_vertices() } void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) +TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { /* This method gets called with a list of unscaled Z coordinates and outputs @@ -427,58 +431,66 @@ TriangleMeshSlicer::slice(const std::vector &z, std::vector* la At the end, we free the tables generated by analyze() as we don't need them anymore. - FUTURE: parallelize slice_facet() and make_loops() NOTE: this method accepts a vector of floats because the mesh coordinate type is float. */ std::vector lines(z.size()); - - for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; facet_idx++) { - stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; - - // find facet extents - float min_z = fminf(facet->vertex[0].z, fminf(facet->vertex[1].z, facet->vertex[2].z)); - float max_z = fmaxf(facet->vertex[0].z, fmaxf(facet->vertex[1].z, facet->vertex[2].z)); - - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, - facet->vertex[0].x, facet->vertex[0].y, facet->vertex[0].z, - facet->vertex[1].x, facet->vertex[1].y, facet->vertex[1].z, - facet->vertex[2].x, facet->vertex[2].y, facet->vertex[2].z); - printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif - - // find layer extents - std::vector::const_iterator min_layer, max_layer; - min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif - - for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { - std::vector::size_type layer_idx = it - z.begin(); - this->slice_facet(*it / SCALING_FACTOR, *facet, facet_idx, min_z, max_z, &lines[layer_idx]); - } + { + boost::mutex lines_mutex; + parallelize( + 0, + this->mesh->stl.stats.number_of_facets-1, + boost::bind(&TriangleMeshSlicer::_slice_do, this, _1, &lines, &lines_mutex, z) + ); } // v_scaled_shared could be freed here // build loops layers->resize(z.size()); - for (std::vector::iterator it = lines.begin(); it != lines.end(); ++it) { - size_t layer_idx = it - lines.begin(); - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer " PRINTF_ZU ":\n", layer_idx); - #endif - this->make_loops(*it, &(*layers)[layer_idx]); + parallelize( + 0, + lines.size()-1, + boost::bind(&TriangleMeshSlicer::_make_loops_do, this, _1, &lines, layers) + ); +} + +void +TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, + const std::vector &z) const +{ + const stl_facet &facet = this->mesh->stl.facet_start[facet_idx]; + + // find facet extents + const float min_z = fminf(facet.vertex[0].z, fminf(facet.vertex[1].z, facet.vertex[2].z)); + const float max_z = fmaxf(facet.vertex[0].z, fmaxf(facet.vertex[1].z, facet.vertex[2].z)); + + #ifdef SLIC3R_DEBUG + printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, + facet.vertex[0].x, facet.vertex[0].y, facet.vertex[0].z, + facet.vertex[1].x, facet.vertex[1].y, facet.vertex[1].z, + facet.vertex[2].x, facet.vertex[2].y, facet.vertex[2].z); + printf("z: min = %.2f, max = %.2f\n", min_z, max_z); + #endif + + // find layer extents + std::vector::const_iterator min_layer, max_layer; + min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z + max_layer = std::upper_bound(z.begin() + (min_layer - z.begin()), z.end(), max_z) - 1; // last layer whose slice_z is <= max_z + #ifdef SLIC3R_DEBUG + printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); + #endif + + for (std::vector::const_iterator it = min_layer; it != max_layer + 1; ++it) { + std::vector::size_type layer_idx = it - z.begin(); + this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &(*lines)[layer_idx], lines_mutex); } } void -TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) +TriangleMeshSlicer::slice(const std::vector &z, std::vector* layers) const { std::vector layers_p; this->slice(z, &layers_p); @@ -495,7 +507,9 @@ TriangleMeshSlicer::slice(const std::vector &z, std::vector* } void -TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const +TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, + const float &min_z, const float &max_z, std::vector* lines, + boost::mutex* lines_mutex) const { std::vector points; std::vector< std::vector::size_type > points_on_layer; @@ -547,7 +561,12 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int line.b.y = b->y; line.a_id = a_id; line.b_id = b_id; - lines->push_back(line); + if (lines_mutex != NULL) { + boost::lock_guard l(*lines_mutex); + lines->push_back(line); + } else { + lines->push_back(line); + } found_horizontal_edge = true; @@ -600,13 +619,24 @@ TriangleMeshSlicer::slice_facet(float slice_z, const stl_facet &facet, const int line.b_id = points[0].point_id; line.edge_a_id = points[1].edge_id; line.edge_b_id = points[0].edge_id; - lines->push_back(line); + if (lines_mutex != NULL) { + boost::lock_guard l(*lines_mutex); + lines->push_back(line); + } else { + lines->push_back(line); + } return; } } void -TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) +TriangleMeshSlicer::_make_loops_do(size_t i, std::vector* lines, std::vector* layers) const +{ + this->make_loops((*lines)[i], &(*layers)[i]); +} + +void +TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const { /* SVG svg("lines.svg"); @@ -707,6 +737,7 @@ TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* l for (IntersectionLinePtrs::const_iterator lineptr = loop.begin(); lineptr != loop.end(); ++lineptr) { p.points.push_back((*lineptr)->a); } + loops->push_back(p); #ifdef SLIC3R_TRIANGLEMESH_DEBUG @@ -746,7 +777,7 @@ class _area_comp { }; void -TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) const { Polygons loops; this->make_loops(lines, &loops); @@ -780,7 +811,7 @@ TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, } void -TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) const { /* Input loops are not suitable for evenodd nor nonzero fill types, as we might get @@ -818,17 +849,15 @@ TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) of the loops, since the Orientation() function provided by Clipper would do the same, thus repeating the calculation */ Polygons::const_iterator loop = loops.begin() + *loop_idx; - if (area[*loop_idx] > +EPSILON) { + if (area[*loop_idx] > +EPSILON) p_slices.push_back(*loop); - } else if (area[*loop_idx] < -EPSILON) { - diff(p_slices, *loop, &p_slices); - } + else if (area[*loop_idx] < -EPSILON) + p_slices = diff(p_slices, *loop); } // perform a safety offset to merge very close facets (TODO: find test case for this) double safety_offset = scale_(0.0499); - ExPolygons ex_slices; - offset2(p_slices, &ex_slices, +safety_offset, -safety_offset); + ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG size_t holes_count = 0; @@ -840,11 +869,11 @@ TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slices) #endif // append to the supplied collection - slices->insert(slices->end(), ex_slices.begin(), ex_slices.end()); + expolygons_append(*slices, ex_slices); } void -TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPolygons* slices) const { Polygons pp; this->make_loops(lines, &pp); @@ -852,7 +881,7 @@ TriangleMeshSlicer::make_expolygons(std::vector &lines, ExPoly } void -TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) +TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) const { IntersectionLines upper_lines, lower_lines; @@ -1004,7 +1033,6 @@ TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) stl_get_size(&(upper->stl)); stl_get_size(&(lower->stl)); - } TriangleMeshSlicer::TriangleMeshSlicer(TriangleMesh* _mesh) : mesh(_mesh), v_scaled_shared(NULL) diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index cf129809a..ff332ed66 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include #include +#include #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" @@ -88,19 +89,23 @@ class TriangleMeshSlicer TriangleMesh* mesh; TriangleMeshSlicer(TriangleMesh* _mesh); ~TriangleMeshSlicer(); - void slice(const std::vector &z, std::vector* layers); - void slice(const std::vector &z, std::vector* layers); - void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, const float &min_z, const float &max_z, std::vector* lines) const; - void cut(float z, TriangleMesh* upper, TriangleMesh* lower); + void slice(const std::vector &z, std::vector* layers) const; + void slice(const std::vector &z, std::vector* layers) const; + void slice_facet(float slice_z, const stl_facet &facet, const int &facet_idx, + const float &min_z, const float &max_z, std::vector* lines, + boost::mutex* lines_mutex = NULL) const; + void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; private: typedef std::vector< std::vector > t_facets_edges; t_facets_edges facets_edges; stl_vertex* v_scaled_shared; - void make_loops(std::vector &lines, Polygons* loops); - void make_expolygons(const Polygons &loops, ExPolygons* slices); - void make_expolygons_simple(std::vector &lines, ExPolygons* slices); - void make_expolygons(std::vector &lines, ExPolygons* slices); + void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; + void _make_loops_do(size_t i, std::vector* lines, std::vector* layers) const; + void make_loops(std::vector &lines, Polygons* loops) const; + void make_expolygons(const Polygons &loops, ExPolygons* slices) const; + void make_expolygons_simple(std::vector &lines, ExPolygons* slices) const; + void make_expolygons(std::vector &lines, ExPolygons* slices) const; }; } diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp new file mode 100644 index 000000000..ca2125b06 --- /dev/null +++ b/xs/src/libslic3r/Utils.hpp @@ -0,0 +1,10 @@ +#ifndef slic3r_Utils_hpp_ +#define slic3r_Utils_hpp_ + +namespace Slic3r { + +extern void set_logging_level(unsigned int level); + +} // namespace Slic3r + +#endif // slic3r_Utils_hpp_ diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 5aa7866b8..d2a3c7c86 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -4,10 +4,14 @@ // this needs to be included early for MSVC (listing it in Build.PL is not enough) #include #include +#include +#include #include #include #include #include +#include +#include #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" #define SLIC3R_VERSION "1.31.6" @@ -40,13 +44,6 @@ typedef long coord_t; typedef double coordf_t; -namespace Slic3r { - -enum Axis { X=0, Y, Z }; - -} -using namespace Slic3r; - /* Implementation of CONFESS("foo"): */ #ifdef _MSC_VER #define CONFESS(...) confess_at(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) @@ -91,4 +88,55 @@ inline std::string debug_out_path(const char *name, ...) // Write slices as SVG images into out directory during the 2D processing of the slices. // #define SLIC3R_DEBUG_SLICE_PROCESSING +namespace Slic3r { + +enum Axis { X=0, Y, Z }; + +template +inline void append_to(std::vector &dst, const std::vector &src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + +template void +_parallelize_do(std::queue* queue, boost::mutex* queue_mutex, boost::function func) +{ + //std::cout << "THREAD STARTED: " << boost::this_thread::get_id() << std::endl; + while (true) { + T i; + { + boost::lock_guard l(*queue_mutex); + if (queue->empty()) return; + i = queue->front(); + queue->pop(); + } + //std::cout << " Thread " << boost::this_thread::get_id() << " processing item " << i << std::endl; + func(i); + boost::this_thread::interruption_point(); + } +} + +template void +parallelize(std::queue queue, boost::function func, + int threads_count = boost::thread::hardware_concurrency()) +{ + if (threads_count == 0) threads_count = 2; + boost::mutex queue_mutex; + boost::thread_group workers; + for (int i = 0; i < std::min(threads_count, int(queue.size())); ++ i) + workers.add_thread(new boost::thread(&_parallelize_do, &queue, &queue_mutex, func)); + workers.join_all(); +} + +template void +parallelize(T start, T end, boost::function func, + int threads_count = boost::thread::hardware_concurrency()) +{ + std::queue queue; + for (T i = start; i <= end; ++i) queue.push(i); + parallelize(queue, func, threads_count); +} + +} // namespace Slic3r + #endif diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 3f340e04a..b66d58489 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,3 +1,30 @@ +#include +#include +#include + +namespace Slic3r { + +static boost::log::trivial::severity_level logSeverity = boost::log::trivial::fatal; + +void set_logging_level(unsigned int level) +{ + switch (level) { + case 0: logSeverity = boost::log::trivial::fatal; break; + case 1: logSeverity = boost::log::trivial::error; break; + case 2: logSeverity = boost::log::trivial::warning; break; + case 3: logSeverity = boost::log::trivial::info; break; + case 4: logSeverity = boost::log::trivial::debug; break; + default: logSeverity = boost::log::trivial::trace; break; + } + + boost::log::core::get()->set_filter + ( + boost::log::trivial::severity >= logSeverity + ); +} + +} // namespace Slic3r + #ifdef SLIC3R_HAS_BROKEN_CROAK // Some Strawberry Perl builds (mainly the latest 64bit builds) have a broken mechanism diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 1544a2608..cae956fe3 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -532,8 +532,7 @@ SV* polynode2perl(const ClipperLib::PolyNode& node) { HV* hv = newHV(); - Slic3r::Polygon p; - ClipperPath_to_Slic3rMultiPoint(node.Contour, &p); + Slic3r::Polygon p = ClipperPath_to_Slic3rPolygon(node.Contour); if (node.IsHole()) { (void)hv_stores( hv, "hole", Slic3r::perl_to_SV_clone_ref(p) ); } else { diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index 6b50bc2e4..79abc2f54 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -31,6 +31,7 @@ #include #include #include +#include #ifdef SLIC3RXS extern "C" { @@ -42,15 +43,18 @@ extern "C" { #undef do_close #undef bind #undef seed +#undef push +#undef pop #ifdef _MSC_VER // Undef some of the macros set by Perl , which cause compilation errors on Win32 - #undef send #undef connect + #undef seek + #undef send + #undef write #endif /* _MSC_VER */ } #endif -#include #include #include #include @@ -163,4 +167,6 @@ SV* polynode2perl(const ClipperLib::PolyNode& node); #endif #endif +using namespace Slic3r; + #endif diff --git a/xs/t/11_clipper.t b/xs/t/11_clipper.t index 8709c0108..321d3048c 100644 --- a/xs/t/11_clipper.t +++ b/xs/t/11_clipper.t @@ -5,7 +5,7 @@ use warnings; use List::Util qw(sum); use Slic3r::XS; -use Test::More tests => 23; +use Test::More tests => 16; my $square = Slic3r::Polygon->new( # ccw [200, 100], @@ -121,41 +121,6 @@ if (0) { # Clipper does not preserve polyline orientation is_deeply $result->[0]->pp, [[200,150], [100,150]], 'clipped line orientation is preserved'; } -if (0) { # Clipper does not preserve polyline orientation - my $result = Slic3r::Geometry::Clipper::intersection_ppl([$hole_in_square], [$square]); - is_deeply $result->[0]->pp, $hole_in_square->split_at_first_point->pp, - 'intersection_ppl - clipping cw polygon as polyline preserves winding order'; -} - -{ - my $square2 = $square->clone; - $square2->translate(50,50); - { - my $result = Slic3r::Geometry::Clipper::intersection_ppl([$square2], [$square]); - is scalar(@$result), 1, 'intersection_ppl - result contains a single line'; - is scalar(@{$result->[0]}), 3, 'intersection_ppl - result contains expected number of points'; - # Clipper does not preserve polyline orientation so we only check the middle point - ###ok $result->[0][0]->coincides_with(Slic3r::Point->new(150,200)), 'intersection_ppl - expected point order'; - ok $result->[0][1]->coincides_with(Slic3r::Point->new(150,150)), 'intersection_ppl - expected point order'; - ###ok $result->[0][2]->coincides_with(Slic3r::Point->new(200,150)), 'intersection_ppl - expected point order'; - } -} - -{ - my $square2 = $square->clone; - $square2->reverse; - $square2->translate(50,50); - { - my $result = Slic3r::Geometry::Clipper::intersection_ppl([$square2], [$square]); - is scalar(@$result), 1, 'intersection_ppl - result contains a single line'; - is scalar(@{$result->[0]}), 3, 'intersection_ppl - result contains expected number of points'; - # Clipper does not preserve polyline orientation so we only check the middle point - ###ok $result->[0][0]->coincides_with(Slic3r::Point->new(200,150)), 'intersection_ppl - expected point order'; - ok $result->[0][1]->coincides_with(Slic3r::Point->new(150,150)), 'intersection_ppl - expected point order'; - ###ok $result->[0][2]->coincides_with(Slic3r::Point->new(150,200)), 'intersection_ppl - expected point order'; - } -} - { # Clipper bug #96 (our issue #2028) my $subject = Slic3r::Polyline->new( @@ -168,17 +133,6 @@ if (0) { # Clipper does not preserve polyline orientation is scalar(@$result), 1, 'intersection_pl - result is not empty'; } -{ - my $subject = Slic3r::Polygon->new( - [44730000,31936670],[55270000,31936670],[55270000,25270000],[74730000,25270000],[74730000,44730000],[68063296,44730000],[68063296,55270000],[74730000,55270000],[74730000,74730000],[55270000,74730000],[55270000,68063296],[44730000,68063296],[44730000,74730000],[25270000,74730000],[25270000,55270000],[31936670,55270000],[31936670,44730000],[25270000,44730000],[25270000,25270000],[44730000,25270000] - ); - my $clip = [ - Slic3r::Polygon->new([75200000,45200000],[54800000,45200000],[54800000,24800000],[75200000,24800000]), - ]; - my $result = Slic3r::Geometry::Clipper::intersection_ppl([$subject], $clip); - is scalar(@$result), 1, 'intersection_ppl - result is not empty'; -} - { # Clipper bug #122 my $subject = [ diff --git a/xs/xsp/BoundingBox.xsp b/xs/xsp/BoundingBox.xsp index f6e35e0df..aec5be564 100644 --- a/xs/xsp/BoundingBox.xsp +++ b/xs/xsp/BoundingBox.xsp @@ -30,7 +30,8 @@ long y_min() %code{% RETVAL = THIS->min.y; %}; long y_max() %code{% RETVAL = THIS->max.y; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%ld,%ld;%ld,%ld", THIS->min.x, THIS->min.y, THIS->max.x, THIS->max.y); RETVAL = buf; %}; - + bool defined() %code{% RETVAL = THIS->defined; %}; + %{ BoundingBox* @@ -69,7 +70,8 @@ new_from_points(CLASS, points) void set_y_min(double val) %code{% THIS->min.y = val; %}; void set_y_max(double val) %code{% THIS->max.y = val; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf;%lf,%lf", THIS->min.x, THIS->min.y, THIS->max.x, THIS->max.y); RETVAL = buf; %}; - + bool defined() %code{% RETVAL = THIS->defined; %}; + %{ BoundingBoxf* @@ -106,4 +108,5 @@ new_from_points(CLASS, points) double z_min() %code{% RETVAL = THIS->min.z; %}; double z_max() %code{% RETVAL = THIS->max.z; %}; std::string serialize() %code{% char buf[2048]; sprintf(buf, "%lf,%lf,%lf;%lf,%lf,%lf", THIS->min.x, THIS->min.y, THIS->min.z, THIS->max.x, THIS->max.y, THIS->max.z); RETVAL = buf; %}; + bool defined() %code{% RETVAL = THIS->defined; %}; }; diff --git a/xs/xsp/Clipper.xsp b/xs/xsp/Clipper.xsp index 7a33ea0c4..7457c8540 100644 --- a/xs/xsp/Clipper.xsp +++ b/xs/xsp/Clipper.xsp @@ -16,58 +16,53 @@ _constant() JT_MITER = jtMiter JT_ROUND = jtRound JT_SQUARE = jtSquare - CLIPPER_OFFSET_SCALE = CLIPPER_OFFSET_SCALE CODE: RETVAL = ix; OUTPUT: RETVAL Polygons -offset(polygons, delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta - double scale ClipperLib::JoinType joinType double miterLimit CODE: - offset(polygons, &RETVAL, delta, scale, joinType, miterLimit); + RETVAL = offset(polygons, delta, joinType, miterLimit); OUTPUT: RETVAL ExPolygons -offset_ex(polygons, delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset_ex(polygons, delta, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta - double scale ClipperLib::JoinType joinType double miterLimit CODE: - offset(polygons, &RETVAL, delta, scale, joinType, miterLimit); + RETVAL = offset_ex(polygons, delta, joinType, miterLimit); OUTPUT: RETVAL Polygons -offset2(polygons, delta1, delta2, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset2(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 - double scale ClipperLib::JoinType joinType double miterLimit CODE: - offset2(polygons, &RETVAL, delta1, delta2, scale, joinType, miterLimit); + RETVAL = offset2(polygons, delta1, delta2, joinType, miterLimit); OUTPUT: RETVAL ExPolygons -offset2_ex(polygons, delta1, delta2, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) +offset2_ex(polygons, delta1, delta2, joinType = ClipperLib::jtMiter, miterLimit = 3) Polygons polygons const float delta1 const float delta2 - double scale ClipperLib::JoinType joinType double miterLimit CODE: - offset2(polygons, &RETVAL, delta1, delta2, scale, joinType, miterLimit); + RETVAL = offset2_ex(polygons, delta1, delta2, joinType, miterLimit); OUTPUT: RETVAL @@ -77,7 +72,7 @@ diff(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - diff(subject, clip, &RETVAL, safety_offset); + RETVAL = diff(subject, clip, safety_offset); OUTPUT: RETVAL @@ -87,7 +82,7 @@ diff_ex(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - diff(subject, clip, &RETVAL, safety_offset); + RETVAL = diff_ex(subject, clip, safety_offset); OUTPUT: RETVAL @@ -96,16 +91,7 @@ diff_pl(subject, clip) Polylines subject Polygons clip CODE: - diff(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -Polylines -diff_ppl(subject, clip) - Polygons subject - Polygons clip - CODE: - diff(subject, clip, &RETVAL); + RETVAL = diff_pl(subject, clip); OUTPUT: RETVAL @@ -115,7 +101,7 @@ intersection(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - intersection(subject, clip, &RETVAL, safety_offset); + RETVAL = intersection(subject, clip, safety_offset); OUTPUT: RETVAL @@ -125,7 +111,7 @@ intersection_ex(subject, clip, safety_offset = false) Polygons clip bool safety_offset CODE: - intersection(subject, clip, &RETVAL, safety_offset); + RETVAL = intersection_ex(subject, clip, safety_offset); OUTPUT: RETVAL @@ -134,26 +120,7 @@ intersection_pl(subject, clip) Polylines subject Polygons clip CODE: - intersection(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -Polylines -intersection_ppl(subject, clip) - Polygons subject - Polygons clip - CODE: - intersection(subject, clip, &RETVAL); - OUTPUT: - RETVAL - -ExPolygons -xor_ex(subject, clip, safety_offset = false) - Polygons subject - Polygons clip - bool safety_offset - CODE: - xor_(subject, clip, &RETVAL, safety_offset); + RETVAL = intersection_pl(subject, clip); OUTPUT: RETVAL @@ -162,7 +129,7 @@ union(subject, safety_offset = false) Polygons subject bool safety_offset CODE: - union_(subject, &RETVAL, safety_offset); + RETVAL = union_(subject, safety_offset); OUTPUT: RETVAL @@ -171,20 +138,7 @@ union_ex(subject, safety_offset = false) Polygons subject bool safety_offset CODE: - union_(subject, &RETVAL, safety_offset); - OUTPUT: - RETVAL - -SV* -union_pt(subject, safety_offset = false) - Polygons subject - bool safety_offset - CODE: - // perform operation - ClipperLib::PolyTree polytree; - union_pt(subject, &polytree, safety_offset); - - RETVAL = polynode_children_2_perl(polytree); + RETVAL = union_ex(subject, safety_offset); OUTPUT: RETVAL @@ -193,7 +147,7 @@ union_pt_chained(subject, safety_offset = false) Polygons subject bool safety_offset CODE: - union_pt_chained(subject, &RETVAL, safety_offset); + RETVAL = union_pt_chained(subject, safety_offset); OUTPUT: RETVAL @@ -201,7 +155,7 @@ Polygons simplify_polygons(subject) Polygons subject CODE: - simplify_polygons(subject, &RETVAL); + RETVAL = simplify_polygons(subject); OUTPUT: RETVAL diff --git a/xs/xsp/Polyline.xsp b/xs/xsp/Polyline.xsp index 31bd4045e..60d7c6aca 100644 --- a/xs/xsp/Polyline.xsp +++ b/xs/xsp/Polyline.xsp @@ -80,13 +80,12 @@ Polyline::rotate(angle, center_sv) THIS->rotate(angle, center); Polygons -Polyline::grow(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtSquare, miterLimit = 3) +Polyline::grow(delta, joinType = ClipperLib::jtSquare, miterLimit = 3) const float delta - double scale ClipperLib::JoinType joinType double miterLimit CODE: - offset(*THIS, &RETVAL, delta, scale, joinType, miterLimit); + RETVAL = offset(*THIS, delta, joinType, miterLimit); OUTPUT: RETVAL diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index ad90eb32e..d2fca6105 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/Print.hpp" +#include "libslic3r/Slicing.hpp" #include "libslic3r/PlaceholderParser.hpp" %} @@ -58,6 +59,8 @@ _constant() Points copies(); t_layer_height_ranges layer_height_ranges() %code%{ RETVAL = THIS->layer_height_ranges; %}; + std::vector layer_height_profile() + %code%{ RETVAL = THIS->layer_height_profile; %}; Ref size() %code%{ RETVAL = &THIS->size; %}; Clone bounding_box(); @@ -82,6 +85,8 @@ _constant() bool reload_model_instances(); void set_layer_height_ranges(t_layer_height_ranges layer_height_ranges) %code%{ THIS->layer_height_ranges = layer_height_ranges; %}; + void set_layer_height_profile(std::vector profile) + %code%{ THIS->layer_height_profile = profile; %}; size_t total_layer_count(); size_t layer_count(); @@ -106,11 +111,32 @@ _constant() %code%{ THIS->state.set_done(step); %}; void set_step_started(PrintObjectStep step) %code%{ THIS->state.set_started(step); %}; - + + void _slice(); void detect_surfaces_type(); void process_external_surfaces(); void discover_vertical_shells(); void bridge_over_infill(); + void _make_perimeters(); + void _infill(); + void _generate_support_material(); + + void adjust_layer_height_profile(coordf_t z, coordf_t layer_thickness_delta, coordf_t band_width, int action) + %code%{ + THIS->update_layer_height_profile(); + adjust_layer_height_profile( + THIS->slicing_parameters(), THIS->layer_height_profile, z, layer_thickness_delta, band_width, LayerHeightEditActionType(action)); + %}; + + int generate_layer_height_texture(void *data, int rows, int cols, bool level_of_detail_2nd_level = true) + %code%{ + THIS->update_layer_height_profile(); + SlicingParameters slicing_params = THIS->slicing_parameters(); + RETVAL = generate_layer_height_texture( + slicing_params, + generate_object_layers(slicing_params, THIS->layer_height_profile), + data, rows, cols, level_of_detail_2nd_level); + %}; int ptr() %code%{ RETVAL = (int)(intptr_t)THIS; %}; diff --git a/xs/xsp/SupportMaterial.xsp b/xs/xsp/SupportMaterial.xsp deleted file mode 100644 index 01a53efa7..000000000 --- a/xs/xsp/SupportMaterial.xsp +++ /dev/null @@ -1,26 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/SupportMaterial.hpp" -%} - -%name{Slic3r::Print::SupportMaterial2} class PrintObjectSupportMaterial { - PrintObjectSupportMaterial(PrintObject *print_object); - ~PrintObjectSupportMaterial(); - - void generate(PrintObject *object) - %code{% THIS->generate(*object); %}; -}; - -%package{Slic3r::Print::SupportMaterial}; -%{ - -SV* -MARGIN() - PROTOTYPE: - CODE: - RETVAL = newSVnv(SUPPORT_MATERIAL_MARGIN); - OUTPUT: RETVAL - -%} diff --git a/xs/xsp/Surface.xsp b/xs/xsp/Surface.xsp index 597ed1b5d..379774f0a 100644 --- a/xs/xsp/Surface.xsp +++ b/xs/xsp/Surface.xsp @@ -83,13 +83,12 @@ Surface::polygons() RETVAL Surfaces -Surface::offset(delta, scale = CLIPPER_OFFSET_SCALE, joinType = ClipperLib::jtMiter, miterLimit = 3) +Surface::offset(delta, joinType = ClipperLib::jtMiter, miterLimit = 3) const float delta - double scale ClipperLib::JoinType joinType double miterLimit CODE: - offset(*THIS, &RETVAL, delta, scale, joinType, miterLimit); + surfaces_append(RETVAL, offset_ex(THIS->expolygon, delta, joinType, miterLimit), *THIS); OUTPUT: RETVAL diff --git a/xs/xsp/XS.xsp b/xs/xsp/XS.xsp index c324396b2..20c9d4154 100644 --- a/xs/xsp/XS.xsp +++ b/xs/xsp/XS.xsp @@ -2,6 +2,7 @@ %package{Slic3r::XS}; #include +#include "Utils.hpp" %{ @@ -28,6 +29,12 @@ FORK_NAME() RETVAL = newSVpv(SLIC3R_FORK_NAME, 0); OUTPUT: RETVAL +void +set_logging_level(level) + unsigned int level; + CODE: + Slic3r::set_logging_level(level); + void xspp_test_croak_hangs_on_strawberry() CODE: