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-29 13:58:36 +01:00
// Distance from the first point in the corresponding boundary
float distance ;
2020-11-17 10:42:27 +01:00
};
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
{
2020-11-29 13:58:36 +01:00
AllIntersectionsVisitor ( const EdgeGrid :: Grid & grid , std :: vector < Intersection > & intersections ) : grid ( grid ), intersections ( intersections )
{
intersection_set . reserve ( intersections . capacity ());
}
2020-11-17 10:42:27 +01:00
2020-11-29 13:58:36 +01:00
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-29 13:58:36 +01:00
{
intersection_set . reserve ( intersections . capacity ());
}
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-29 13:58:36 +01:00
// Visitor to check for any collision of a line segment with any contour stored inside the edge_grid.
struct FirstIntersectionVisitor
{
explicit FirstIntersectionVisitor ( 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 ;
};
// point_idx is the index from which is different vertex is searched.
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-29 13:58:36 +01:00
// Solve case when vertex on passed index point_idx is different that pass point. This helps the following code keep simple.
if ( point != polygon . points [ point_idx ])
return polygon . points [ point_idx ];
auto line_idx = ( int ( point_idx ) + 1 ) % int ( polygon . points . size ());
assert ( line_idx != int ( point_idx ));
2020-11-17 15:18:19 +01:00
if constexpr ( forward )
2020-11-29 13:58:36 +01:00
for (; point == polygon . points [ line_idx ] && line_idx != int ( point_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-29 13:58:36 +01:00
for (; point == polygon . points [ line_idx ] && line_idx != int ( point_idx ); line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int ( polygon . points . size ()) - 1 );
assert ( point != polygon . points [ line_idx ]);
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-29 13:58:36 +01:00
const size_t left_idx = prev_idx_modulo ( point_idx , polygon . points );
const size_t right_idx = next_idx_modulo ( point_idx , polygon . points );
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 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 };
2020-11-29 13:58:36 +01:00
// Returns a direction of the shortest path along the polygon boundary
static Direction get_shortest_direction ( const AvoidCrossingPerimeters :: Boundary & boundary ,
const Intersection & intersection_first ,
const Intersection & intersection_second ,
float contour_length )
2020-10-16 09:03:49 +02:00
{
2020-11-29 13:58:36 +01:00
assert ( intersection_first . border_idx == intersection_second . border_idx );
const Polygon & poly = boundary . boundaries [ intersection_first . border_idx ];
float dist_first = intersection_first . distance ;
float dist_second = intersection_second . distance ;
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
assert ( dist_first >= 0.f && dist_first <= contour_length );
assert ( dist_second >= 0.f && dist_second <= contour_length );
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
bool reversed = false ;
if ( dist_first > dist_second ) {
std :: swap ( dist_first , dist_second );
reversed = true ;
}
float total_length_forward = dist_second - dist_first ;
float total_length_backward = dist_first + contour_length - dist_second ;
if ( reversed ) std :: swap ( total_length_forward , total_length_backward );
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
total_length_forward -= ( intersection_first . point - poly [ intersection_first . line_idx ]). cast < double > (). norm ();
total_length_backward -= ( poly [( intersection_first . line_idx + 1 ) % poly . size ()] - intersection_first . point ). cast < double > (). norm ();
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
total_length_forward -= ( poly [( intersection_second . line_idx + 1 ) % poly . size ()] - intersection_second . point ). cast < double > (). norm ();
total_length_backward -= ( intersection_second . point - poly [ intersection_second . line_idx ]). cast < double > (). norm ();
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
if ( total_length_forward < total_length_backward ) return Direction :: Forward ;
return Direction :: Backward ;
2020-10-16 09:03:49 +02:00
}
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.
2020-11-29 13:58:36 +01:00
static std :: vector < TravelPoint > simplify_travel ( const AvoidCrossingPerimeters :: Boundary & boundary , const std :: vector < TravelPoint > & travel )
2020-10-16 09:03:49 +02:00
{
2020-11-29 13:58:36 +01:00
FirstIntersectionVisitor visitor ( boundary . grid );
2020-11-17 15:18:19 +01:00
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 ;
2020-11-29 13:58:36 +01:00
boundary . grid . visit_cells_intersecting_line ( * visitor . pt_current , * visitor . pt_next , visitor );
2020-11-17 15:18:19 +01:00
// 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().
2020-11-29 13:58:36 +01:00
static size_t avoid_perimeters_inner ( const GCode & gcodegen , const AvoidCrossingPerimeters :: Boundary & boundary ,
2020-11-17 15:18:19 +01:00
const Point & start ,
const Point & end ,
std :: vector < TravelPoint > & result_out )
{
2020-11-29 13:58:36 +01:00
const Polygons & boundaries = boundary . boundaries ;
const EdgeGrid :: Grid & edge_grid = boundary . grid ;
2020-11-17 15:18:19 +01:00
// Find all intersections between boundaries and the line segment, sort them along the line segment.
std :: vector < Intersection > intersections ;
{
2020-11-29 13:58:36 +01:00
intersections . reserve ( boundaries . size ());
2020-11-17 15:18:19 +01:00
AllIntersectionsVisitor visitor ( edge_grid , intersections , Line ( start , end ));
edge_grid . visit_cells_intersecting_line ( start , end , visitor );
Vec2d dir = ( end - start ). cast < double > ();
2020-11-29 13:58:36 +01:00
for ( Intersection & intersection : intersections )
intersection . distance = boundary . boundaries_params [ intersection . border_idx ][ intersection . line_idx ];
std :: sort ( intersections . begin (), intersections . end (), [ dir ]( const auto & l , const auto & r ) { return ( r . point - l . point ). template cast < double > (). dot ( dir ) > 0. ; });
}
std :: vector < TravelPoint > result ;
result . push_back ({ start , - 1 });
auto crossing_boundary_from_inside = [ & boundary ]( const Point & start , const Intersection & intersection ) {
const Polygon & poly = boundary . boundaries [ intersection . border_idx ];
Vec2d poly_line = Line ( poly [ intersection . line_idx ], poly [( intersection . line_idx + 1 ) % poly . size ()]). normal (). cast < double > ();
Vec2d intersection_vec = ( intersection . point - start ). cast < double > ();
return poly_line . normalized (). dot ( intersection_vec . normalized ()) >= 0 ;
};
for ( auto it_first = intersections . begin (); it_first != intersections . end (); ++ it_first ) {
// The entry point to the boundary polygon
const Intersection & intersection_first = * it_first ;
if ( ! crossing_boundary_from_inside ( start , intersection_first ))
continue ;
// 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 + 1 == boundaries [ intersection_first . border_idx ]. points . size () ? 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.
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 )});
// 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
const Intersection & intersection_second = * it_second ;
Direction shortest_direction = get_shortest_direction ( boundary , intersection_first , intersection_second ,
boundary . boundaries_params [ intersection_first . border_idx ]. back ());
// Append the path around the border into the path
if ( shortest_direction == Direction :: Forward )
for ( int line_idx = int ( intersection_first . line_idx ); line_idx != int ( intersection_second . line_idx );
line_idx = line_idx + 1 < int ( boundaries [ intersection_first . border_idx ]. size ()) ? line_idx + 1 : 0 )
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 )});
else
for ( int line_idx = int ( intersection_first . line_idx ); line_idx != int ( intersection_second . line_idx );
line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int ( boundaries [ intersection_first . border_idx ]. size ()) - 1 )
result . push_back ({ get_polygon_vertex_offset ( boundaries [ intersection_second . border_idx ], line_idx + 0 , coord_t ( SCALED_EPSILON )), int ( intersection_first . border_idx )});
// 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 );
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 )});
// Skip intersections in between
it_first = it_second ;
}
}
result . push_back ({ end , - 1 });
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0 ;
export_travel_to_svg ( boundaries , Line ( start , end ), result , intersections ,
debug_out_path ( "AvoidCrossingPerimetersInner-initial-%d.svg" , iRun ++ ));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
if ( ! intersections . empty ())
result = simplify_travel ( boundary , result );
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0 ;
export_travel_to_svg ( boundaries , Line ( start , end ), result , intersections ,
debug_out_path ( "AvoidCrossingPerimetersInner-final-%d.svg" , iRun ++ ));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
append ( result_out , std :: move ( result ));
return intersections . size ();
}
// Called by AvoidCrossingPerimeters::travel_to()
static size_t avoid_perimeters ( const GCode & gcodegen , const AvoidCrossingPerimeters :: Boundary & boundary ,
const Point & start ,
const Point & end ,
Polyline & result_out )
{
// Travel line is completely or partially inside the bounding box.
std :: vector < TravelPoint > path ;
size_t num_intersections = avoid_perimeters_inner ( gcodegen , boundary , start , end , path );
result_out = to_polyline ( path );
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
{
static int iRun = 0 ;
export_travel_to_svg ( boundary . boundaries , Line ( start , end ), path , {}, debug_out_path ( "AvoidCrossingPerimeters-final-%d.svg" , iRun ++ ));
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
return num_intersections ;
}
// Check if anyone of ExPolygons contains whole travel.
// called by need_wipe() and AvoidCrossingPerimeters::travel_to()
// FIXME Lukas H.: Maybe similar approach could also be used for ExPolygon::contains()
static bool any_expolygon_contains ( const ExPolygons & ex_polygons ,
const std :: vector < BoundingBox > & ex_polygons_bboxes ,
const EdgeGrid :: Grid & grid_lslice ,
const Line & travel )
{
assert ( ex_polygons . size () == ex_polygons_bboxes . size ());
if ( ! grid_lslice . bbox (). contains ( travel . a ) || ! grid_lslice . bbox (). contains ( travel . b ))
return false ;
FirstIntersectionVisitor visitor ( grid_lslice );
visitor . pt_current = & travel . a ;
visitor . pt_next = & travel . b ;
grid_lslice . visit_cells_intersecting_line ( * visitor . pt_current , * visitor . pt_next , visitor );
if ( ! visitor . intersect ) {
for ( const ExPolygon & ex_polygon : ex_polygons ) {
const BoundingBox & bbox = ex_polygons_bboxes [ & ex_polygon - & ex_polygons . front ()];
if ( bbox . contains ( travel . a ) && bbox . contains ( travel . b ) && ex_polygon . contains ( travel . a ))
return true ;
}
}
return false ;
}
// Check if anyone of ExPolygons contains whole travel.
// called by need_wipe()
static bool any_expolygon_contains ( const ExPolygons & ex_polygons , const std :: vector < BoundingBox > & ex_polygons_bboxes , const EdgeGrid :: Grid & grid_lslice , const Polyline & travel )
{
assert ( ex_polygons . size () == ex_polygons_bboxes . size ());
if ( std :: any_of ( travel . points . begin (), travel . points . end (), [ & grid_lslice ]( const Point & point ) { return ! grid_lslice . bbox (). contains ( point ); }))
return false ;
FirstIntersectionVisitor visitor ( grid_lslice );
bool any_intersection = false ;
for ( size_t line_idx = 1 ; line_idx < travel . size (); ++ line_idx ) {
visitor . pt_current = & travel . points [ line_idx - 1 ];
visitor . pt_next = & travel . points [ line_idx ];
grid_lslice . visit_cells_intersecting_line ( * visitor . pt_current , * visitor . pt_next , visitor );
any_intersection = visitor . intersect ;
if ( any_intersection ) break ;
}
if ( ! any_intersection ) {
for ( const ExPolygon & ex_polygon : ex_polygons ) {
const BoundingBox & bbox = ex_polygons_bboxes [ & ex_polygon - & ex_polygons . front ()];
if ( std :: all_of ( travel . points . begin (), travel . points . end (), [ & bbox ]( const Point & point ) { return bbox . contains ( point ); }) &&
ex_polygon . contains ( travel . points . front ()))
return true ;
}
}
return false ;
}
static bool need_wipe ( const GCode & gcodegen ,
const EdgeGrid :: Grid & grid_lslice ,
const Line & original_travel ,
const Polyline & result_travel ,
const size_t intersection_count )
{
const ExPolygons & lslices = gcodegen . layer () -> lslices ;
const std :: vector < BoundingBox > & lslices_bboxes = gcodegen . layer () -> lslices_bboxes ;
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 ( lslices , lslices_bboxes , grid_lslice , original_travel )) {
// 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
wipe_needed = ! ( result_travel . size () > 2 && result_travel . first_point () == original_travel . a && result_travel . last_point () == original_travel . b ) &&
! any_expolygon_contains ( lslices , lslices_bboxes , grid_lslice , result_travel );
} else {
wipe_needed = true ;
}
} else {
wipe_needed = ! any_expolygon_contains ( lslices , lslices_bboxes , grid_lslice , result_travel );
}
}
return wipe_needed ;
}
// called by get_perimeter_spacing() / get_perimeter_spacing_external()
static inline float get_default_perimeter_spacing ( const PrintObject & print_object )
{
std :: vector < unsigned int > printing_extruders = print_object . object_extruders ();
assert ( ! printing_extruders . empty ());
float avg_extruder = 0 ;
for ( unsigned int extruder_id : printing_extruders )
avg_extruder += scale_ ( print_object . print () -> config (). nozzle_diameter . get_at ( extruder_id ));
avg_extruder /= printing_extruders . size ();
return avg_extruder ;
}
// called by get_boundary()
static float get_perimeter_spacing ( const Layer & layer )
{
size_t regions_count = 0 ;
float perimeter_spacing = 0.f ;
for ( const LayerRegion * layer_region : layer . regions ())
if ( layer_region != nullptr && ! layer_region -> slices . empty ()) {
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 ());
return perimeter_spacing ;
}
// Adds points around all vertices so that the offset affects only small sections around these vertices.
static void resample_polygon ( Polygon & polygon , double dist_from_vertex )
{
Points resampled_poly ;
resampled_poly . reserve ( 3 * polygon . size ());
resampled_poly . emplace_back ( polygon . first_point ());
for ( size_t pt_idx = 1 ; pt_idx < polygon . size (); ++ pt_idx ) {
const Point & p1 = polygon [ pt_idx - 1 ];
const Point & p2 = polygon [ pt_idx ];
double line_length = ( p2 - p1 ). cast < double > (). norm ();
Vector line_vec = (( p2 - p1 ). cast < double > (). normalized () * dist_from_vertex ). cast < coord_t > ();
if ( line_length > 2 * dist_from_vertex ) {
resampled_poly . emplace_back ( p1 + line_vec );
resampled_poly . emplace_back ( p2 - line_vec );
}
resampled_poly . emplace_back ( polygon [ pt_idx ]);
}
polygon . points = std :: move ( resampled_poly );
}
static void resample_expolygon ( ExPolygon & ex_polygon , double dist_from_vertex )
{
resample_polygon ( ex_polygon . contour , dist_from_vertex );
for ( Polygon & polygon : ex_polygon . holes ) resample_polygon ( polygon , dist_from_vertex );
}
static void resample_expolygons ( ExPolygons & ex_polygons , double dist_from_vertex )
{
for ( ExPolygon & ex_poly : ex_polygons ) resample_expolygon ( ex_poly , dist_from_vertex );
}
static void precompute_polygon_distances ( const Polygon & polygon , std :: vector < float > & polygon_distances_out )
{
polygon_distances_out . assign ( polygon . size () + 1 , 0.f );
for ( size_t point_idx = 1 ; point_idx < polygon . size (); ++ point_idx )
polygon_distances_out [ point_idx ] = polygon_distances_out [ point_idx - 1 ] + ( polygon [ point_idx ]. cast < float > () - polygon [ point_idx - 1 ]. cast < float > ()). norm ();
polygon_distances_out . back () = polygon_distances_out [ polygon . size () - 1 ] + ( polygon . last_point (). cast < float > () - polygon . first_point (). cast < float > ()). norm ();
}
static void precompute_expolygon_distances ( const ExPolygon & ex_polygon , std :: vector < std :: vector < float >> & expolygon_distances_out )
{
expolygon_distances_out . assign ( ex_polygon . holes . size () + 1 , std :: vector < float > ());
precompute_polygon_distances ( ex_polygon . contour , expolygon_distances_out . front ());
for ( size_t hole_idx = 0 ; hole_idx < ex_polygon . holes . size (); ++ hole_idx )
precompute_polygon_distances ( ex_polygon . holes [ hole_idx ], expolygon_distances_out [ hole_idx + 1 ]);
}
// It is highly based on the function contour_distance2 from the ElephantFootCompensation.cpp
static std :: vector < float > contour_distance ( const EdgeGrid :: Grid & grid ,
const std :: vector < float > & poly_distances ,
const size_t contour_idx ,
const Polygon & polygon ,
double compensation ,
double search_radius )
{
assert ( ! polygon . empty ());
assert ( polygon . size () >= 2 );
std :: vector < float > out ;
if ( polygon . size () > 2 )
{
struct Visitor {
Visitor ( const EdgeGrid :: Grid & grid , const size_t contour_idx , const std :: vector < float > & polygon_distances , double dist_same_contour_accept , double dist_same_contour_reject ) :
grid ( grid ), idx_contour ( contour_idx ), contour ( * grid . contours ()[ contour_idx ]), boundary_parameters ( polygon_distances ), dist_same_contour_accept ( dist_same_contour_accept ), dist_same_contour_reject ( dist_same_contour_reject ) {}
void init ( const Points & contour , const Point & apoint )
{
this -> idx_point = & apoint - contour . data ();
this -> point = apoint ;
this -> found = false ;
this -> dir_inside = this -> dir_inside_at_point ( contour , this -> idx_point );
this -> distance = std :: numeric_limits < double >:: max ();
}
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 = this -> grid . cell_data_range ( iy , ix );
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.
std :: pair < const Point & , const Point &> segment = this -> grid . segment ( * it_contour_and_segment );
const Vec2d v = ( segment . second - segment . first ). cast < double > ();
const Vec2d va = ( this -> point - segment . first ). cast < double > ();
const double l2 = v . squaredNorm (); // avoid a sqrt
const double t = ( l2 == 0.0 ) ? 0. : clamp ( 0. , 1. , va . dot ( v ) / l2 );
// Closest point from this->point to the segment.
const Vec2d foot = segment . first . cast < double > () + t * v ;
const Vec2d bisector = foot - this -> point . cast < double > ();
const double dist = bisector . norm ();
if (( ! this -> found || dist < this -> distance ) && this -> dir_inside . dot ( bisector ) > 0 ) {
bool accept = true ;
if ( it_contour_and_segment -> first == idx_contour ) {
// Complex case: The closest segment originates from the same contour as the starting point.
// Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector
// (this->pt, foot).
const Slic3r :: Points & ipts = * grid . contours ()[ it_contour_and_segment -> first ];
double param_lo = boundary_parameters [ this -> idx_point ];
double param_hi = t * sqrt ( l2 );
double param_end = boundary_parameters . back ();
const size_t ipt = it_contour_and_segment -> second ;
if ( ipt + 1 < ipts . size ())
param_hi += boundary_parameters [ ipt > 0 ? ipt - 1 : 0 ];
if ( param_lo > param_hi )
std :: swap ( param_lo , param_hi );
assert ( param_lo > - SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON );
assert ( param_hi > - SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON );
double dist_along_contour = std :: min ( param_hi - param_lo , param_lo + param_end - param_hi );
if ( dist_along_contour < dist_same_contour_accept )
accept = false ;
else if ( dist < dist_same_contour_reject + SCALED_EPSILON ) {
// this->point is close to foot. This point will only be accepted if the path along the contour is significantly
// longer than the bisector. That is, the path shall not bulge away from the bisector too much.
// Bulge is estimated by 0.6 of the circle circumference drawn around the bisector.
// Test whether the contour is convex or concave.
bool inside = ( t == 0. ) ? this -> inside_corner ( ipts , ipt , this -> point ) :
( t == 1. ) ? this -> inside_corner ( ipts , ipt + 1 == ipts . size () ? 0 : ipt + 1 , this -> point ) :
this -> left_of_segment ( ipts , ipt , this -> point );
accept = inside && dist_along_contour > 0.6 * M_PI * dist ;
}
}
if ( accept && ( ! this -> found || dist < this -> distance )) {
// Simple case: Just measure the shortest distance.
this -> distance = dist ;
this -> found = true ;
}
}
}
// Continue traversing the grid.
return true ;
}
const EdgeGrid :: Grid & grid ;
const size_t idx_contour ;
const Points & contour ;
const std :: vector < float > & boundary_parameters ;
const double dist_same_contour_accept ;
const double dist_same_contour_reject ;
size_t idx_point ;
Point point ;
// Direction inside the contour from idx_point, not normalized.
Vec2d dir_inside ;
bool found ;
double distance ;
private :
static Vec2d dir_inside_at_point ( const Points & contour , size_t i )
{
size_t iprev = prev_idx_modulo ( i , contour );
size_t inext = next_idx_modulo ( i , contour );
Vec2d v1 = ( contour [ i ] - contour [ iprev ]). cast < double > ();
Vec2d v2 = ( contour [ inext ] - contour [ i ]). cast < double > ();
return Vec2d ( - v1 . y () - v2 . y (), v1 . x () + v2 . x ());
}
static bool inside_corner ( const Slic3r :: Points & contour , size_t i , const Point & pt_oposite )
{
const Vec2d pt = pt_oposite . cast < double > ();
size_t iprev = prev_idx_modulo ( i , contour );
size_t inext = next_idx_modulo ( i , contour );
Vec2d v1 = ( contour [ i ] - contour [ iprev ]). cast < double > ();
Vec2d v2 = ( contour [ inext ] - contour [ i ]). cast < double > ();
bool left_of_v1 = cross2 ( v1 , pt - contour [ iprev ]. cast < double > ()) > 0. ;
bool left_of_v2 = cross2 ( v2 , pt - contour [ i ]. cast < double > ()) > 0. ;
return cross2 ( v1 , v2 ) > 0 ? left_of_v1 && left_of_v2 : // convex corner
left_of_v1 || left_of_v2 ; // concave corner
}
static bool left_of_segment ( const Slic3r :: Points & contour , size_t i , const Point & pt_oposite )
{
const Vec2d pt = pt_oposite . cast < double > ();
size_t inext = next_idx_modulo ( i , contour );
Vec2d v = ( contour [ inext ] - contour [ i ]). cast < double > ();
return cross2 ( v , pt - contour [ i ]. cast < double > ()) > 0. ;
}
} visitor ( grid , contour_idx , poly_distances , 0.5 * compensation * M_PI , search_radius );
out . reserve ( polygon . size ());
Point radius_vector ( search_radius , search_radius );
for ( const Point & pt : polygon . points ) {
visitor . init ( polygon . points , pt );
grid . visit_cells_intersecting_box ( BoundingBox ( pt - radius_vector , pt + radius_vector ), visitor );
out . emplace_back ( float ( visitor . found ? std :: min ( visitor . distance , search_radius ) : search_radius ));
}
}
return out ;
}
// Polygon offset which ensures that if a polygon breaks up into several separate parts, the original polygon will be used in these places.
static ExPolygons inner_offset ( const ExPolygons & ex_polygons , double offset , double min_contour_width = scale_ ( 0.001 ))
{
double search_radius = 2. * ( offset + min_contour_width );
ExPolygons ex_poly_result = ex_polygons ;
resample_expolygons ( ex_poly_result , offset / 2 );
for ( ExPolygon & ex_poly : ex_poly_result ) {
BoundingBox bbox ( get_extents ( ex_poly ));
bbox . offset ( SCALED_EPSILON );
EdgeGrid :: Grid grid ;
grid . set_bbox ( bbox );
grid . create ( ex_poly , coord_t ( 0.7 * search_radius ));
std :: vector < std :: vector < float >> ex_poly_distances ;
precompute_expolygon_distances ( ex_poly , ex_poly_distances );
std :: vector < std :: vector < float >> offsets ;
offsets . reserve ( ex_poly . holes . size () + 1 );
for ( size_t idx_contour = 0 ; idx_contour <= ex_poly . holes . size (); ++ idx_contour ) {
const Polygon & poly = ( idx_contour == 0 ) ? ex_poly . contour : ex_poly . holes [ idx_contour - 1 ];
assert ( poly . is_counter_clockwise () == ( idx_contour == 0 ));
std :: vector < float > distances = contour_distance ( grid , ex_poly_distances [ idx_contour ], idx_contour , poly , offset , search_radius );
for ( float & distance : distances ) {
if ( distance < min_contour_width )
distance = 0.f ;
else if ( distance > min_contour_width + 2. * offset )
distance = - float ( offset );
else
distance = - ( distance - float ( min_contour_width )) / 2.f ;
}
offsets . emplace_back ( distances );
}
ExPolygons offset_ex_poly = variable_offset_inner_ex ( ex_poly , offsets );
// If variable_offset_inner_ex produces empty result, then original ex_polygon is used
if ( offset_ex_poly . size () == 1 ) {
ex_poly = std :: move ( offset_ex_poly . front ());
} else if ( offset_ex_poly . size () > 1 ) {
// fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces
// tiny artefacts polygons, so these artefacts are removed.
double max_area = offset_ex_poly . front (). area ();
size_t max_area_idx = 0 ;
for ( size_t poly_idx = 1 ; poly_idx < offset_ex_poly . size (); ++ poly_idx ) {
double area = offset_ex_poly [ poly_idx ]. area ();
if ( max_area < area ) {
max_area = area ;
max_area_idx = poly_idx ;
}
}
ex_poly = std :: move ( offset_ex_poly [ max_area_idx ]);
}
}
return ex_poly_result ;
}
// called by AvoidCrossingPerimeters::travel_to()
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 = union_ex ( inner_offset ( layer . lslices , perimeter_offset ));
// 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 ( boundary , offset_ex ( top_layer_polygons , - perimeter_offset ));
}
return boundary ;
}
static void init_boundary_distances ( AvoidCrossingPerimeters :: Boundary * boundary )
{
boundary -> boundaries_params . assign ( boundary -> boundaries . size (), std :: vector < float > ());
for ( size_t poly_idx = 0 ; poly_idx < boundary -> boundaries . size (); ++ poly_idx )
precompute_polygon_distances ( boundary -> boundaries [ poly_idx ], boundary -> boundaries_params [ poly_idx ]);
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters :: travel_to ( const GCode & gcodegen , const Point & point , bool * could_be_wipe_disabled )
{
// 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 = m_use_external_mp || m_use_external_mp_once ;
Point scaled_origin = use_external ? Point :: new_scale ( gcodegen . origin ()( 0 ), gcodegen . origin ()( 1 )) : Point ( 0 , 0 );
const Point start = gcodegen . last_pos () + scaled_origin ;
const Point end = point + scaled_origin ;
const Line travel ( start , end );
Polyline result_pl ;
size_t travel_intersection_count = 0 ;
Vec2d startf = start . cast < double > ();
Vec2d endf = end . cast < double > ();
if ( ! use_external && ! any_expolygon_contains ( gcodegen . layer () -> lslices , gcodegen . layer () -> lslices_bboxes , m_grid_lslice , travel )) {
// Initialize m_internal only when it is necessary.
if ( m_internal . boundaries . empty ()) {
m_internal . boundaries_params . clear ();
m_internal . boundaries = to_polygons ( get_boundary ( * gcodegen . layer ()));
BoundingBox bbox ( get_extents ( m_internal . boundaries ));
bbox . offset ( SCALED_EPSILON );
m_internal . bbox = BoundingBoxf ( bbox . min . cast < double > (), bbox . max . cast < double > ());
m_internal . grid . set_bbox ( bbox );
// FIXME 1mm grid?
m_internal . grid . create ( m_internal . boundaries , coord_t ( scale_ ( 1. )));
init_boundary_distances ( & m_internal );
}
// Trim the travel line by the bounding box.
if ( Geometry :: liang_barsky_line_clipping ( startf , endf , m_internal . bbox )) {
travel_intersection_count = avoid_perimeters ( gcodegen , m_internal , startf . cast < coord_t > (), endf . cast < coord_t > (), result_pl );
result_pl . points . front () = start ;
result_pl . points . back () = end ;
} else {
// Travel line is completely outside the bounding box.
result_pl = { start , end };
travel_intersection_count = 0 ;
}
} else {
// Travel line is completely outside the bounding box.
result_pl = { start , end };
travel_intersection_count = 0 ;
}
double max_detour_length scale_ ( gcodegen . config (). avoid_crossing_perimeters_max_detour );
if ( max_detour_length > 0 && ( result_pl . length () - travel . length ()) > max_detour_length )
result_pl = { start , end };
if ( use_external ) {
result_pl . translate ( - scaled_origin );
* could_be_wipe_disabled = false ;
} else
* could_be_wipe_disabled = ! need_wipe ( gcodegen , m_grid_lslice , travel , result_pl , travel_intersection_count );
return result_pl ;
}
// ************************************* AvoidCrossingPerimeters::init_layer() *****************************************
void AvoidCrossingPerimeters :: init_layer ( const Layer & layer )
{
m_internal . boundaries . clear ();
m_internal . boundaries_params . clear ();
BoundingBox bbox_slice ( get_extents ( layer . lslices ));
bbox_slice . offset ( SCALED_EPSILON );
m_grid_lslice . set_bbox ( bbox_slice );
//FIXME 1mm grid?
m_grid_lslice . create ( layer . lslices , coord_t ( scale_ ( 1. )));
}
#if 0
static double travel_length(const std::vector<TravelPoint> &travel) {
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;
}
// Called by avoid_perimeters() and by simplify_travel_heuristics().
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start,
const Point &end,
std::vector<TravelPoint> &result_out)
{
const Polygons &boundaries = boundary.boundaries;
const EdgeGrid::Grid &edge_grid = boundary.grid;
// Find all intersections between boundaries and the line segment, sort them along the line segment.
std::vector<Intersection> intersections;
{
intersections.reserve(boundaries.size());
AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end));
edge_grid.visit_cells_intersecting_line(start, end, visitor);
Vec2d dir = (end - start).cast<double>();
for (Intersection &intersection : intersections)
intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx];
2020-11-17 15:18:19 +01:00
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;
2020-11-20 11:56:40 +01:00
size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1;
2020-10-28 01:52:50 +01:00
// 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
2020-11-20 11:56:40 +01:00
auto it_second = it_second_r.base() - 1;
2020-10-28 01:52:50 +01:00
// The exit point from the boundary polygon
2020-10-16 09:03:49 +02:00
const Intersection &intersection_second = *it_second;
2020-11-29 13:58:36 +01:00
Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second,
boundary.boundaries_params[intersection_first.border_idx].back());
2020-10-28 01:52:50 +01:00
// 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-11-29 13:58:36 +01:00
line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].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-11-29 13:58:36 +01:00
line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].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())
2020-11-29 13:58:36 +01:00
result = simplify_travel(boundary, 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-29 13:58:36 +01:00
static std::vector<TravelPoint> simplify_travel_heuristics(const AvoidCrossingPerimeters::Boundary &boundary,
const std::vector<TravelPoint> &travel)
2020-11-17 10:42:27 +01:00
{
std::vector<TravelPoint> simplified_path;
std::vector<Intersection> intersections;
2020-11-29 13:58:36 +01:00
AllIntersectionsVisitor visitor(boundary.grid, intersections);
2020-11-17 10:42:27 +01:00
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 ¤t = 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;
2020-11-29 13:58:36 +01:00
boundary.grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor);
2020-11-17 10:42:27 +01:00
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-29 13:58:36 +01:00
avoid_perimeters_inner(boundary, 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()
2020-11-29 13:58:36 +01:00
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary,
2020-11-17 15:18:19 +01:00
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;
2020-11-29 13:58:36 +01:00
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path);
2020-11-17 15:18:19 +01:00
if (num_intersections) {
2020-11-29 13:58:36 +01:00
path = simplify_travel_heuristics(boundary, path);
2020-11-17 15:18:19 +01:00
std::reverse(path.begin(), path.end());
2020-11-29 13:58:36 +01:00
path = simplify_travel_heuristics(boundary, path);
2020-11-17 15:18:19 +01:00
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;
}
2020-10-22 06:20:38 +02:00
// 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.
2020-11-29 13:58:36 +01:00
if (Geometry::liang_barsky_line_clipping(startf, endf, (use_external ? m_external : m_internal).bbox)) {
2020-11-17 15:18:19 +01:00
// Travel line is completely or partially inside the bounding box.
2020-11-20 11:56:40 +01:00
//FIXME initialize m_boundaries / m_boundaries_external on demand?
2020-11-29 13:58:36 +01:00
travel_intersection_count = avoid_perimeters((use_external ? m_external : m_internal), 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-29 13:58:36 +01:00
*could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, 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
// 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?
2020-11-29 13:58:36 +01:00
if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
for (const LayerRegion *layer_region : l->regions())
if (layer_region != nullptr && !layer_region->slices.empty()) {
perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing();
++ regions_count;
}
2020-11-17 15:18:19 +01:00
assert(perimeter_spacing >= 0.f);
if (regions_count != 0)
perimeter_spacing /= float(regions_count);
else
2020-11-29 13:58:36 +01:00
perimeter_spacing = get_default_perimeter_spacing(*layer.object());
2020-11-17 15:18:19 +01:00
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(
2020-11-29 13:58:36 +01:00
diff(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing), offset(contours, perimeter_spacing + perimeter_offset)));
2020-11-17 15:18:19 +01:00
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?
2020-11-29 13:58:36 +01:00
if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l)
2020-11-17 15:18:19 +01:00
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.
2020-11-20 11:56:40 +01:00
ExPolygons result_boundary =
2020-11-29 13:58:36 +01:00
diff_ex(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing),offset(contours, perimeter_spacing + perimeter_offset));
2020-11-17 15:18:19 +01:00
// All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed.
2020-11-20 11:56:40 +01:00
append(result_boundary, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset)));
2020-11-17 15:18:19 +01:00
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-11-29 13:58:36 +01:00
m_internal.boundaries.clear();
m_external.boundaries.clear();
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
m_internal.boundaries = to_polygons(get_boundary(layer));
m_external.boundaries = to_polygons(get_boundary_external(layer));
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
BoundingBox bbox(get_extents(m_internal.boundaries));
2020-11-17 15:18:19 +01:00
bbox.offset(SCALED_EPSILON);
2020-11-29 13:58:36 +01:00
BoundingBox bbox_external = get_extents(m_external.boundaries);
2020-11-17 15:18:19 +01:00
bbox_external.offset(SCALED_EPSILON);
2020-11-29 13:58:36 +01:00
BoundingBox bbox_slice(get_extents(layer.lslices));
bbox_slice.offset(SCALED_EPSILON);
2020-10-16 09:03:49 +02:00
2020-11-29 13:58:36 +01:00
m_internal.bbox = BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>());
m_external.bbox = BoundingBoxf(bbox_external.min.cast<double>(), bbox_external.max.cast<double>());
2020-11-17 15:18:19 +01:00
2020-11-29 13:58:36 +01:00
m_internal.grid.set_bbox(bbox);
//FIX1ME 1mm grid?
m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.)));
m_external.grid.set_bbox(bbox_external);
//FIX1ME 1mm grid?
m_external.grid.create(m_external.boundaries, coord_t(scale_(1.)));
m_grid_lslice.set_bbox(bbox_slice);
//FIX1ME 1mm grid?
m_grid_lslice.create(layer.lslices, coord_t(scale_(1.)));
init_boundary_distances(&m_internal);
init_boundary_distances(&m_external);
2020-10-16 09:03:49 +02:00
}
2020-11-29 13:58:36 +01:00
#endif
2020-10-16 09:03:49 +02:00
} // namespace Slic3r