2020-10-16 09:03:49 +02:00
#include "../Layer.hpp"
#include "../GCode.hpp"
#include "../EdgeGrid.hpp"
#include "../Print.hpp"
#include "../Polygon.hpp"
#include "../ExPolygon.hpp"
2020-11-17 15:18:19 +01:00
#include "../Geometry.hpp"
2020-10-16 09:03:49 +02:00
#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"
2020-11-15 19:46:17 +01:00
#include <numeric>
#include <unordered_set>
2020-10-16 09:03:49 +02:00
namespace Slic3r {
2020-11-17 10:42:27 +01:00
struct TravelPoint
{
Point point ;
2020-11-17 15:18:19 +01:00
// Index of the polygon containing this point. A negative value indicates that the point is not on any border.
2020-11-17 10:42:27 +01:00
int border_idx ;
};
struct Intersection
{
// Index of the polygon containing this point of intersection.
size_t border_idx ;
// Index of the line on the polygon containing this point of intersection.
size_t line_idx ;
// Point of intersection.
Point point ;
};
2020-11-17 15:18:19 +01:00
// Finding all intersections of a set of contours with a line segment.
2020-11-17 10:42:27 +01:00
struct AllIntersectionsVisitor
{
AllIntersectionsVisitor ( const EdgeGrid :: Grid & grid , std :: vector < Intersection > & intersections )
: grid ( grid ), intersections ( intersections )
{}
AllIntersectionsVisitor ( const EdgeGrid :: Grid & grid ,
std :: vector < Intersection > & intersections ,
const Line & travel_line )
2020-11-17 15:18:19 +01:00
: grid ( grid ), intersections ( intersections ), travel_line ( travel_line )
2020-11-17 10:42:27 +01:00
{}
void reset () {
intersection_set . clear ();
}
bool operator ()( coord_t iy , coord_t ix )
{
// 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 );
2020-11-17 15:18:19 +01:00
for ( auto it_contour_and_segment = cell_data_range . first ; it_contour_and_segment != cell_data_range . second ; ++ it_contour_and_segment ) {
2020-11-17 10:42:27 +01:00
Point intersection_point ;
2020-11-17 15:18:19 +01:00
if ( travel_line . intersection ( grid . line ( * it_contour_and_segment ), & intersection_point ) &&
2020-11-17 10:42:27 +01:00
intersection_set . find ( * it_contour_and_segment ) == intersection_set . end ()) {
2020-11-17 15:18:19 +01:00
intersections . push_back ({ it_contour_and_segment -> first , it_contour_and_segment -> second , intersection_point });
2020-11-17 10:42:27 +01:00
intersection_set . insert ( * it_contour_and_segment );
}
}
// Continue traversing the grid along the edge.
return true ;
}
const EdgeGrid :: Grid & grid ;
std :: vector < Intersection > & intersections ;
Line travel_line ;
std :: unordered_set < std :: pair < size_t , size_t > , boost :: hash < std :: pair < size_t , size_t >>> intersection_set ;
};
2020-11-17 15:18:19 +01:00
template < bool forward >
static Point find_first_different_vertex ( const Polygon & polygon , const size_t point_idx , const Point & point )
2020-10-16 09:03:49 +02:00
{
2020-10-22 06:20:38 +02:00
assert ( point_idx < polygon . size ());
2020-11-17 15:18:19 +01:00
auto line_idx = int ( point_idx );
//FIXME endless loop if all points are equal to point?
if constexpr ( forward )
for (; point == polygon . points [ line_idx ]; line_idx = line_idx + 1 < int ( polygon . points . size ()) ? line_idx + 1 : 0 );
2020-10-16 09:03:49 +02:00
else
2020-11-17 15:18:19 +01:00
for (; point == polygon . points [ line_idx ]; line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int ( polygon . points . size ()) - 1 );
2020-10-16 09:03:49 +02:00
return polygon . points [ line_idx ];
}
2020-11-17 15:18:19 +01:00
//FIXME will be in Point.h in the master
template < typename T , int Options >
inline Eigen :: Matrix < T , 2 , 1 , Eigen :: DontAlign > perp ( const Eigen :: MatrixBase < Eigen :: Matrix < T , 2 , 1 , Options >>& v ) { return Eigen :: Matrix < T , 2 , 1 , Eigen :: DontAlign > ( - v . y (), v . x ()); }
2020-10-16 09:03:49 +02:00
static Vec2d three_points_inward_normal ( const Point & left , const Point & middle , const Point & right )
{
assert ( left != middle );
assert ( middle != right );
2020-11-17 15:18:19 +01:00
return ( perp ( Point ( middle - left )). cast < double > (). normalized () + perp ( Point ( right - middle )). cast < double > (). normalized ()). 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 )
{
2020-11-17 15:18:19 +01:00
const size_t left_idx = point_idx == 0 ? polygon . size () - 1 : point_idx - 1 ;
const size_t right_idx = point_idx + 1 == polygon . size () ? 0 : point_idx + 1 ;
2020-10-16 09:03:49 +02:00
const Point & middle = polygon . points [ point_idx ];
2020-11-17 15:18:19 +01:00
const Point & left = find_first_different_vertex < false > ( polygon , left_idx , middle );
const Point & right = find_first_different_vertex < true > ( polygon , right_idx , middle );
2020-10-16 09:03:49 +02:00
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
{
2020-11-17 15:18:19 +01:00
const Point & left = find_first_different_vertex < false > ( polygon , left_idx , middle );
const Point & right = find_first_different_vertex < true > ( polygon , right_idx , middle );
2020-10-16 09:03:49 +02:00
return middle + ( three_points_inward_normal ( left , middle , right ) * double ( offset )). cast < coord_t > ();
}
2020-11-17 10:42:27 +01:00
static Polyline to_polyline ( const std :: vector < TravelPoint > & travel )
2020-11-16 13:43:15 +01:00
{
Polyline result ;
result . points . reserve ( travel . size ());
2020-11-17 10:42:27 +01:00
for ( const TravelPoint & t_point : travel )
2020-11-16 13:43:15 +01:00
result . append ( t_point . point );
return result ;
}
2020-11-17 10:42:27 +01:00
static double travel_length ( const std :: vector < 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-11-17 15:18:19 +01:00
// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
2020-10-28 01:52:50 +01:00
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
2020-11-17 10:42:27 +01:00
static void export_travel_to_svg ( const Polygons & boundary ,
const Line & original_travel ,
const Polyline & result_travel ,
const std :: vector < 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 10:42:27 +01:00
for ( const 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 10:42:27 +01:00
static void export_travel_to_svg ( const Polygons & boundary ,
const Line & original_travel ,
const std :: vector < TravelPoint > & result_travel ,
const std :: vector < 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-10-16 09:03:49 +02:00
// Returns a direction of the shortest path along the polygon boundary
2020-11-17 10:42:27 +01:00
enum class Direction { Forward , Backward };
static Direction 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 15:18:19 +01:00
// Straighten the travel path as long as it does not collide with the contours stored in edge_grid.
static std :: vector < TravelPoint > simplify_travel ( const EdgeGrid :: Grid & edge_grid , const std :: vector < TravelPoint > & travel )
2020-10-16 09:03:49 +02:00
{
2020-11-17 15:18:19 +01:00
// Visitor to check for a collision of a line segment with any contour stored inside the edge_grid.
struct Visitor
2020-10-16 09:03:49 +02:00
{
2020-11-17 15:18:19 +01:00
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 );
std :: vector < TravelPoint > simplified_path ;
simplified_path . reserve ( travel . size ());
simplified_path . emplace_back ( travel . front ());
// Try to skip some points in the path.
//FIXME maybe use a binary search to trim the line?
//FIXME how about searching tangent point at long segments?
for ( size_t point_idx = 1 ; point_idx < travel . size (); ++ point_idx ) {
const Point & current_point = travel [ point_idx - 1 ]. point ;
TravelPoint next = travel [ point_idx ];
visitor . pt_current = & current_point ;
for ( size_t point_idx_2 = point_idx + 1 ; point_idx_2 < travel . size (); ++ point_idx_2 ) {
if ( travel [ point_idx_2 ]. point == current_point ) {
next = travel [ point_idx_2 ];
point_idx = point_idx_2 ;
continue ;
}
visitor . pt_next = & travel [ point_idx_2 ]. point ;
edge_grid . visit_cells_intersecting_line ( * visitor . pt_current , * visitor . pt_next , visitor );
// Check if deleting point causes crossing a boundary
if ( ! visitor . intersect ) {
next = travel [ point_idx_2 ];
point_idx = point_idx_2 ;
}
}
simplified_path . emplace_back ( next );
2020-10-16 09:03:49 +02:00
}
2020-11-17 15:18:19 +01:00
return simplified_path ;
}
// Called by avoid_perimeters() and by simplify_travel_heuristics().
static size_t avoid_perimeters_inner ( const Polygons & boundaries ,
const EdgeGrid :: Grid & edge_grid ,
const Point & start ,
const Point & end ,
std :: vector < TravelPoint > & result_out )
{
// Find all intersections between boundaries and the line segment, sort them along the line segment.
std :: vector < Intersection > intersections ;
{
AllIntersectionsVisitor visitor ( edge_grid , intersections , Line ( start , end ));
edge_grid . visit_cells_intersecting_line ( start , end , visitor );
Vec2d dir = ( end - start ). cast < double > ();
std :: sort ( intersections . begin (), intersections . end (), [ dir ]( const auto & l , const auto & r ) { return ( r . point - l . point ). template cast < double > (). dot ( dir ) > 0. ; });
}
2020-10-16 09:03:49 +02:00
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 ;
2020-11-17 15:18:19 +01:00
export_travel_to_svg ( boundaries , Line ( start , end ), result , intersections ,
debug_out_path ( "AvoidCrossingPerimetersInner-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 */
2020-11-17 15:18:19 +01:00
if ( ! intersections . empty ())
result = simplify_travel ( edge_grid , result );
2020-10-28 01:52:50 +01:00
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0 ;
2020-11-17 15:18:19 +01:00
export_travel_to_svg ( boundaries , Line ( start , end ), result , intersections ,
debug_out_path ( "AvoidCrossingPerimetersInner-final-%d.svg" , iRun ++ ));
2020-10-28 01:52:50 +01:00
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
2020-10-22 06:20:38 +02:00
2020-11-17 15:18:19 +01:00
append ( result_out , std :: move ( result ));
2020-10-22 06:20:38 +02:00
return intersections . size ();
2020-10-16 09:03:49 +02:00
}
2020-11-17 10:42:27 +01:00
static std :: vector < TravelPoint > simplify_travel_heuristics ( const EdgeGrid :: Grid & edge_grid ,
const std :: vector < TravelPoint > & travel ,
const Polygons & boundaries )
{
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 ;
edge_grid . visit_cells_intersecting_line ( visitor . travel_line . a , visitor . travel_line . b , visitor );
if ( ! intersections . empty ()) {
2020-11-17 15:18:19 +01:00
Vec2d dir = ( visitor . travel_line . b - visitor . travel_line . a ). cast < double > ();
std :: sort ( intersections . begin (), intersections . end (), [ dir ]( const auto & l , const auto & r ) { return ( r . point - l . point ). template cast < double > (). dot ( dir ) > 0. ; });
2020-11-17 10:42:27 +01:00
size_t last_border_idx_count = 0 ;
for ( const Intersection & intersection : intersections )
if ( int ( intersection . border_idx ) == possible_new_next . border_idx )
++ last_border_idx_count ;
if ( last_border_idx_count > 0 )
continue ;
std :: vector < TravelPoint > possible_shortcut ;
2020-11-17 15:18:19 +01:00
avoid_perimeters_inner ( boundaries , edge_grid , current . point , possible_new_next . point , possible_shortcut );
2020-11-17 10:42:27 +01:00
double shortcut_travel = travel_length ( possible_shortcut );
2020-11-17 15:18:19 +01:00
if ( path_length > shortcut_travel && path_length - shortcut_travel > new_path_shorter_by ) {
2020-11-17 10:42:27 +01:00
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 15:18:19 +01:00
// Called by AvoidCrossingPerimeters::travel_to()
static size_t avoid_perimeters ( const Polygons & boundaries ,
const EdgeGrid :: Grid & edge_grid ,
const Point & start ,
const Point & end ,
Polyline & result_out )
2020-11-17 10:42:27 +01:00
{
2020-11-17 15:18:19 +01:00
// Travel line is completely or partially inside the bounding box.
std :: vector < TravelPoint > path ;
size_t num_intersections = avoid_perimeters_inner ( boundaries , edge_grid , start , end , path );
if ( num_intersections ) {
path = simplify_travel_heuristics ( edge_grid , path , boundaries );
std :: reverse ( path . begin (), path . end ());
path = simplify_travel_heuristics ( edge_grid , path , boundaries );
std :: reverse ( path . begin (), path . end ());
}
result_out = to_polyline ( path );
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
2020-11-17 10:42:27 +01:00
{
2020-11-17 15:18:19 +01:00
static int iRun = 0 ;
export_travel_to_svg ( boundaries , Line ( start , end ), path , {}, debug_out_path ( "AvoidCrossingPerimeters-final-%d.svg" , iRun ++ ));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
2020-11-17 10:42:27 +01:00
2020-11-17 15:18:19 +01:00
return num_intersections ;
}
// Check if anyone of ExPolygons contains whole travel.
// called by need_wipe()
template < class T > static bool any_expolygon_contains ( const ExPolygons & ex_polygons , const T & travel )
{
//FIXME filter by bounding boxes!
for ( const ExPolygon & ex_polygon : ex_polygons )
if ( ex_polygon . contains ( travel ))
2020-11-17 10:42:27 +01:00
return true ;
2020-11-17 15:18:19 +01:00
return false ;
2020-11-17 10:42:27 +01:00
}
static bool need_wipe ( const GCode & gcodegen ,
const ExPolygons & slice ,
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 ) {
2020-11-17 10:42:27 +01:00
if ( any_expolygon_contains ( 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-11-17 15:18:19 +01:00
wipe_needed = ! ( result_travel . size () > 2 && result_travel . first_point () == original_travel . a && result_travel . last_point () == original_travel . b ) &&
! any_expolygon_contains ( slice , result_travel );
2020-10-22 06:20:38 +02:00
} else {
wipe_needed = true ;
}
} else {
2020-11-17 10:42:27 +01:00
wipe_needed = ! any_expolygon_contains ( slice , result_travel );
2020-10-22 06:20:38 +02:00
}
}
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.
2020-11-17 10:42:27 +01:00
bool use_external = m_use_external_mp || m_use_external_mp_once ;
2020-10-16 09:03:49 +02:00
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-11-17 15:18:19 +01:00
Vec2d startf = start . cast < double > ();
Vec2d endf = end . cast < double > ();
// Trim the travel line by the bounding box.
if ( Geometry :: liang_barsky_line_clipping ( startf , endf , use_external ? m_bbox_external : m_bbox )) {
// Travel line is completely or partially inside the bounding box.
travel_intersection_count = use_external ?
avoid_perimeters ( m_boundaries_external , m_grid_external , startf . cast < coord_t > (), endf . cast < coord_t > (), result_pl ) :
avoid_perimeters ( m_boundaries , m_grid , startf . cast < coord_t > (), endf . cast < coord_t > (), result_pl );
result_pl . points . front () = start ;
result_pl . points . back () = end ;
2020-10-16 09:03:49 +02:00
} else {
2020-11-17 15:18:19 +01:00
// Travel line is completely outside the bounding box.
result_pl = { start , end };
travel_intersection_count = 0 ;
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-17 15:18:19 +01:00
if ( max_detour_length > 0 && ( result_pl . length () - travel . length ()) > max_detour_length )
result_pl = { start , end };
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-17 10:42:27 +01:00
* could_be_wipe_disabled = ! need_wipe ( gcodegen , m_slice , 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 15:18:19 +01:00
// ************************************* AvoidCrossingPerimeters::init_layer() *****************************************
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
static inline float get_default_perimeter_spacing ( const Print & print )
{
//FIXME better use extruders printing this PrintObject or this Print?
//FIXME maybe better use an average of printing extruders?
const std :: vector < double > & nozzle_diameters = print . config (). nozzle_diameter . values ;
return float ( scale_ ( * std :: max_element ( nozzle_diameters . begin (), nozzle_diameters . end ())));
}
// called by get_boundary()
static float get_perimeter_spacing ( const Layer & layer )
{
size_t regions_count = 0 ;
float perimeter_spacing = 0.f ;
//FIXME not all regions are printing. Collect only non-empty regions?
for ( const LayerRegion * layer_region : layer . regions ()) {
perimeter_spacing += layer_region -> flow ( frPerimeter ). scaled_spacing ();
++ regions_count ;
}
assert ( perimeter_spacing >= 0.f );
if ( regions_count != 0 )
perimeter_spacing /= float ( regions_count );
else
perimeter_spacing = get_default_perimeter_spacing ( * layer . object () -> print ());
return perimeter_spacing ;
}
// called by get_boundary_external()
static float get_perimeter_spacing_external ( const Layer & layer )
{
size_t regions_count = 0 ;
float perimeter_spacing = 0.f ;
for ( const PrintObject * object : layer . object () -> print () -> objects ())
//FIXME with different layering, layers on other objects will not be found at this object's print_z.
// Search an overlap of layers?
if ( const Layer * l = object -> get_layer_at_printz ( layer . print_z , EPSILON ); l )
//FIXME not all regions are printing. Collect only non-empty regions?
for ( const LayerRegion * layer_region : l -> regions ()) {
perimeter_spacing += layer_region -> flow ( frPerimeter ). scaled_spacing ();
++ regions_count ;
}
assert ( perimeter_spacing >= 0.f );
if ( regions_count != 0 )
perimeter_spacing /= float ( regions_count );
else
perimeter_spacing = get_default_perimeter_spacing ( * layer . object () -> print ());
return perimeter_spacing ;
}
// called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external()
static std :: pair < Polygons , Polygons > split_expolygon ( const ExPolygons & ex_polygons )
{
Polygons contours , holes ;
contours . reserve ( ex_polygons . size ());
holes . reserve ( std :: accumulate ( ex_polygons . begin (), ex_polygons . end (), size_t ( 0 ),
[]( 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 ));
}
// called by AvoidCrossingPerimeters::init_layer()
static ExPolygons get_boundary ( const Layer & layer )
{
const float perimeter_spacing = get_perimeter_spacing ( layer );
const float perimeter_offset = perimeter_spacing / 2.f ;
size_t polygons_count = 0 ;
for ( const LayerRegion * layer_region : layer . regions ())
polygons_count += layer_region -> slices . surfaces . size ();
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 );
ExPolygons perimeter_boundary = offset_ex ( boundary , - perimeter_offset );
ExPolygons result_boundary ;
if ( perimeter_boundary . size () != boundary . size ()) {
//FIXME ???
// 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 ,
offset_ex ( perimeter_boundary , perimeter_offset + float ( SCALED_EPSILON ) / 2.f )),
perimeter_offset + float ( SCALED_EPSILON ));
perimeter_boundary = offset_ex ( perimeter_boundary , perimeter_offset );
append ( perimeter_boundary , std :: move ( missing_perimeter_boundary ));
// By calling intersection_ex some artifacts arose by previous operations are removed.
result_boundary = intersection_ex ( offset_ex ( perimeter_boundary , - perimeter_offset ), boundary );
} else {
result_boundary = std :: move ( perimeter_boundary );
}
auto [ contours , holes ] = split_expolygon ( boundary );
// Add an outer boundary to avoid crossing perimeters from supports
ExPolygons outer_boundary = union_ex (
//FIXME flip order of offset and convex_hull
diff ( static_cast < Polygons > ( Geometry :: convex_hull ( offset ( contours , 2.f * perimeter_spacing ))),
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 );
// 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 );
return diff_ex ( result_boundary , offset_ex ( top_layer_polygons , - perimeter_offset ));
}
return result_boundary ;
}
// called by AvoidCrossingPerimeters::init_layer()
static ExPolygons get_boundary_external ( const Layer & layer )
{
const float perimeter_spacing = get_perimeter_spacing_external ( layer );
const float perimeter_offset = perimeter_spacing / 2.f ;
ExPolygons boundary ;
// Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer".
for ( const PrintObject * object : layer . object () -> print () -> objects ()) {
ExPolygons polygons_per_obj ;
//FIXME with different layering, layers on other objects will not be found at this object's print_z.
// Search an overlap of layers?
if ( const Layer * l = object -> get_layer_at_printz ( layer . print_z , EPSILON ); l )
for ( const LayerRegion * layer_region : l -> regions ())
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 . insert ( boundary . end (), polygons_per_obj . begin (), polygons_per_obj . end ());
for (; boundary_idx < boundary . size (); ++ boundary_idx )
boundary [ boundary_idx ]. translate ( instance . shift );
}
}
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 (
//FIXME flip order of offset and convex_hull
diff ( static_cast < Polygons > ( Geometry :: convex_hull ( offset ( contours , 2.f * perimeter_spacing ))),
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.
append ( result_boundary , union_ex ( diff ( offset ( holes , perimeter_spacing ), offset ( holes , perimeter_offset ))));
return union_ex ( result_boundary );
}
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 ())
2020-11-17 15:18:19 +01:00
//FIXME making copies?
2020-10-22 06:20:38 +02:00
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 ));
2020-11-17 15:18:19 +01:00
BoundingBox bbox ( get_extents ( m_boundaries ));
bbox . offset ( SCALED_EPSILON );
BoundingBox bbox_external = get_extents ( m_boundaries_external );
bbox_external . offset ( SCALED_EPSILON );
2020-10-16 09:03:49 +02:00
2020-11-17 15:18:19 +01:00
m_bbox = BoundingBoxf ( bbox . min . cast < double > (), bbox . max . cast < double > ());
m_bbox_external = BoundingBoxf ( bbox_external . min . cast < double > (), bbox_external . max . cast < double > ());
m_grid . set_bbox ( bbox );
//FIXME 1mm grid?
2020-10-28 12:57:45 +01:00
m_grid . create ( m_boundaries , coord_t ( scale_ ( 1. )));
2020-11-17 15:18:19 +01:00
m_grid_external . set_bbox ( bbox_external );
//FIXME 1mm grid?
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