2020-10-16 09:03:49 +02:00
#include "../Layer.hpp"
#include "../GCode.hpp"
#include "../EdgeGrid.hpp"
#include "../Geometry.hpp"
#include "../ShortestPath.hpp"
#include "../Print.hpp"
#include "../Polygon.hpp"
#include "../ExPolygon.hpp"
#include "../ClipperUtils.hpp"
2020-11-16 13:43:15 +01:00
#include "../SVG.hpp"
2020-10-16 09:03:49 +02:00
#include "AvoidCrossingPerimeters.hpp"
#include <memory>
2020-11-15 19:46:17 +01:00
#include <numeric>
#include <unordered_set>
#include <tbb/parallel_for.h>
#include <boost/log/trivial.hpp>
2020-10-16 09:03:49 +02:00
namespace Slic3r {
// Create a rotation matrix for projection on the given vector
static Matrix2d rotation_by_direction ( const Point & direction )
{
Matrix2d rotation ;
rotation . block < 1 , 2 > ( 0 , 0 ) = direction . cast < double > () / direction . cast < double > (). norm ();
rotation ( 1 , 0 ) = - rotation ( 0 , 1 );
rotation ( 1 , 1 ) = rotation ( 0 , 0 );
return rotation ;
}
static Point find_first_different_vertex ( const Polygon & polygon , const size_t point_idx , const Point & point , bool forward )
{
2020-10-22 06:20:38 +02:00
assert ( point_idx < polygon . size ());
2020-10-16 09:03:49 +02:00
if ( point != polygon . points [ point_idx ])
return polygon . points [ point_idx ];
2020-10-28 12:57:45 +01:00
int line_idx = int ( point_idx );
2020-10-16 09:03:49 +02:00
if ( forward )
for (; point == polygon . points [ line_idx ]; line_idx = ((( line_idx + 1 ) < int ( polygon . points . size ())) ? ( line_idx + 1 ) : 0 ));
else
for (; point == polygon . points [ line_idx ]; line_idx = ((( line_idx - 1 ) >= 0 ) ? ( line_idx - 1 ) : ( int ( polygon . points . size ()) - 1 )));
return polygon . points [ line_idx ];
}
static Vec2d three_points_inward_normal ( const Point & left , const Point & middle , const Point & right )
{
assert ( left != middle );
assert ( middle != right );
Vec2d normal_1 ( - 1 * ( middle . y () - left . y ()), middle . x () - left . x ());
Vec2d normal_2 ( - 1 * ( right . y () - middle . y ()), right . x () - middle . x ());
normal_1 . normalize ();
normal_2 . normalize ();
return ( normal_1 + normal_2 ). normalized ();
2020-10-22 06:20:38 +02:00
}
2020-10-16 09:03:49 +02:00
2020-10-22 06:20:38 +02:00
// Compute normal of the polygon's vertex in an inward direction
2020-10-16 09:03:49 +02:00
static Vec2d get_polygon_vertex_inward_normal ( const Polygon & polygon , const size_t point_idx )
{
const size_t left_idx = ( point_idx <= 0 ) ? ( polygon . size () - 1 ) : ( point_idx - 1 );
const size_t right_idx = ( point_idx >= ( polygon . size () - 1 )) ? 0 : ( point_idx + 1 );
const Point & middle = polygon . points [ point_idx ];
const Point & left = find_first_different_vertex ( polygon , left_idx , middle , false );
const Point & right = find_first_different_vertex ( polygon , right_idx , middle , true );
return three_points_inward_normal ( left , middle , right );
}
2020-10-22 06:20:38 +02:00
// Compute offset of point_idx of the polygon in a direction of inward normal
2020-10-16 09:03:49 +02:00
static Point get_polygon_vertex_offset ( const Polygon & polygon , const size_t point_idx , const int offset )
{
return polygon . points [ point_idx ] + ( get_polygon_vertex_inward_normal ( polygon , point_idx ) * double ( offset )). cast < coord_t > ();
}
2020-10-22 06:20:38 +02:00
// Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx).
2020-10-28 12:57:45 +01:00
static Point get_middle_point_offset ( const Polygon & polygon , const size_t left_idx , const size_t right_idx , const Point & middle , const coord_t offset )
2020-10-16 09:03:49 +02:00
{
const Point & left = find_first_different_vertex ( polygon , left_idx , middle , false );
const Point & right = find_first_different_vertex ( polygon , right_idx , middle , true );
return middle + ( three_points_inward_normal ( left , middle , right ) * double ( offset )). cast < coord_t > ();
}
static bool check_if_could_cross_perimeters ( const BoundingBox & bbox , const Point & start , const Point & end )
{
bool start_out_of_bound = ! bbox . contains ( start ), end_out_of_bound = ! bbox . contains ( end );
// When both endpoints are out of the bounding box, it needs to check in more detail.
if ( start_out_of_bound && end_out_of_bound ) {
Point intersection ;
return bbox . polygon (). intersection ( Line ( start , end ), & intersection );
}
return true ;
}
static std :: pair < Point , Point > clamp_endpoints_by_bounding_box ( const BoundingBox & bbox , const Point & start , const Point & end )
{
bool start_out_of_bound = ! bbox . contains ( start ), end_out_of_bound = ! bbox . contains ( end );
Point start_clamped = start , end_clamped = end ;
Points intersections ;
if ( start_out_of_bound || end_out_of_bound ) {
bbox . polygon (). intersections ( Line ( start , end ), & intersections );
assert ( intersections . size () <= 2 );
}
if ( start_out_of_bound && ! end_out_of_bound && intersections . size () == 1 ) {
start_clamped = intersections [ 0 ];
} else if ( ! start_out_of_bound && end_out_of_bound && intersections . size () == 1 ) {
end_clamped = intersections [ 0 ];
} else if ( start_out_of_bound && end_out_of_bound && intersections . size () == 2 ) {
if (( intersections [ 0 ] - start ). cast < double > (). norm () < ( intersections [ 1 ] - start ). cast < double > (). norm ()) {
start_clamped = intersections [ 0 ];
end_clamped = intersections [ 1 ];
} else {
start_clamped = intersections [ 1 ];
end_clamped = intersections [ 0 ];
}
}
return std :: make_pair ( start_clamped , end_clamped );
}
2020-10-28 12:57:45 +01:00
static inline float get_default_perimeter_spacing ( const Print & print )
2020-10-16 09:03:49 +02:00
{
2020-10-16 14:37:16 +02:00
const std :: vector < double > & nozzle_diameters = print . config (). nozzle_diameter . values ;
2020-10-28 12:57:45 +01:00
return float ( scale_ ( * std :: max_element ( nozzle_diameters . begin (), nozzle_diameters . end ())));
2020-10-16 14:37:16 +02:00
}
2020-10-28 12:57:45 +01:00
static float get_perimeter_spacing ( const Layer & layer )
2020-10-16 14:37:16 +02:00
{
2020-10-28 12:57:45 +01:00
size_t regions_count = 0 ;
float perimeter_spacing = 0.f ;
2020-10-16 09:03:49 +02:00
for ( const LayerRegion * layer_region : layer . regions ()) {
perimeter_spacing += layer_region -> flow ( frPerimeter ). scaled_spacing ();
++ regions_count ;
}
2020-10-16 14:37:16 +02:00
2020-10-28 12:57:45 +01:00
assert ( perimeter_spacing >= 0.f );
2020-10-16 14:37:16 +02:00
if ( regions_count != 0 )
2020-10-28 12:57:45 +01:00
perimeter_spacing /= float ( regions_count );
2020-10-16 14:37:16 +02:00
else
perimeter_spacing = get_default_perimeter_spacing ( * layer . object () -> print ());
return perimeter_spacing ;
}
2020-10-28 12:57:45 +01:00
static float get_perimeter_spacing_external ( const Layer & layer )
2020-10-16 14:37:16 +02:00
{
2020-10-28 12:57:45 +01:00
size_t regions_count = 0 ;
float perimeter_spacing = 0.f ;
2020-10-16 14:37:16 +02:00
for ( const PrintObject * object : layer . object () -> print () -> objects ())
for ( Layer * l : object -> layers ())
if (( layer . print_z - EPSILON ) <= l -> print_z && l -> print_z <= ( layer . print_z + EPSILON ))
for ( const LayerRegion * layer_region : l -> regions ()) {
perimeter_spacing += layer_region -> flow ( frPerimeter ). scaled_spacing ();
++ regions_count ;
}
2020-10-28 12:57:45 +01:00
assert ( perimeter_spacing >= 0.f );
2020-10-16 14:37:16 +02:00
if ( regions_count != 0 )
2020-10-28 12:57:45 +01:00
perimeter_spacing /= float ( regions_count );
2020-10-16 14:37:16 +02:00
else
perimeter_spacing = get_default_perimeter_spacing ( * layer . object () -> print ());
return perimeter_spacing ;
}
2020-10-22 06:20:38 +02:00
// Check if anyone of ExPolygons contains whole travel.
template < class T > static bool any_expolygon_contains ( const ExPolygons & ex_polygons , const T & travel )
{
for ( const ExPolygon & ex_polygon : ex_polygons )
if ( ex_polygon . contains ( travel )) return true ;
return false ;
}
2020-10-28 01:52:50 +01:00
static std :: pair < Polygons , Polygons > split_expolygon ( const ExPolygons & ex_polygons )
{
Polygons contours , holes ;
contours . reserve ( ex_polygons . size ());
2020-10-28 13:21:06 +01:00
holes . reserve ( std :: accumulate ( ex_polygons . begin (), ex_polygons . end (), size_t ( 0 ),
2020-10-28 01:52:50 +01:00
[]( size_t sum , const ExPolygon & ex_poly ) { return sum + ex_poly . holes . size (); }));
for ( const ExPolygon & ex_poly : ex_polygons ) {
contours . emplace_back ( ex_poly . contour );
append ( holes , ex_poly . holes );
}
return std :: make_pair ( std :: move ( contours ), std :: move ( holes ));
}
2020-11-17 09:33:10 +01:00
static Polyline to_polyline ( const std :: vector < AvoidCrossingPerimeters :: TravelPoint > & travel )
2020-11-16 13:43:15 +01:00
{
Polyline result ;
result . points . reserve ( travel . size ());
2020-11-17 09:33:10 +01:00
for ( const AvoidCrossingPerimeters :: TravelPoint & t_point : travel )
2020-11-16 13:43:15 +01:00
result . append ( t_point . point );
return result ;
}
2020-11-17 09:33:10 +01:00
static double travel_length ( const std :: vector < AvoidCrossingPerimeters :: TravelPoint > & travel ) {
2020-11-16 13:43:15 +01:00
double total_length = 0 ;
for ( size_t idx = 1 ; idx < travel . size (); ++ idx )
total_length += ( travel [ idx ]. point - travel [ idx - 1 ]. point ). cast < double > (). norm ();
return total_length ;
}
2020-10-28 01:52:50 +01:00
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
2020-11-17 09:33:10 +01:00
static void export_travel_to_svg ( const Polygons & boundary ,
const Line & original_travel ,
const Polyline & result_travel ,
const std :: vector < AvoidCrossingPerimeters :: Intersection > & intersections ,
const std :: string & path )
2020-10-28 01:52:50 +01:00
{
BoundingBox bbox = get_extents ( boundary );
:: Slic3r :: SVG svg ( path , bbox );
svg . draw_outline ( boundary , "green" );
svg . draw ( original_travel , "blue" );
svg . draw ( result_travel , "red" );
svg . draw ( original_travel . a , "black" );
svg . draw ( original_travel . b , "grey" );
2020-11-17 09:33:10 +01:00
for ( const AvoidCrossingPerimeters :: Intersection & intersection : intersections )
2020-10-28 01:52:50 +01:00
svg . draw ( intersection . point , "lightseagreen" );
}
2020-11-16 13:43:15 +01:00
2020-11-17 09:33:10 +01:00
static void export_travel_to_svg ( const Polygons & boundary ,
const Line & original_travel ,
const std :: vector < AvoidCrossingPerimeters :: TravelPoint > & result_travel ,
const std :: vector < AvoidCrossingPerimeters :: Intersection > & intersections ,
const std :: string & path )
2020-11-16 13:43:15 +01:00
{
export_travel_to_svg ( boundary , original_travel , to_polyline ( result_travel ), intersections , path );
}
2020-10-28 01:52:50 +01:00
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
2020-11-17 09:33:10 +01:00
ExPolygons AvoidCrossingPerimeters :: get_boundary ( const Layer & layer )
2020-10-16 14:37:16 +02:00
{
2020-10-28 12:57:45 +01:00
const float perimeter_spacing = get_perimeter_spacing ( layer );
const float perimeter_offset = perimeter_spacing / 2.f ;
size_t polygons_count = 0 ;
2020-10-16 14:37:16 +02:00
for ( const LayerRegion * layer_region : layer . regions ())
polygons_count += layer_region -> slices . surfaces . size ();
2020-10-16 09:03:49 +02:00
ExPolygons boundary ;
boundary . reserve ( polygons_count );
for ( const LayerRegion * layer_region : layer . regions ())
for ( const Surface & surface : layer_region -> slices . surfaces ) boundary . emplace_back ( surface . expolygon );
boundary = union_ex ( boundary );
2020-10-28 01:52:50 +01:00
ExPolygons perimeter_boundary = offset_ex ( boundary , - perimeter_offset );
ExPolygons result_boundary ;
2020-10-16 09:03:49 +02:00
if ( perimeter_boundary . size () != boundary . size ()) {
2020-10-28 01:52:50 +01:00
// If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice.
ExPolygons missing_perimeter_boundary = offset_ex ( diff_ex ( boundary ,
2020-10-28 12:57:45 +01:00
offset_ex ( perimeter_boundary , perimeter_offset + float ( SCALED_EPSILON ) / 2.f )),
perimeter_offset + float ( SCALED_EPSILON ));
2020-10-28 01:52:50 +01:00
perimeter_boundary = offset_ex ( perimeter_boundary , perimeter_offset );
2020-10-19 09:44:33 +02:00
perimeter_boundary . reserve ( perimeter_boundary . size () + missing_perimeter_boundary . size ());
2020-10-28 01:52:50 +01:00
perimeter_boundary . insert ( perimeter_boundary . end (), missing_perimeter_boundary . begin (), missing_perimeter_boundary . end ());
// By calling intersection_ex some artifacts arose by previous operations are removed.
result_boundary = union_ex ( intersection_ex ( offset_ex ( perimeter_boundary , - perimeter_offset ), boundary ));
2020-10-16 09:03:49 +02:00
} else {
2020-10-28 01:52:50 +01:00
result_boundary = std :: move ( perimeter_boundary );
2020-10-16 09:03:49 +02:00
}
2020-10-28 01:52:50 +01:00
auto [ contours , holes ] = split_expolygon ( boundary );
2020-10-19 09:44:33 +02:00
// Add an outer boundary to avoid crossing perimeters from supports
2020-10-28 01:52:50 +01:00
ExPolygons outer_boundary = union_ex (
2020-10-28 12:57:45 +01:00
diff ( static_cast < Polygons > ( Geometry :: convex_hull ( offset ( contours , 2.f * perimeter_spacing ))),
2020-10-28 01:52:50 +01:00
offset ( contours , perimeter_spacing + perimeter_offset )));
result_boundary . insert ( result_boundary . end (), outer_boundary . begin (), outer_boundary . end ());
ExPolygons holes_boundary = offset_ex ( holes , - perimeter_spacing );
result_boundary . insert ( result_boundary . end (), holes_boundary . begin (), holes_boundary . end ());
result_boundary = union_ex ( result_boundary );
2020-10-19 09:44:33 +02:00
2020-10-16 09:03:49 +02:00
// Collect all top layers that will not be crossed.
polygons_count = 0 ;
for ( const LayerRegion * layer_region : layer . regions ())
for ( const Surface & surface : layer_region -> fill_surfaces . surfaces )
if ( surface . is_top ()) ++ polygons_count ;
if ( polygons_count > 0 ) {
ExPolygons top_layer_polygons ;
top_layer_polygons . reserve ( polygons_count );
for ( const LayerRegion * layer_region : layer . regions ())
for ( const Surface & surface : layer_region -> fill_surfaces . surfaces )
if ( surface . is_top ()) top_layer_polygons . emplace_back ( surface . expolygon );
top_layer_polygons = union_ex ( top_layer_polygons );
2020-10-28 01:52:50 +01:00
return diff_ex ( result_boundary , offset_ex ( top_layer_polygons , - perimeter_offset ));
2020-10-16 09:03:49 +02:00
}
2020-10-28 01:52:50 +01:00
return result_boundary ;
2020-10-16 09:03:49 +02:00
}
2020-11-17 09:33:10 +01:00
ExPolygons AvoidCrossingPerimeters :: get_boundary_external ( const Layer & layer )
2020-10-16 09:03:49 +02:00
{
2020-10-28 12:57:45 +01:00
const float perimeter_spacing = get_perimeter_spacing_external ( layer );
const float perimeter_offset = perimeter_spacing / 2.f ;
ExPolygons boundary ;
2020-10-28 01:52:50 +01:00
// Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer".
2020-10-16 14:37:16 +02:00
for ( const PrintObject * object : layer . object () -> print () -> objects ()) {
2020-10-16 09:03:49 +02:00
ExPolygons polygons_per_obj ;
2020-10-16 14:37:16 +02:00
for ( Layer * l : object -> layers ())
if (( layer . print_z - EPSILON ) <= l -> print_z && l -> print_z <= ( layer . print_z + EPSILON ))
for ( const LayerRegion * layer_region : l -> regions ())
2020-10-16 09:03:49 +02:00
for ( const Surface & surface : layer_region -> slices . surfaces )
polygons_per_obj . emplace_back ( surface . expolygon );
for ( const PrintInstance & instance : object -> instances ()) {
size_t boundary_idx = boundary . size ();
boundary . reserve ( boundary . size () + polygons_per_obj . size ());
boundary . insert ( boundary . end (), polygons_per_obj . begin (), polygons_per_obj . end ());
2020-10-16 14:37:16 +02:00
for (; boundary_idx < boundary . size (); ++ boundary_idx ) boundary [ boundary_idx ]. translate ( instance . shift . x (), instance . shift . y ());
2020-10-16 09:03:49 +02:00
}
}
2020-10-28 01:52:50 +01:00
boundary = union_ex ( boundary );
auto [ contours , holes ] = split_expolygon ( boundary );
// Polygons in which is possible traveling without crossing perimeters of another object.
// A convex hull allows removing unnecessary detour caused by following the boundary of the object.
ExPolygons result_boundary = union_ex (
2020-10-28 12:57:45 +01:00
diff ( static_cast < Polygons > ( Geometry :: convex_hull ( offset ( contours , 2.f * perimeter_spacing ))),
2020-10-28 01:52:50 +01:00
offset ( contours , perimeter_spacing + perimeter_offset )));
// All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed.
2020-10-16 09:03:49 +02:00
ExPolygons holes_boundary = union_ex ( diff ( offset ( holes , perimeter_spacing ), offset ( holes , perimeter_offset )));
2020-10-28 01:52:50 +01:00
result_boundary . reserve ( result_boundary . size () + holes_boundary . size ());
result_boundary . insert ( result_boundary . end (), holes_boundary . begin (), holes_boundary . end ());
result_boundary = union_ex ( result_boundary );
return result_boundary ;
2020-10-16 09:03:49 +02:00
}
// Returns a direction of the shortest path along the polygon boundary
2020-11-17 09:33:10 +01:00
AvoidCrossingPerimeters :: Direction AvoidCrossingPerimeters :: get_shortest_direction ( const Lines & lines ,
const size_t start_idx ,
const size_t end_idx ,
const Point & intersection_first ,
const Point & intersection_last )
2020-10-16 09:03:49 +02:00
{
double total_length_forward = ( lines [ start_idx ]. b - intersection_first ). cast < double > (). norm ();
double total_length_backward = ( lines [ start_idx ]. a - intersection_first ). cast < double > (). norm ();
auto cyclic_index = [ & lines ]( int index ) {
if ( index >= int ( lines . size ()))
index = 0 ;
else if ( index < 0 )
2020-10-28 12:57:45 +01:00
index = int ( lines . size ()) - 1 ;
2020-10-16 09:03:49 +02:00
return index ;
};
for ( int line_idx = cyclic_index ( int ( start_idx ) + 1 ); line_idx != int ( end_idx ); line_idx = cyclic_index ( line_idx + 1 ))
total_length_forward += lines [ line_idx ]. length ();
for ( int line_idx = cyclic_index ( int ( start_idx ) - 1 ); line_idx != int ( end_idx ); line_idx = cyclic_index ( line_idx - 1 ))
total_length_backward += lines [ line_idx ]. length ();
total_length_forward += ( lines [ end_idx ]. a - intersection_last ). cast < double > (). norm ();
total_length_backward += ( lines [ end_idx ]. b - intersection_last ). cast < double > (). norm ();
return ( total_length_forward < total_length_backward ) ? Direction :: Forward : Direction :: Backward ;
}
2020-11-17 09:33:10 +01:00
std :: vector < AvoidCrossingPerimeters :: TravelPoint > AvoidCrossingPerimeters :: simplify_travel ( const EdgeGrid :: Grid & edge_grid ,
const std :: vector < TravelPoint > & travel ,
const Polygons & boundaries ,
const bool use_heuristics )
2020-10-16 09:03:49 +02:00
{
struct Visitor
{
Visitor ( const EdgeGrid :: Grid & grid ) : grid ( grid ) {}
bool operator ()( coord_t iy , coord_t ix )
{
assert ( pt_current != nullptr );
assert ( pt_next != nullptr );
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid . cell_data_range ( iy , ix );
this -> intersect = false ;
for ( auto it_contour_and_segment = cell_data_range . first ; it_contour_and_segment != cell_data_range . second ; ++ it_contour_and_segment ) {
// End points of the line segment and their vector.
auto segment = grid . segment ( * it_contour_and_segment );
if ( Geometry :: segments_intersect ( segment . first , segment . second , * pt_current , * pt_next )) {
this -> intersect = true ;
return false ;
}
}
// Continue traversing the grid along the edge.
return true ;
}
const EdgeGrid :: Grid & grid ;
const Slic3r :: Point * pt_current = nullptr ;
const Slic3r :: Point * pt_next = nullptr ;
bool intersect = false ;
} visitor ( edge_grid );
2020-11-16 13:43:15 +01:00
std :: vector < TravelPoint > simplified_path ;
simplified_path . reserve ( travel . size ());
simplified_path . emplace_back ( travel . front ());
2020-10-16 09:03:49 +02:00
// Try to skip some points in the path.
2020-10-28 01:52:50 +01:00
for ( size_t point_idx = 1 ; point_idx < travel . size (); ++ point_idx ) {
2020-11-16 13:43:15 +01:00
const Point & current_point = travel [ point_idx - 1 ]. point ;
TravelPoint next = travel [ point_idx ];
2020-10-16 09:03:49 +02:00
visitor . pt_current = & current_point ;
2020-11-15 19:46:17 +01:00
for ( size_t point_idx_2 = point_idx + 1 ; point_idx_2 < travel . size (); ++ point_idx_2 ) {
2020-11-16 13:43:15 +01:00
if ( travel [ point_idx_2 ]. point == current_point ) {
next = travel [ point_idx_2 ];
2020-10-16 09:03:49 +02:00
point_idx = point_idx_2 ;
continue ;
}
2020-11-16 13:43:15 +01:00
visitor . pt_next = & travel [ point_idx_2 ]. point ;
2020-10-16 09:03:49 +02:00
edge_grid . visit_cells_intersecting_line ( * visitor . pt_current , * visitor . pt_next , visitor );
// Check if deleting point causes crossing a boundary
if ( ! visitor . intersect ) {
2020-11-16 13:43:15 +01:00
next = travel [ point_idx_2 ];
2020-10-16 09:03:49 +02:00
point_idx = point_idx_2 ;
}
}
2020-11-16 13:43:15 +01:00
simplified_path . emplace_back ( next );
}
if ( use_heuristics ) {
simplified_path = simplify_travel_heuristics ( edge_grid , simplified_path , boundaries );
std :: reverse ( simplified_path . begin (), simplified_path . end ());
simplified_path = simplify_travel_heuristics ( edge_grid , simplified_path , boundaries );
std :: reverse ( simplified_path . begin (), simplified_path . end ());
2020-10-16 09:03:49 +02:00
}
2020-10-28 01:52:50 +01:00
return simplified_path ;
2020-10-16 09:03:49 +02:00
}
2020-11-17 09:33:10 +01:00
std :: vector < AvoidCrossingPerimeters :: TravelPoint > AvoidCrossingPerimeters :: simplify_travel_heuristics ( const EdgeGrid :: Grid & edge_grid ,
const std :: vector < TravelPoint > & travel ,
const Polygons & boundaries )
2020-11-16 13:43:15 +01:00
{
std :: vector < TravelPoint > simplified_path ;
std :: vector < Intersection > intersections ;
AllIntersectionsVisitor visitor ( edge_grid , intersections );
simplified_path . reserve ( travel . size ());
simplified_path . emplace_back ( travel . front ());
for ( size_t point_idx = 1 ; point_idx < travel . size (); ++ point_idx ) {
// Skip all indexes on the same polygon
while ( point_idx < travel . size () && travel [ point_idx - 1 ]. border_idx == travel [ point_idx ]. border_idx ) {
simplified_path . emplace_back ( travel [ point_idx ]);
point_idx ++ ;
}
if ( point_idx < travel . size ()) {
const TravelPoint & current = travel [ point_idx - 1 ];
const TravelPoint & next = travel [ point_idx ];
TravelPoint new_next = next ;
size_t new_point_idx = point_idx ;
double path_length = ( next . point - current . point ). cast < double > (). norm ();
double new_path_shorter_by = 0. ;
size_t border_idx_change_count = 0 ;
std :: vector < TravelPoint > shortcut ;
for ( size_t point_idx_2 = point_idx + 1 ; point_idx_2 < travel . size (); ++ point_idx_2 ) {
const TravelPoint & possible_new_next = travel [ point_idx_2 ];
if ( travel [ point_idx_2 - 1 ]. border_idx != travel [ point_idx_2 ]. border_idx )
border_idx_change_count ++ ;
if ( border_idx_change_count >= 2 )
break ;
path_length += ( possible_new_next . point - travel [ point_idx_2 - 1 ]. point ). cast < double > (). norm ();
double shortcut_length = ( possible_new_next . point - current . point ). cast < double > (). norm ();
if (( path_length - shortcut_length ) <= scale_ ( 10.0 ))
continue ;
intersections . clear ();
visitor . reset ();
visitor . travel_line . a = current . point ;
visitor . travel_line . b = possible_new_next . point ;
visitor . transform_to_x_axis = rotation_by_direction ( visitor . travel_line . vector ());
edge_grid . visit_cells_intersecting_line ( visitor . travel_line . a , visitor . travel_line . b , visitor );
if ( ! intersections . empty ()) {
std :: sort ( intersections . begin (), intersections . end ());
size_t last_border_idx_count = 0 ;
for ( const Intersection & intersection : intersections )
2020-11-16 14:37:42 +01:00
if ( int ( intersection . border_idx ) == possible_new_next . border_idx )
2020-11-16 13:43:15 +01:00
++ last_border_idx_count ;
if ( last_border_idx_count > 0 )
continue ;
std :: vector < TravelPoint > possible_shortcut ;
avoid_perimeters ( boundaries , edge_grid , current . point , possible_new_next . point , false , & possible_shortcut );
double shortcut_travel = travel_length ( possible_shortcut );
if ( path_length > shortcut_travel && ( path_length - shortcut_travel ) > new_path_shorter_by ) {
new_path_shorter_by = path_length - shortcut_travel ;
shortcut = possible_shortcut ;
new_next = possible_new_next ;
new_point_idx = point_idx_2 ;
}
}
}
if ( ! shortcut . empty ()) {
assert ( shortcut . size () >= 2 );
simplified_path . insert ( simplified_path . end (), shortcut . begin () + 1 , shortcut . end () - 1 );
point_idx = new_point_idx ;
}
simplified_path . emplace_back ( new_next );
}
}
return simplified_path ;
}
2020-11-17 09:33:10 +01:00
size_t AvoidCrossingPerimeters :: avoid_perimeters ( const Polygons & boundaries ,
const EdgeGrid :: Grid & edge_grid ,
const Point & start ,
const Point & end ,
const bool use_heuristics ,
std :: vector < TravelPoint > * result_out )
2020-10-16 09:03:49 +02:00
{
const Point direction = end - start ;
Matrix2d transform_to_x_axis = rotation_by_direction ( direction );
const Line travel_line_orig ( start , end );
const Line travel_line (( transform_to_x_axis * start . cast < double > ()). cast < coord_t > (),
( transform_to_x_axis * end . cast < double > ()). cast < coord_t > ());
std :: vector < Intersection > intersections ;
{
2020-11-16 13:43:15 +01:00
AllIntersectionsVisitor visitor ( edge_grid , intersections , transform_to_x_axis , travel_line_orig );
2020-10-16 09:03:49 +02:00
edge_grid . visit_cells_intersecting_line ( start , end , visitor );
}
std :: sort ( intersections . begin (), intersections . end ());
2020-11-16 13:43:15 +01:00
std :: vector < TravelPoint > result ;
result . push_back ({ start , - 1 });
2020-10-16 09:03:49 +02:00
for ( auto it_first = intersections . begin (); it_first != intersections . end (); ++ it_first ) {
2020-10-28 01:52:50 +01:00
// The entry point to the boundary polygon
2020-10-16 09:03:49 +02:00
const Intersection & intersection_first = * it_first ;
2020-10-28 01:52:50 +01:00
// Skip the it_first from the search for the farthest exit point from the boundary polygon
auto it_last_item = std :: make_reverse_iterator ( it_first ) - 1 ;
// Search for the farthest intersection different from it_first but with the same border_idx
auto it_second_r = std :: find_if ( intersections . rbegin (), it_last_item , [ & intersection_first ]( const Intersection & intersection ) {
return intersection_first . border_idx == intersection . border_idx ;
});
// Append the first intersection into the path
size_t left_idx = intersection_first . line_idx ;
size_t right_idx = ( intersection_first . line_idx >= ( boundaries [ intersection_first . border_idx ]. points . size () - 1 )) ? 0 : ( intersection_first . line_idx + 1 );
// Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the
// boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the
// appended point will be inside the polygon and not on the polygon border.
2020-11-16 13:43:15 +01:00
result . push_back ({ get_middle_point_offset ( boundaries [ intersection_first . border_idx ], left_idx , right_idx , intersection_first . point , coord_t ( SCALED_EPSILON )), int ( intersection_first . border_idx )});
2020-10-28 01:52:50 +01:00
// Check if intersection line also exit the boundary polygon
if ( it_second_r != it_last_item ) {
// Transform reverse iterator to forward
auto it_second = ( it_second_r . base () - 1 );
// The exit point from the boundary polygon
2020-10-16 09:03:49 +02:00
const Intersection & intersection_second = * it_second ;
2020-10-28 01:52:50 +01:00
Lines border_lines = boundaries [ intersection_first . border_idx ]. lines ();
2020-10-16 09:03:49 +02:00
2020-10-28 01:52:50 +01:00
Direction shortest_direction = get_shortest_direction ( border_lines , intersection_first . line_idx , intersection_second . line_idx , intersection_first . point , intersection_second . point );
// Append the path around the border into the path
if ( shortest_direction == Direction :: Forward )
2020-10-28 12:57:45 +01:00
for ( int line_idx = int ( intersection_first . line_idx ); line_idx != int ( intersection_second . line_idx );
2020-10-28 01:52:50 +01:00
line_idx = ((( line_idx + 1 ) < int ( border_lines . size ())) ? ( line_idx + 1 ) : 0 ))
2020-11-16 13:43:15 +01:00
result . push_back ({ get_polygon_vertex_offset ( boundaries [ intersection_first . border_idx ],
( line_idx + 1 == int ( boundaries [ intersection_first . border_idx ]. points . size ())) ? 0 : ( line_idx + 1 ), coord_t ( SCALED_EPSILON )), int ( intersection_first . border_idx )});
2020-10-28 01:52:50 +01:00
else
2020-10-28 12:57:45 +01:00
for ( int line_idx = int ( intersection_first . line_idx ); line_idx != int ( intersection_second . line_idx );
2020-10-28 01:52:50 +01:00
line_idx = ((( line_idx - 1 ) >= 0 ) ? ( line_idx - 1 ) : ( int ( border_lines . size ()) - 1 )))
2020-11-16 13:43:15 +01:00
result . push_back ({ get_polygon_vertex_offset ( boundaries [ intersection_second . border_idx ], line_idx + 0 , coord_t ( SCALED_EPSILON )), int ( intersection_first . border_idx )});
2020-10-16 09:03:49 +02:00
2020-10-28 01:52:50 +01:00
// Append the farthest intersection into the path
left_idx = intersection_second . line_idx ;
right_idx = ( intersection_second . line_idx >= ( boundaries [ intersection_second . border_idx ]. points . size () - 1 )) ? 0 : ( intersection_second . line_idx + 1 );
2020-11-16 13:43:15 +01:00
result . push_back ({ get_middle_point_offset ( boundaries [ intersection_second . border_idx ], left_idx , right_idx , intersection_second . point , coord_t ( SCALED_EPSILON )), int ( intersection_second . border_idx )});
2020-10-28 01:52:50 +01:00
// Skip intersections in between
it_first = it_second ;
2020-10-16 09:03:49 +02:00
}
}
2020-11-16 13:43:15 +01:00
result . push_back ({ end , - 1 });
2020-10-28 01:52:50 +01:00
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0 ;
export_travel_to_svg ( boundaries , travel_line_orig , result , intersections ,
debug_out_path ( "AvoidCrossingPerimeters-initial-%d.svg" , iRun ++ ));
2020-10-22 06:20:38 +02:00
}
2020-10-28 01:52:50 +01:00
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
if ( ! intersections . empty ())
2020-11-16 13:43:15 +01:00
result = simplify_travel ( edge_grid , result , boundaries , use_heuristics );
2020-10-28 01:52:50 +01:00
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0 ;
export_travel_to_svg ( boundaries , travel_line_orig , result , intersections ,
debug_out_path ( "AvoidCrossingPerimeters-final-%d.svg" , iRun ++ ));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
2020-10-22 06:20:38 +02:00
2020-11-16 13:43:15 +01:00
result_out -> reserve ( result_out -> size () + result . size ());
result_out -> insert ( result_out -> end (), result . begin (), result . end ());
2020-10-22 06:20:38 +02:00
return intersections . size ();
2020-10-16 09:03:49 +02:00
}
2020-11-17 09:33:10 +01:00
bool AvoidCrossingPerimeters :: need_wipe ( const GCode & gcodegen ,
const Line & original_travel ,
const Polyline & result_travel ,
const size_t intersection_count )
2020-10-22 06:20:38 +02:00
{
bool z_lift_enabled = gcodegen . config (). retract_lift . get_at ( gcodegen . writer (). extruder () -> id ()) > 0. ;
bool wipe_needed = false ;
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely
// outside the object.
if ( intersection_count > 0 ) {
// The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test.
// If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes.
if ( z_lift_enabled ) {
if ( any_expolygon_contains ( m_slice , original_travel )) {
2020-11-15 19:46:17 +01:00
// Check if original_travel and result_travel are not same.
// If both are the same, then it is possible to skip testing of result_travel
2020-10-22 06:20:38 +02:00
if ( result_travel . size () == 2 && result_travel . first_point () == original_travel . a && result_travel . last_point () == original_travel . b ) {
wipe_needed = false ;
} else {
wipe_needed = ! any_expolygon_contains ( m_slice , result_travel );
}
} else {
wipe_needed = true ;
}
} else {
wipe_needed = ! any_expolygon_contains ( m_slice , result_travel );
}
}
return wipe_needed ;
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
2020-11-17 09:33:10 +01:00
Polyline AvoidCrossingPerimeters :: travel_to ( const GCode & gcodegen , const Point & point , bool * could_be_wipe_disabled )
2020-10-16 09:03:49 +02:00
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = this -> use_external_mp || this -> use_external_mp_once ;
Point scaled_origin = use_external ? Point :: new_scale ( gcodegen . origin ()( 0 ), gcodegen . origin ()( 1 )) : Point ( 0 , 0 );
Point start = gcodegen . last_pos () + scaled_origin ;
Point end = point + scaled_origin ;
2020-11-16 13:43:15 +01:00
Polyline result_pl ;
2020-10-22 06:20:38 +02:00
size_t travel_intersection_count = 0 ;
2020-10-16 09:03:49 +02:00
if ( ! check_if_could_cross_perimeters ( use_external ? m_bbox_external : m_bbox , start , end )) {
2020-11-16 13:43:15 +01:00
result_pl = Polyline ({ start , end });
2020-10-22 06:20:38 +02:00
travel_intersection_count = 0 ;
2020-10-16 09:03:49 +02:00
} else {
2020-11-16 13:43:15 +01:00
std :: vector < TravelPoint > result ;
2020-10-16 09:03:49 +02:00
auto [ start_clamped , end_clamped ] = clamp_endpoints_by_bounding_box ( use_external ? m_bbox_external : m_bbox , start , end );
if ( use_external )
2020-11-16 14:37:42 +01:00
travel_intersection_count = this -> avoid_perimeters ( m_boundaries_external , m_grid_external , start_clamped , end_clamped , true , & result );
2020-10-16 09:03:49 +02:00
else
2020-11-16 14:37:42 +01:00
travel_intersection_count = this -> avoid_perimeters ( m_boundaries , m_grid , start_clamped , end_clamped , true , & result );
2020-11-16 13:43:15 +01:00
result_pl = to_polyline ( result );
2020-10-16 09:03:49 +02:00
}
2020-11-16 13:43:15 +01:00
result_pl . points . front () = start ;
result_pl . points . back () = end ;
2020-10-16 09:03:49 +02:00
Line travel ( start , end );
double max_detour_length scale_ ( gcodegen . config (). avoid_crossing_perimeters_max_detour );
2020-11-16 13:43:15 +01:00
if (( max_detour_length > 0 ) && (( result_pl . length () - travel . length ()) > max_detour_length )) {
result_pl = Polyline ({ start , end });
2020-10-16 09:03:49 +02:00
}
2020-10-22 06:20:38 +02:00
if ( use_external ) {
2020-11-16 13:43:15 +01:00
result_pl . translate ( - scaled_origin );
2020-10-22 06:20:38 +02:00
* could_be_wipe_disabled = false ;
} else
2020-11-16 13:43:15 +01:00
* could_be_wipe_disabled = ! need_wipe ( gcodegen , travel , result_pl , travel_intersection_count );
2020-10-22 06:20:38 +02:00
2020-11-16 13:43:15 +01:00
return result_pl ;
2020-10-16 09:03:49 +02:00
}
2020-11-17 09:33:10 +01:00
void AvoidCrossingPerimeters :: init_layer ( const Layer & layer )
2020-10-16 09:03:49 +02:00
{
2020-10-22 06:20:38 +02:00
m_slice . clear ();
2020-10-16 09:03:49 +02:00
m_boundaries . clear ();
m_boundaries_external . clear ();
2020-10-22 06:20:38 +02:00
for ( const LayerRegion * layer_region : layer . regions ())
append ( m_slice , ( ExPolygons ) layer_region -> slices );
2020-10-16 09:03:49 +02:00
2020-10-22 06:20:38 +02:00
m_boundaries = to_polygons ( get_boundary ( layer ));
m_boundaries_external = to_polygons ( get_boundary_external ( layer ));
m_bbox = get_extents ( m_boundaries );
2020-10-16 09:03:49 +02:00
m_bbox . offset ( SCALED_EPSILON );
2020-10-22 06:20:38 +02:00
m_bbox_external = get_extents ( m_boundaries_external );
2020-10-16 09:03:49 +02:00
m_bbox_external . offset ( SCALED_EPSILON );
m_grid . set_bbox ( m_bbox );
2020-10-28 12:57:45 +01:00
m_grid . create ( m_boundaries , coord_t ( scale_ ( 1. )));
2020-10-16 09:03:49 +02:00
m_grid_external . set_bbox ( m_bbox_external );
2020-10-28 12:57:45 +01:00
m_grid_external . create ( m_boundaries_external , coord_t ( scale_ ( 1. )));
2020-10-16 09:03:49 +02:00
}
} // namespace Slic3r