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..374544dfe 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -53,10 +53,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 +87,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 +138,11 @@ 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->reset_objects; @@ -178,6 +191,22 @@ sub new { return $self; } +sub Destroy { + my ($self) = @_; + $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; +} + sub mouse_event { my ($self, $e) = @_; @@ -192,40 +221,65 @@ sub mouse_event { # 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); - - 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->_layer_height_edited(0); + 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. + my ($cw, $ch) = $self->GetSizeWH; + my $bar_width = 70; + if ($e->GetX >= $cw - $bar_width) { + # Start editing the layer height. + $self->_layer_height_edited(1); + my $z = unscale($self->{print}->get_object($object_idx_selected)->size->z) * ($ch - $e->GetY - 1.) / ($ch - 1); +# print "Modifying height profile at $z\n"; +# $self->{print}->get_object($object_idx_selected)->adjust_layer_height_profile($z, $e->RightDown ? - 0.05 : 0.05, 2., 0); + $self->{print}->get_object($object_idx_selected)->generate_layer_height_texture( + $self->volumes->[$object_idx_selected]->layer_height_texture_data->ptr, + $self->{layer_preview_z_texture_height}, + $self->{layer_preview_z_texture_width}); + $self->Refresh; } } - - $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; + + if (! $self->_layer_height_edited) { + # 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) { + 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 +304,22 @@ sub mouse_event { $self->_dragged(1); $self->Refresh; } elsif ($e->Dragging) { - if ($e->LeftIsDown) { + if ($self->_layer_height_edited) { + 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. + my ($cw, $ch) = $self->GetSizeWH; + my $z = unscale($self->{print}->get_object($object_idx_selected)->size->z) * ($ch - $e->GetY - 1.) / ($ch - 1); +# print "Modifying height profile at $z\n"; + my $strength = 0.005; + $self->{print}->get_object($object_idx_selected)->adjust_layer_height_profile($z, $e->RightIsDown ? - $strength : $strength, 2., $e->ShiftDown ? 1 : 0); + $self->{print}->get_object($object_idx_selected)->generate_layer_height_texture( + $self->volumes->[$object_idx_selected]->layer_height_texture_data->ptr, + $self->{layer_preview_z_texture_height}, + $self->{layer_preview_z_texture_width}); + $self->Refresh; + } + } 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,6 +374,7 @@ sub mouse_event { $self->_drag_start_pos(undef); $self->_drag_start_xy(undef); $self->_dragged(undef); + $self->_layer_height_edited(undef); } elsif ($e->Moving) { $self->_mouse_pos($pos); $self->Refresh; @@ -720,7 +790,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 +846,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 +890,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 +925,7 @@ sub Render { glFlush(); glFinish(); glEnable(GL_LIGHTING); + glEnable(GL_MULTISAMPLE); } # draw fixed background @@ -928,7 +1025,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,6 +1044,8 @@ sub Render { glEnable(GL_CULL_FACE); glDisable(GL_BLEND); } + + $self->draw_active_object_annotations; glFlush(); @@ -963,10 +1062,58 @@ 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'); + die if ! defined($z_to_texture_row_id); + die if ! defined($z_texture_row_to_normalized_id); + die if ! defined($z_cursor_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); + 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 +1164,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 +1206,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 +1327,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)))) + 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 +1440,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 +1463,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 +1508,6 @@ __PACKAGE__->mk_accessors(qw( color_by select_by drag_by - volumes_by_object - _objects_by_volumes )); sub new { @@ -1133,14 +1517,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 +1534,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 +1569,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 +1588,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 +1938,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/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/Object.pm b/lib/Slic3r/Print/Object.pm index 8fd2bf4cf..720294cd0 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 { @@ -863,7 +486,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 +516,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..b4caffc7d 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); @@ -299,9 +299,8 @@ sub contact_area { offset( $diff, $_, - CLIPPER_OFFSET_SCALE, JT_ROUND, - scale(0.05)*CLIPPER_OFFSET_SCALE), + scale(0.05)), $slices_margin ); } @@ -584,9 +583,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. } diff --git a/xs/Build.PL b/xs/Build.PL index 0a700c37a..51d1a014f 100644 --- a/xs/Build.PL +++ b/xs/Build.PL @@ -119,15 +119,19 @@ if (defined $ENV{BOOST_LIBRARYDIR}) { my $have_boost = 0; 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,7 +142,6 @@ 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] =~ /\Q${lib_prefix}system\E([^.]*)\Q$lib_ext\E$/) { # Suffix contains the version number, the build type etc. my $suffix = $1; @@ -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..8ea48192b 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 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 <stdio.h> #include <stdint.h> #include <stddef.h> -#include <boost/predef/detail/endian_compat.h> +#include <boost/detail/endian.hpp> #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<PointClass>::overlap(const BoundingBoxBase<PointClass> &other) c template bool BoundingBoxBase<Point>::overlap(const BoundingBoxBase<Point> &point) const; template bool BoundingBoxBase<Pointf>::overlap(const BoundingBoxBase<Pointf> &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<Point> 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<Point>() {}; BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase<Point>(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<double> 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 f2cc7eb38..7625cba49 100644 --- a/xs/src/libslic3r/ClipperUtils.cpp +++ b/xs/src/libslic3r/ClipperUtils.cpp @@ -9,65 +9,157 @@ #include <Shiny/Shiny.h> +// 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 <class T> -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<Slic3r::Polygon>(const ClipperLib::Path &input, Slic3r::Polygon* output); - -template <class T> -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 <class T> -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); @@ -318,47 +283,22 @@ void offset(const Slic3r::ExPolygon &expolygon, ClipperLib::Paths* retval, const 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); -} - -Slic3r::Polygons offset(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::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; + unscaleClipperPolygons(output); + return output; } // 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) +ClipperLib::Paths _offset(const Slic3r::ExPolygons &expolygons, const float delta, + ClipperLib::JoinType joinType, double miterLimit) { // printf("new ExPolygon offset\n"); - const float delta_scaled = float(delta * scale); + const float delta_scaled = delta * float(CLIPPER_OFFSET_SCALE); ClipperLib::Paths contours; ClipperLib::Paths holes; contours.reserve(expolygons.size()); @@ -372,12 +312,11 @@ void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, con for (Slic3r::ExPolygons::const_iterator it_expoly = expolygons.begin(); it_expoly != expolygons.end(); ++ it_expoly) { // 1) Offset the outer contour. { - 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); @@ -389,12 +328,11 @@ void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, con // 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::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); @@ -412,74 +350,23 @@ void offset(const Slic3r::ExPolygons &expolygons, ClipperLib::Paths* retval, con 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::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 +379,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 <class T> -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 +437,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 +463,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<ClipperLib::Paths>(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<ClipperLib::PolyTree>(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<ClipperLib::Paths>(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<ClipperLib::PolyTree>(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 <class SubjectType, class ResultType> -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<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -template void diff<Slic3r::Polygons, Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -template void diff<Slic3r::Polygons, Slic3r::Polylines>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void diff<Slic3r::Polylines, Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void diff<Slic3r::Lines, Slic3r::Lines>(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); - -template <class SubjectType, class ResultType> -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<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); - -template <class ResultType> -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 <class SubjectType, class ClipType> -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<Slic3r::Polygons, Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::ExPolygons diff_ex<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); -template Slic3r::ExPolygons diff_ex<Slic3r::ExPolygons, Slic3r::ExPolygons>(const Slic3r::ExPolygons &subject, const Slic3r::ExPolygons &clip, bool safety_offset_); - -template <class SubjectType, class ResultType> -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<Slic3r::Polygons, Slic3r::ExPolygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::ExPolygons* retval, bool safety_offset_); -template void intersection<Slic3r::Polygons, Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polygons* retval, bool safety_offset_); -template void intersection<Slic3r::Polygons, Slic3r::Polylines>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void intersection<Slic3r::Polylines, Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, Slic3r::Polylines* retval, bool safety_offset_); -template void intersection<Slic3r::Lines, Slic3r::Lines>(const Slic3r::Lines &subject, const Slic3r::Polygons &clip, Slic3r::Lines* retval, bool safety_offset_); - -template <class SubjectType> -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<Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::Polylines intersection<Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template Slic3r::Lines intersection<Slic3r::Lines>(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 <class SubjectType> -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<Slic3r::Polygons>(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template bool intersects<Slic3r::Polylines>(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip, bool safety_offset_); -template bool intersects<Slic3r::Lines>(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 <class T> -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_<Slic3r::ExPolygons>(const Slic3r::Polygons &subject, Slic3r::ExPolygons* retval, bool safety_offset_); -template void union_<Slic3r::Polygons>(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::PolyTree>(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::PolyTree>(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 +604,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 +627,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 +649,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,43 +657,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) { - ClipperLib::Paths input; - Slic3rMultiPoints_to_ClipperPaths(polygons, &input); // init Clipper ClipperLib::Clipper clipper; clipper.Clear(); // perform union - clipper.AddPaths(input, ClipperLib::ptSubject, true); + 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(Polygon()); - ClipperPath_to_Slic3rMultiPoint(polytree.Childs[i]->Contour, &out.back()); - } + for (int i = 0; i < polytree.ChildCount(); ++i) + out.push_back(ClipperPath_to_Slic3rPolygon(polytree.Childs[i]->Contour)); return out; } -} // namespace Slic3r \ No newline at end of file +} \ No newline at end of file diff --git a/xs/src/libslic3r/ClipperUtils.hpp b/xs/src/libslic3r/ClipperUtils.hpp index 188867fe5..d6060386a 100644 --- a/xs/src/libslic3r/ClipperUtils.hpp +++ b/xs/src/libslic3r/ClipperUtils.hpp @@ -14,151 +14,197 @@ 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 <class T> -void Slic3rMultiPoints_to_ClipperPaths(const T &input, ClipperLib::Paths* output); -template <class T> -void ClipperPath_to_Slic3rMultiPoint(const ClipperLib::Path &input, T* output); -template <class T> -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 <class T> -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 <class SubjectType, class ResultType> -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 <class SubjectType, class ResultType> -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 <class SubjectType, class ClipType> -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 <class SubjectType, class ResultType> -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 <class SubjectType> -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 <class SubjectType> -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 <class T> -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); @@ -166,4 +212,4 @@ 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 <assert.h> #include <map> #include <climits> #include <cstdio> @@ -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..4d1432722 100644 --- a/xs/src/libslic3r/EdgeGrid.cpp +++ b/xs/src/libslic3r/EdgeGrid.cpp @@ -1,6 +1,7 @@ #include <algorithm> #include <vector> #include <float.h> +#include <unordered_map> #ifdef SLIC3R_GUI #include <wx/image.h> @@ -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) @@ -1118,7 +1118,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 +1222,114 @@ bool EdgeGrid::Grid::signed_distance(const Point &pt, coord_t search_radius, coo return true; } +Polygons EdgeGrid::Grid::contours_simplified() const +{ + typedef std::unordered_multimap<Point, int, PointHash> EndPointMapType; + // 1) Collect the lines. + std::vector<Line> 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) { + bool left = cell_inside_or_crossing(r , c-1); + bool top = cell_inside_or_crossing(r-1, c ); + bool current = cell_inside_or_crossing(r , c ); + 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<Point, int>(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<Point, int>(lines.back().a, int(lines.size()) - 1)); + } + } + } + + // 2) Chain the lines. + std::vector<char> 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<EndPointMapType::iterator,EndPointMapType::iterator> 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()); + coord_t downscale = 5; + 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) ? downscale : -downscale; + p.x += (v.y > 0) ? downscale : -downscale; + 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) { diff --git a/xs/src/libslic3r/EdgeGrid.hpp b/xs/src/libslic3r/EdgeGrid.hpp index 626a430cd..2473ce423 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() 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<Polygons,Polygons>(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<TPPLPoly> 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 7f76cfe4e..7d1a609ea 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -293,37 +293,37 @@ extern bool remove_sticks(ExPolygon &poly); #include <boost/polygon/polygon.hpp> namespace boost { namespace polygon { template <> - struct polygon_traits<ExPolygon> { + struct polygon_traits<Slic3r::ExPolygon> { 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<ExPolygon> { + struct polygon_mutable_traits<Slic3r::ExPolygon> { //expects stl style iterators template <typename iT> - 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(); @@ -333,27 +333,27 @@ namespace boost { namespace polygon { template <> - struct geometry_concept<ExPolygon> { typedef polygon_with_holes_concept type; }; + struct geometry_concept<Slic3r::ExPolygon> { typedef polygon_with_holes_concept type; }; template <> - struct polygon_with_holes_traits<ExPolygon> { - 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<Slic3r::ExPolygon> { + 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<ExPolygon> { + struct polygon_with_holes_mutable_traits<Slic3r::ExPolygon> { template <typename iT> - 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; } @@ -361,32 +361,32 @@ namespace boost { namespace polygon { //first we register CPolygonSet as a polygon set template <> - struct geometry_concept<ExPolygons> { typedef polygon_set_concept type; }; + struct geometry_concept<Slic3r::ExPolygons> { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> - struct polygon_set_traits<ExPolygons> { + struct polygon_set_traits<Slic3r::ExPolygons> { 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<ExPolygons> { + struct polygon_set_mutable_traits<Slic3r::ExPolygons> { template <typename input_iterator_type> - 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<Polylines,Polylines>(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<Polylines,Polylines>(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 7ce053a10..dfc28d035 100644 --- a/xs/src/libslic3r/ExtrusionEntity.hpp +++ b/xs/src/libslic3r/ExtrusionEntity.hpp @@ -194,6 +194,26 @@ 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()); 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/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 766f389b0..3f7232e06 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. 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..929460169 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<unsigned short,Surfaces>::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()); } } } diff --git a/xs/src/libslic3r/Layer.hpp b/xs/src/libslic3r/Layer.hpp index e59f0c9e1..a5ba3f804 100644 --- a/xs/src/libslic3r/Layer.hpp +++ b/xs/src/libslic3r/Layer.hpp @@ -11,9 +11,6 @@ namespace Slic3r { -typedef std::pair<coordf_t,coordf_t> t_layer_height_range; -typedef std::map<t_layer_height_range,coordf_t> t_layer_height_ranges; - class Layer; class PrintRegion; class PrintObject; diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index 7c84629e2..f0063114a 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()); 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 <boost/polygon/polygon.hpp> namespace boost { namespace polygon { template <> - struct geometry_concept<Line> { typedef segment_concept type; }; + struct geometry_concept<Slic3r::Line> { typedef segment_concept type; }; template <> - struct segment_traits<Line> { + struct segment_traits<Slic3r::Line> { 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 <map> #include <string> #include <utility> 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<coord_t>()(pt.x) ^ std::hash<coord_t>()(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 <boost/version.hpp> @@ -146,28 +155,28 @@ namespace boost { namespace polygon { #endif template <> - struct geometry_concept<Point> { typedef point_concept type; }; + struct geometry_concept<Slic3r::Point> { typedef point_concept type; }; template <> - struct point_traits<Point> { + struct point_traits<Slic3r::Point> { 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<Point> { + struct point_mutable_traits<Slic3r::Point> { 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 <boost/polygon/polygon.hpp> namespace boost { namespace polygon { template <> - struct geometry_concept<Polygon>{ typedef polygon_concept type; }; + struct geometry_concept<Slic3r::Polygon>{ typedef polygon_concept type; }; template <> - struct polygon_traits<Polygon> { + struct polygon_traits<Slic3r::Polygon> { 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<Polygon> { + struct polygon_mutable_traits<Slic3r::Polygon> { // expects stl style iterators template <typename iT> - 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<Polygons> { typedef polygon_set_concept type; }; + struct geometry_concept<Slic3r::Polygons> { typedef polygon_set_concept type; }; //next we map to the concept through traits template <> - struct polygon_set_traits<Polygons> { + struct polygon_set_traits<Slic3r::Polygons> { 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<Polygons> { + struct polygon_set_mutable_traits<Slic3r::Polygons> { template <typename input_iterator_type> - 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..3583c627b 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -608,20 +608,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..f97f888cd 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<int> > 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 <z, layer_height> are packed into a 1D array to simplify handling by the Perl XS. + std::vector<coordf_t> 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,26 @@ public: bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &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(); + private: Print* _print; ModelObject* _model_object; @@ -152,6 +169,8 @@ private: // parameter PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox); ~PrintObject() {} + + std::vector<ExPolygons> _slice_region(size_t region_id, const std::vector<float> &z, bool modifier); }; typedef std::vector<PrintObject*> PrintObjectPtrs; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 59dcea879..746cf7f1d 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,4 +1,5 @@ #include "PrintConfig.hpp" +#include <boost/thread.hpp> namespace Slic3r { @@ -1290,9 +1291,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/PrintObject.cpp b/xs/src/libslic3r/PrintObject.cpp index 3808a545e..8d6ddaf21 100644 --- a/xs/src/libslic3r/PrintObject.cpp +++ b/xs/src/libslic3r/PrintObject.cpp @@ -2,10 +2,23 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" -#include "SVG.hpp" + +#include <boost/log/trivial.hpp> #include <Shiny/Shiny.h> +// #define SLIC3R_DEBUG + +// Make assert active if SLIC3R_DEBUG +#ifdef SLIC3R_DEBUG + #undef NDEBUG + #define DEBUG + #define _DEBUG + #include "SVG.hpp" + #undef assert + #include <cassert> +#endif + namespace Slic3r { PrintObject::PrintObject(Print* print, ModelObject* model_object, const BoundingBoxf3 &modobj_bbox) @@ -323,7 +336,8 @@ PrintObject::has_support_material() const // If a part of a region is of S_TYPE_BOTTOM and S_TYPE_TOP, the S_TYPE_BOTTOM wins. void PrintObject::detect_surfaces_type() { -// Slic3r::debugf "Detecting solid surfaces...\n"; + BOOST_LOG_TRIVIAL(info) << "Detecting solid surfaces..."; + for (int idx_region = 0; idx_region < this->_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 +449,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 +458,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 +483,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 +513,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) { @@ -692,8 +710,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 +722,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 @@ -754,9 +771,9 @@ PrintObject::discover_vertical_shells() // 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); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells"); @@ -775,6 +792,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 +865,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 +921,326 @@ 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<float> slice_zs; + { + this->clear_layers(); + // Object layers (pairs of bottom/top Z coordinate), without the raft. + this->update_layer_height_profile(); + std::vector<coordf_t> 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> 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> 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> 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<ExPolygons> PrintObject::_slice_region(size_t region_id, const std::vector<float> &z, bool modifier) +{ + std::vector<ExPolygons> layers; + assert(region_id < this->region_volumes.size()); + std::vector<int> &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<int>::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<Layer*>( + std::queue<Layer*>(std::deque<Layer*>(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue<Layer*> + 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<Layer*>( + std::queue<Layer*>(std::deque<Layer*>(this->layers.begin(), this->layers.end())), // cast LayerPtrs to std::queue<Layer*> + 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); +} + +} // namespace Slic3r diff --git a/xs/src/libslic3r/Slicing.cpp b/xs/src/libslic3r/Slicing.cpp new file mode 100644 index 000000000..0de781c64 --- /dev/null +++ b/xs/src/libslic3r/Slicing.cpp @@ -0,0 +1,585 @@ +#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 <cassert> +#endif + +namespace Slic3r +{ + +SlicingParameters SlicingParameters::create_from_config( + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::set<size_t> &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_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; + + 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<size_t>::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. + coordf_t print_z = first_layer_height + object_config.support_material_contact_distance.value; + if (params.raft_layers() == 1) { + params.contact_raft_layer_height = first_layer_height; + } else { + print_z += + // Number of the base raft layers is decreased by the first layer, which has already been added to print_z. + coordf_t(params.base_raft_layers - 1) * params.base_raft_layer_height + + // Number of the interface raft layers is decreased by the contact layer. + coordf_t(params.interface_raft_layers - 1) * params.interface_raft_layer_height + + params.contact_raft_layer_height; + } + 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<size_t>::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<coordf_t> 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<std::pair<t_layer_height_range,coordf_t>> 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,coordf_t>( + 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,coordf_t>(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<coordf_t> layer_height_profile; + for (std::vector<std::pair<t_layer_height_range,coordf_t>>::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<coordf_t> 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<coordf_t> 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 <typename T> +static inline T clamp(const T low, const T high, const T value) +{ + return std::max(low, std::min(high, value)); +} + +template <typename T> +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<coordf_t> &layer_height_profile, + coordf_t z, + coordf_t layer_thickness_delta, + coordf_t band_width, + int action) +{ + // Constrain the profile variability by the 1st layer height. + std::pair<coordf_t, coordf_t> z_span_variable = + std::pair<coordf_t, coordf_t>( + 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 0: + default: + 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 1: + 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; + } + + // 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<double> 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; + 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 0: + default: + height += weight * layer_thickness_delta; + break; + case 1: + { + 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; + } + } + // 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; + if (i < layer_height_profile.size()) { + if (profile_new[profile_new.size() - 2] + z_step < layer_height_profile[i]) { + profile_new.push_back(profile_new[profile_new.size() - 2] + z_step); + 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); + + 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<coordf_t> generate_object_layers( + const SlicingParameters &slicing_params, + const std::vector<coordf_t> &layer_height_profile) +{ + coordf_t print_z = 0; + coordf_t height = 0; + + std::vector<coordf_t> 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<coordf_t> &layers, + void *data, int rows, int cols, bool level_of_detail_2nd_level) +{ +// https://github.com/aschn/gnuplot-colorbrewer + std::vector<Point3> 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<unsigned char*>(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<int>(0, 255, int(floor(color.x + 0.5))); + ptr[1] = clamp<int>(0, 255, int(floor(color.y + 0.5))); + ptr[2] = clamp<int>(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<int>(0, 255, int(floor(color.x + 0.5))); + ptr[1] = clamp<int>(0, 255, int(floor(color.y + 0.5))); + ptr[2] = clamp<int>(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..349be2e4a --- /dev/null +++ b/xs/src/libslic3r/Slicing.hpp @@ -0,0 +1,112 @@ +// Based on implementation by @platsch + +#ifndef slic3r_Slicing_hpp_ +#define slic3r_Slicing_hpp_ + +#include <set> +#include <vector> + +#include "libslic3r.h" +namespace Slic3r +{ + +class PrintConfig; +class PrintObjectConfig; +class ModelVolume; +typedef std::vector<ModelVolume*> 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<size_t> &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; + + // 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; + + // 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 object_print_z_min; + coordf_t object_print_z_max; +}; + +typedef std::pair<coordf_t,coordf_t> t_layer_height_range; +typedef std::map<t_layer_height_range,coordf_t> t_layer_height_ranges; + +extern std::vector<coordf_t> layer_height_profile_from_ranges( + const SlicingParameters &slicing_params, + const t_layer_height_ranges &layer_height_ranges); + +extern std::vector<coordf_t> layer_height_profile_adaptive( + const SlicingParameters &slicing_params, + const t_layer_height_ranges &layer_height_ranges, + const ModelVolumePtrs &volumes); + +extern void adjust_layer_height_profile( + const SlicingParameters &slicing_params, + std::vector<coordf_t> &layer_height_profile, + coordf_t z, + coordf_t layer_thickness_delta, + coordf_t band_width, + int 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<coordf_t> generate_object_layers( + const SlicingParameters &slicing_params, + const std::vector<coordf_t> &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<coordf_t> &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<float, float> face_z_span(const stl_facet *f) +{ + return std::pair<float, float>( + 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 TriangleMesh*>::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 TriangleMesh*>::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<float, float> span1 = face_z_span(f1); + std::pair<float, float> 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<float, float> 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<float, float> 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<float, float> 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<float>(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<const TriangleMesh*> m_meshes; + // Collected faces of all meshes, sorted by raising Z of the bottom most face. + std::vector<const stl_facet*> m_faces; + // Z component of face normals, normalized. + std::vector<float> 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 29b016568..8bf9e9fc7 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -5,11 +5,12 @@ #include "Print.hpp" #include "SupportMaterial.hpp" #include "Fill/FillBase.hpp" +#include "EdgeGrid.hpp" #include <cmath> -#include <cassert> #include <memory> #include <boost/log/trivial.hpp> +#include <unordered_set> // #define SLIC3R_DEBUG @@ -19,6 +20,8 @@ #include "SVG.hpp" #endif +#include <cassert> + namespace Slic3r { // Increment used to reach MARGIN in steps to avoid trespassing thin objects @@ -241,16 +244,10 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) // 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); - -#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); - } -#endif /* SLIC3R_DEBUG */ + std::vector<Polygons> layer_support_areas; + MyLayersPtr bottom_contacts = this->bottom_contact_layers_and_layer_support_areas( + object, top_contacts, layer_storage, + layer_support_areas); BOOST_LOG_TRIVIAL(info) << "Support generator - Trimming top contacts by bottom contacts"; @@ -271,7 +268,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object) BOOST_LOG_TRIVIAL(info) << "Support generator - Creating base layers"; // 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->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) { @@ -437,7 +434,8 @@ Polygons collect_slices_outer(const Layer &layer) } // Find the top contact surfaces of the support or the raft. -PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers(const PrintObject &object, MyLayerStorage &layer_storage) const +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_layers( + const PrintObject &object, MyLayerStorage &layer_storage) const { #ifdef SLIC3R_DEBUG static int iRun = 0; @@ -577,7 +575,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ // 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); + overhang_perimeters = diff_pl(overhang_perimeters, lower_grown_slices); // only consider straight overhangs // only consider overhangs having endpoints inside layer's slices @@ -590,13 +588,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ 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())) { + 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); - } + polygons_append(bridged_perimeters, offset(*it, 0.5f * w + 10.f)); } } bridged_perimeters = union_(bridged_perimeters); @@ -613,13 +607,9 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ Polygons unsupported_bridge_polygons; for (Polylines::const_iterator it = layerm.unsupported_bridge_edges.polylines.begin(); - it != layerm.unsupported_bridge_edges.polylines.end(); ++ it) { + 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(unsupported_bridge_polygons, offset(*it, scale_(SUPPORT_MATERIAL_MARGIN))); polygons_append(diff_polygons, intersection(unsupported_bridge_polygons, bridges)); } else { // just remove bridged areas @@ -664,10 +654,9 @@ 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), + scale_(0.05)), slices_margin); } } @@ -753,14 +742,188 @@ 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 +#if 0 +typedef std::unordered_set<Point,PointHash> PointHashMap; + +void fillet(Polygon &poly, PointHashMap &new_points_hash_map) { + if (poly.points.size() < 3) + // an invalid contour will not be modified. + return; + + // Flag describing a contour point. + std::vector<char> point_flag(std::vector<char>(poly.points.size(), 0)); + + // Does a point belong to new points? + for (size_t i = 0; i < poly.points.size(); ++ i) + if (new_points_hash_map.find(poly.points[i]) != new_points_hash_map.end()) + // Mark the point as from the new contour. + point_flag[i] = 1; + + // Mark the intersection points between the old and new contours. + size_t j = poly.points.size() - 1; + bool has_some = false; + for (size_t i = 0; i < poly.points.size(); j = i, ++ i) + if ((point_flag[i] ^ point_flag[j]) & 1) { + point_flag[(point_flag[i] & 1) ? j : i] |= 2; + has_some = true; + } + if (! has_some) + return; + +#ifdef SLIC3R_DEBUG + static int iRun = 0; + ++ iRun; + { + FILE *pfile = ::fopen(debug_out_path("fillet-in-%d.bin", iRun).c_str(), "wb"); + size_t cnt = poly.points.size(); + ::fwrite(&cnt, 1, sizeof(cnt), pfile); + ::fwrite(poly.points.data(), cnt, sizeof(Point), pfile); + cnt = new_points_hash_map.size(); + ::fwrite(&cnt, 1, sizeof(cnt), pfile); + for (PointHashMap::iterator it = new_points_hash_map.begin(); it != new_points_hash_map.end(); ++ it) { + const Point &pt = *it; + ::fwrite(&pt, 1, sizeof(Point), pfile); + } + ::fclose(pfile); + } + ::Slic3r::SVG svg(debug_out_path("fillet-%d.svg", iRun), get_extents(poly)); + svg.draw(poly, "black", scale_(0.05)); + for (size_t i = 0; i < poly.points.size(); ++ i) { + const Point &pt1 = poly.points[i]; + const Point &pt2 = poly.points[(i+1)%poly.points.size()]; + if (new_points_hash_map.find(pt1) != new_points_hash_map.end()) + svg.draw(Line(pt1, pt2), "red", scale_(0.035)); + if (new_points_hash_map.find(pt1) != new_points_hash_map.end() && + new_points_hash_map.find(pt2) != new_points_hash_map.end()) + svg.draw(Line(pt1, pt2), "red", scale_(0.05)); + } +#endif + + // Mark a range of points around the intersection points. + const double rounding_range = scale_(1.5); + std::vector<Pointf> pts; + pts.reserve(poly.points.size()); + for (int i = 0; i < int(poly.points.size()); ++ i) { + if (point_flag[i] & 2) { + point_flag[i] |= 4; + // Extend a filetting span left / right from i by an Euclidian distance of rounding_range. + double d = 0.f; + const Point *pt = &poly.points[i]; + for (int j = 1; j < int(poly.points.size()); ++ j) { + int idx = (i + j) % poly.points.size(); + const Point *pt2 = &poly.points[idx]; + d += pt->distance_to(*pt2); + if (d > rounding_range) + break; + point_flag[idx] |= 4; + //pt = pt2; + } + for (int j = 1; j < int(poly.points.size()); ++ j) { + int idx = (i + int(poly.points.size()) - j) % poly.points.size(); + const Point *pt2 = &poly.points[idx]; + d += pt->distance_to(*pt2); + if (d > rounding_range) + break; + point_flag[idx] |= 4; + //pt = pt2; + } + } + pts.push_back(Pointf(poly.points[i].x, poly.points[i].y)); + } + + //FIXME avoid filetting over long edges. Insert new points into long edges at the ends of the filetting interval. + + // Perform the filetting over the marked vertices. + std::vector<Pointf> pts2(pts); + double laplacian_weight = 0.5; + for (size_t i_round = 0; i_round < 5; ++ i_round) { + for (size_t i = 0; i < int(pts.size()); ++ i) { + if (point_flag[i] & 4) { + size_t prev = (i == 0) ? pts.size() - 1 : i - 1; + size_t next = (i + 1 == pts.size()) ? 0 : i + 1; + Pointf &p0 = pts[prev]; + Pointf &p1 = pts[i]; + Pointf &p2 = pts[next]; + // Is the point reflex? + coordf_t c = cross(p1 - p0, p2 - p1); + if (c < 0) + // The point is reflex, perform Laplacian smoothing. + pts2[i] = (1. - laplacian_weight) * pts[i] + (0.5 * laplacian_weight) * (pts[prev] + pts[next]); + } + } + pts.swap(pts2); + } + + // Mark vertices representing short edges for removal. + + // Convert the filetted points back, remove points marked for removal. + j = 0; + for (size_t i = 0; i < poly.points.size(); ++ i) { + if (point_flag[i] & 8) + // Remove this point. + continue; + if (point_flag[i] & 4) + // Update the point coordinates. + poly.points[i] = Point(pts[i].x, pts[i].y); + if (j < i) + poly.points[j] = poly.points[i]; + ++ j; + } + if (j < poly.points.size()) + poly.points.erase(poly.points.begin() + j, poly.points.end()); + +#ifdef SLIC3R_DEBUG + svg.draw_outline(poly, "blue", scale_(0.025)); +#endif /* SLIC3R_DEBUG */ +} + +void fillet(Polygons &polygons, PointHashMap &new_points_hash_map) +{ + for (Polygons::iterator it = polygons.begin(); it != polygons.end(); ++ it) + fillet(*it, new_points_hash_map); +} + +void union_and_fillet(Polygons &polygons, size_t n_polygons_old) +{ + if (n_polygons_old == polygons.size()) + // No new polygons. + return; + + // Fill in the new_points hash table with points of new contours. + PointHashMap new_points; + for (size_t i = n_polygons_old; i < polygons.size(); ++ i) { + const Polygon &poly = polygons[i]; + for (size_t j = 0; j < poly.points.size(); ++ j) + new_points.insert(poly.points[j]); + } + // Merge the newly added regions. Don't use the safety offset, the offset has been added already. + polygons = union_(polygons, false); + // Fillet transition between the old and new points. + fillet(polygons, new_points); +} +#endif + +// Collect +PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_contact_layers_and_layer_support_areas( + const PrintObject &object, const MyLayersPtr &top_contacts, MyLayerStorage &layer_storage, + std::vector<Polygons> &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. @@ -768,56 +931,116 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta 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); size_t projection_size_old = projection.size(); // 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 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(projection, offset(top_contacts[contact_idx]->polygons, SCALED_EPSILON)); + polygons_append(polygons_new, offset(top_contacts[contact_idx]->polygons, SCALED_EPSILON)); + size_t size1 = polygons_new.size(); // 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(projection, offset(*top_contacts[contact_idx]->aux_polygons, SCALED_EPSILON)); + polygons_append(polygons_new, offset(*top_contacts[contact_idx]->aux_polygons, SCALED_EPSILON)); +#if 0 + union_and_fillet(polygons_new, size1); +#else + union_(polygons_new); +#endif + polygons_append(projection, std::move(polygons_new)); } if (projection.empty()) continue; - if (projection_size_old < projection.size()) { - // Merge the newly added regions. Don't use the safety offset, the offset has been added already. - projection = union_(projection, false); +#if 0 + union_and_fillet(projection, projection_size_old); +#else + union_(projection); +#endif + #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(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. // Don't use a safety offset as it has been applied during insertion of polygons. - Polygons touching = intersection(top, projection, false); - 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? - layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width())); - // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. - projection = diff(projection, touching); - } - } + 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_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? + layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width())); + #ifdef SLIC3R_DEBUG + { + ::Slic3r::SVG svg(debug_out_path("support-bottom-contacts-%d-%lf.svg", iRun, layer_new.print_z), get_extents(layer_new.polygons)); + Slic3r::ExPolygons expolys = union_ex(layer_new.polygons, false); + svg.draw(expolys); + } + #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; + coord_t grid_resolution = 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(); +#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); + } +#endif /* SLIC3R_DEBUG */ + projection = std::move(projection_simplified); + + // Remove the areas that touched from the projection that will continue on next, lower, top surfaces. + // projection = diff(projection, touching); + projection = diff(projection, to_polygons(layer.slices.expolygons), true); + layer_support_areas[layer_id] = projection; + } + std::reverse(bottom_contacts.begin(), bottom_contacts.end()); + } // ! top_contacts.empty() - std::reverse(bottom_contacts.begin(), bottom_contacts.end()); return bottom_contacts; } @@ -952,7 +1175,8 @@ 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<Polygons> &layer_support_areas) const { #ifdef SLIC3R_DEBUG static int iRun = 0; @@ -965,25 +1189,50 @@ void PrintObjectSupportMaterial::generate_base_layers( // coordf_t fillet_radius_scaled = scale_(m_object_config->support_material_spacing); int idx_top_contact_above = 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); - + + // New polygons for layer_intermediate. + Polygons polygons_new; + +#if 0 // Add polygons projected from the intermediate layer above. if (idx_intermediate + 1 < int(intermediate_layers.size())) polygons_append(polygons_new, intermediate_layers[idx_intermediate+1]->polygons); + if (idx_top_contact_above >= 0 && top_contacts[idx_top_contact_above]->print_z > layer_intermediate.print_z) { + // Contact surfaces are expanded away from the object, trimmed by the object. + // Use a slight positive offset to overlap the touching regions. + Polygons polygons_new2; + polygons_append(polygons_new2, offset(top_contacts[idx_top_contact_above]->polygons, SCALED_EPSILON)); + size_t size2 = polygons_new2.size(); + // 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_new2, offset(*top_contacts[idx_top_contact_above]->aux_polygons, SCALED_EPSILON)); + union_and_fillet(polygons_new2, size2); + if (! polygons_new2.empty()) { + size_t polygons_size_old = polygons_new.size(); + polygons_append(polygons_new, std::move(polygons_new2)); + union_and_fillet(polygons_new, polygons_size_old); + } + } +#else + // 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]; +#endif + // 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; @@ -1004,7 +1253,7 @@ void PrintObjectSupportMaterial::generate_base_layers( -- 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]; + 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); @@ -1038,9 +1287,8 @@ 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. } @@ -1063,6 +1311,9 @@ void PrintObjectSupportMaterial::generate_base_layers( size_t idx_object_layer_overlapping = 0; // For all intermediate support layers: for (MyLayersPtr::iterator it_layer = intermediate_layers.begin(); it_layer != intermediate_layers.end(); ++ it_layer) { + BOOST_LOG_TRIVIAL(trace) << "Support generator - generate_base_layers - trimmming layer " << + (it_layer - intermediate_layers.begin()) << " of " << intermediate_layers.size(); + MyLayer &layer_intermediate = *(*it_layer); if (layer_intermediate.polygons.empty()) // Empty support layer, nothing to trim. @@ -1337,10 +1588,8 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // 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) { - Polylines tmp; - tmp.push_back(it_contact->split_at_first_point()); - if (! intersection(tmp, overhang_with_margin).empty()) { + 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) { @@ -1350,7 +1599,6 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const circle_new.points[i].translate(*it_center); } } - } // Apply a pattern to the loop. loops0 = diff(external_loops, circles); } @@ -1369,7 +1617,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const 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))); + loop_lines = intersection_pl(loop_lines, offset(overhang_polygons, scale_(SUPPORT_MATERIAL_MARGIN))); } // add the contact infill area to the interface area @@ -1377,9 +1625,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const // 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 thick_loop_lines; - offset(loop_lines, &thick_loop_lines, float(circle_radius * 1.1)); - top_contact_layer.layer->polygons = diff(top_contact_layer.layer->polygons, std::move(thick_loop_lines)); + 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( @@ -1598,7 +1844,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill_polygons, - flow.scaled_spacing()); + to_infill = offset_ex(to_infill, - flow.scaled_spacing()); extrusion_entities_append_paths( support_layer.support_fills.entities, to_polylines(STDMOVE(to_infill_polygons)), diff --git a/xs/src/libslic3r/SupportMaterial.hpp b/xs/src/libslic3r/SupportMaterial.hpp index 31a7899ce..abcaf676a 100644 --- a/xs/src/libslic3r/SupportMaterial.hpp +++ b/xs/src/libslic3r/SupportMaterial.hpp @@ -163,7 +163,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<Polygons> &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; @@ -180,7 +182,8 @@ private: const PrintObject &object, const MyLayersPtr &bottom_contacts, const MyLayersPtr &top_contacts, - MyLayersPtr &intermediate_layers) const; + MyLayersPtr &intermediate_layers, + std::vector<Polygons> &layer_support_areas) const; Polygons generate_raft_base( const PrintObject &object, diff --git a/xs/src/libslic3r/SurfaceCollection.cpp b/xs/src/libslic3r/SurfaceCollection.cpp index 804190548..cfeae4ccc 100644 --- a/xs/src/libslic3r/SurfaceCollection.cpp +++ b/xs/src/libslic3r/SurfaceCollection.cpp @@ -196,19 +196,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 <cmath> -#include <queue> #include <deque> +#include <queue> #include <set> #include <vector> #include <map> @@ -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<float> &z, std::vector<Polygons>* layers) +TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers) const { /* This method gets called with a list of unscaled Z coordinates and outputs @@ -427,58 +431,66 @@ TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* 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<IntersectionLines> 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<float>::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<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) { - std::vector<float>::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<int>( + 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<IntersectionLines>::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<size_t>( + 0, + lines.size()-1, + boost::bind(&TriangleMeshSlicer::_make_loops_do, this, _1, &lines, layers) + ); +} + +void +TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex, + const std::vector<float> &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<float>::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<float>::const_iterator it = min_layer; it != max_layer + 1; ++it) { + std::vector<float>::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<float> &z, std::vector<ExPolygons>* layers) +TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* layers) const { std::vector<Polygons> layers_p; this->slice(z, &layers_p); @@ -495,7 +507,9 @@ TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<ExPolygons>* } 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<IntersectionLine>* 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<IntersectionLine>* lines, + boost::mutex* lines_mutex) const { std::vector<IntersectionPoint> points; std::vector< std::vector<IntersectionPoint>::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<boost::mutex> 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<boost::mutex> l(*lines_mutex); + lines->push_back(line); + } else { + lines->push_back(line); + } return; } } void -TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) +TriangleMeshSlicer::_make_loops_do(size_t i, std::vector<IntersectionLines>* lines, std::vector<Polygons>* layers) const +{ + this->make_loops((*lines)[i], &(*layers)[i]); +} + +void +TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const { /* SVG svg("lines.svg"); @@ -707,6 +737,7 @@ TriangleMeshSlicer::make_loops(std::vector<IntersectionLine> &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<IntersectionLine> &lines, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &lines, ExPolygons* slices) const { Polygons loops; this->make_loops(lines, &loops); @@ -780,7 +811,7 @@ TriangleMeshSlicer::make_expolygons_simple(std::vector<IntersectionLine> &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<IntersectionLine> &lines, ExPolygons* slices) +TriangleMeshSlicer::make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const { Polygons pp; this->make_loops(lines, &pp); @@ -852,7 +881,7 @@ TriangleMeshSlicer::make_expolygons(std::vector<IntersectionLine> &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 <admesh/stl.h> #include <vector> +#include <boost/thread.hpp> #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<float> &z, std::vector<Polygons>* layers); - void slice(const std::vector<float> &z, std::vector<ExPolygons>* 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<IntersectionLine>* lines) const; - void cut(float z, TriangleMesh* upper, TriangleMesh* lower); + void slice(const std::vector<float> &z, std::vector<Polygons>* layers) const; + void slice(const std::vector<float> &z, std::vector<ExPolygons>* 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<IntersectionLine>* lines, + boost::mutex* lines_mutex = NULL) const; + void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; private: typedef std::vector< std::vector<int> > t_facets_edges; t_facets_edges facets_edges; stl_vertex* v_scaled_shared; - void make_loops(std::vector<IntersectionLine> &lines, Polygons* loops); - void make_expolygons(const Polygons &loops, ExPolygons* slices); - void make_expolygons_simple(std::vector<IntersectionLine> &lines, ExPolygons* slices); - void make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices); + void _slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex, const std::vector<float> &z) const; + void _make_loops_do(size_t i, std::vector<IntersectionLines>* lines, std::vector<Polygons>* layers) const; + void make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const; + void make_expolygons(const Polygons &loops, ExPolygons* slices) const; + void make_expolygons_simple(std::vector<IntersectionLine> &lines, ExPolygons* slices) const; + void make_expolygons(std::vector<IntersectionLine> &lines, ExPolygons* slices) const; }; } diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index 5aa7866b8..89db6d81e 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 <ostream> #include <iostream> +#include <math.h> +#include <queue> #include <sstream> #include <cstdio> #include <stdint.h> #include <stdarg.h> +#include <vector> +#include <boost/thread.hpp> #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 <class T> +inline void append_to(std::vector<T> &dst, const std::vector<T> &src) +{ + dst.insert(dst.end(), src.begin(), src.end()); +} + +template <class T> void +_parallelize_do(std::queue<T>* queue, boost::mutex* queue_mutex, boost::function<void(T)> func) +{ + //std::cout << "THREAD STARTED: " << boost::this_thread::get_id() << std::endl; + while (true) { + T i; + { + boost::lock_guard<boost::mutex> 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 <class T> void +parallelize(std::queue<T> queue, boost::function<void(T)> 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 < fminf(threads_count, queue.size()); i++) + workers.add_thread(new boost::thread(&_parallelize_do<T>, &queue, &queue_mutex, func)); + workers.join_all(); +} + +template <class T> void +parallelize(T start, T end, boost::function<void(T)> func, + int threads_count = boost::thread::hardware_concurrency()) +{ + std::queue<T> 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 0f298e954..b66d58489 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,3 +1,30 @@ +#include <boost/log/core.hpp> +#include <boost/log/trivial.hpp> +#include <boost/log/expressions.hpp> + +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 @@ -66,30 +93,3 @@ confess_at(const char *file, int line, const char *func, } #endif - -#include <boost/log/core.hpp> -#include <boost/log/trivial.hpp> -#include <boost/log/expressions.hpp> - -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 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 <ostream> #include <iostream> #include <sstream> +#include <libslic3r.h> #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 <xsinit.h>, which cause compilation errors on Win32 - #undef send #undef connect + #undef seek + #undef send + #undef write #endif /* _MSC_VER */ } #endif -#include <libslic3r.h> #include <ClipperUtils.hpp> #include <Config.hpp> #include <ExPolygon.hpp> @@ -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..7461b9d91 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -3,6 +3,7 @@ %{ #include <xsinit.h> #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<double> layer_height_profile() + %code%{ RETVAL = THIS->layer_height_profile; %}; Ref<Point3> size() %code%{ RETVAL = &THIS->size; %}; Clone<BoundingBox> 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<double> profile) + %code%{ THIS->layer_height_profile = profile; %}; size_t total_layer_count(); size_t layer_count(); @@ -106,11 +111,31 @@ _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 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, 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/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