2022-07-15 15:37:19 +00:00
# include <math.h>
# include "MinimumSpanningTree.hpp"
# include "TreeSupport.hpp"
# include "Print.hpp"
# include "Layer.hpp"
# include "Fill/FillBase.hpp"
# include "CurveAnalyzer.hpp"
# include "SVG.hpp"
# include "ShortestPath.hpp"
# include "I18N.hpp"
2022-07-22 09:46:10 +00:00
# include <libnest2d/backends/libslic3r/geometries.hpp>
2022-07-15 15:37:19 +00:00
2022-07-06 04:16:37 +00:00
# include "Fill/FillBase.hpp"
2022-07-15 15:37:19 +00:00
# define _L(s) Slic3r::I18N::translate(s)
2022-12-07 12:32:33 +00:00
# define USE_PLAN_LAYER_HEIGHTS 1
2022-07-15 15:37:19 +00:00
# define HEIGHT_TO_SWITCH_INFILL_DIRECTION 30 // change infill direction every 20mm
# ifndef M_PI
# define M_PI 3.1415926535897932384626433832795
# endif
# ifndef SIGN
# define SIGN(x) (x>=0?1:-1)
# endif
# define TAU (2.0 * M_PI)
# define NO_INDEX (std::numeric_limits<unsigned int>::max())
2022-11-30 03:51:25 +00:00
// #define SUPPORT_TREE_DEBUG_TO_SVG
2022-11-30 03:51:25 +00:00
2022-07-15 15:37:19 +00:00
namespace Slic3r
{
# define unscale_(val) ((val) * SCALING_FACTOR)
# define FIRST_LAYER_EXPANSION 1.2
inline unsigned int round_divide ( unsigned int dividend , unsigned int divisor ) //!< Return dividend divided by divisor rounded to the nearest integer
{
return ( dividend + divisor / 2 ) / divisor ;
}
inline unsigned int round_up_divide ( unsigned int dividend , unsigned int divisor ) //!< Return dividend divided by divisor rounded to the nearest integer
{
return ( dividend + divisor - 1 ) / divisor ;
}
inline double dot_with_unscale ( const Point a , const Point b )
{
return unscale_ ( a ( 0 ) ) * unscale_ ( b ( 0 ) ) + unscale_ ( a ( 1 ) ) * unscale_ ( b ( 1 ) ) ;
}
inline double vsize2_with_unscale ( const Point pt )
{
return dot_with_unscale ( pt , pt ) ;
}
inline Point turn90_ccw ( const Point pt )
{
Point ret ;
ret ( 0 ) = - pt ( 1 ) ;
ret ( 1 ) = pt ( 0 ) ;
return ret ;
}
inline Point normal ( Point pt , double scale )
{
double length = scale_ ( sqrt ( vsize2_with_unscale ( pt ) ) ) ;
return pt * ( scale / length ) ;
}
enum TreeSupportStage {
STAGE_DETECT_OVERHANGS ,
STAGE_GENERATE_CONTACT_NODES ,
STAGE_DROP_DOWN_NODES ,
STAGE_DRAW_CIRCLES ,
STAGE_GENERATE_TOOLPATHS ,
STAGE_MinimumSpanningTree ,
STAGE_GET_AVOIDANCE ,
STAGE_projection_onto_ex ,
STAGE_get_collision ,
STAGE_intersection_ln ,
STAGE_total ,
NUM_STAGES
} ;
class TreeSupportProfiler
{
public :
uint32_t stage_durations [ NUM_STAGES ] ;
uint32_t stage_index = 0 ;
boost : : posix_time : : ptime tic_time ;
boost : : posix_time : : ptime toc_time ;
TreeSupportProfiler ( )
{
for ( uint32_t & item : stage_durations ) {
item = 0 ;
}
}
void stage_start ( TreeSupportStage stage )
{
if ( stage > NUM_STAGES )
return ;
m_stage_start_times [ stage ] = boost : : posix_time : : microsec_clock : : local_time ( ) ;
}
void stage_finish ( TreeSupportStage stage )
{
if ( stage > NUM_STAGES )
return ;
boost : : posix_time : : ptime time = boost : : posix_time : : microsec_clock : : local_time ( ) ;
stage_durations [ stage ] = ( time - m_stage_start_times [ stage ] ) . total_milliseconds ( ) ;
}
void tic ( ) { tic_time = boost : : posix_time : : microsec_clock : : local_time ( ) ; }
void toc ( ) { toc_time = boost : : posix_time : : microsec_clock : : local_time ( ) ; }
void stage_add ( TreeSupportStage stage , bool do_toc = false )
{
if ( stage > NUM_STAGES )
return ;
if ( do_toc )
toc_time = boost : : posix_time : : microsec_clock : : local_time ( ) ;
stage_durations [ stage ] + = ( toc_time - tic_time ) . total_milliseconds ( ) ;
}
std : : string report ( )
{
std : : stringstream ss ;
ss < < " total overhange cost: " < < stage_durations [ STAGE_total ]
< < " ; STAGE_DETECT_OVERHANGS: " < < stage_durations [ STAGE_DETECT_OVERHANGS ]
< < " ; STAGE_GENERATE_CONTACT_NODES: " < < stage_durations [ STAGE_GENERATE_CONTACT_NODES ]
< < " ; STAGE_DROP_DOWN_NODES: " < < stage_durations [ STAGE_DROP_DOWN_NODES ]
< < " ; STAGE_DRAW_CIRCLES: " < < stage_durations [ STAGE_DRAW_CIRCLES ]
< < " ; STAGE_GENERATE_TOOLPATHS: " < < stage_durations [ STAGE_GENERATE_TOOLPATHS ]
< < " ; STAGE_MinimumSpanningTree: " < < stage_durations [ STAGE_MinimumSpanningTree ]
< < " ; STAGE_GET_AVOIDANCE: " < < stage_durations [ STAGE_GET_AVOIDANCE ]
< < " ; STAGE_projection_onto_ex: " < < stage_durations [ STAGE_projection_onto_ex ]
< < " ; STAGE_get_collision: " < < stage_durations [ STAGE_get_collision ]
< < " ; STAGE_intersection_ln: " < < stage_durations [ STAGE_intersection_ln ] ;
return ss . str ( ) ;
}
private :
boost : : posix_time : : ptime m_stage_start_times [ NUM_STAGES ] ;
} ;
TreeSupportProfiler profiler ;
Lines spanning_tree_to_lines ( const std : : vector < MinimumSpanningTree > & spanning_trees )
{
Lines polylines ;
for ( const MinimumSpanningTree & mst : spanning_trees ) {
std : : vector < Point > points = mst . vertices ( ) ;
std : : unordered_set < Point , PointHash > to_ignore ;
for ( Point pt1 : points ) {
if ( to_ignore . find ( pt1 ) ! = to_ignore . end ( ) )
continue ;
const std : : vector < Point > & neighbours = mst . adjacent_nodes ( pt1 ) ;
if ( neighbours . empty ( ) )
continue ;
for ( Point pt2 : neighbours ) {
if ( to_ignore . find ( pt2 ) ! = to_ignore . end ( ) )
continue ;
Line line ( pt1 , pt2 ) ;
polylines . push_back ( line ) ;
}
to_ignore . insert ( pt1 ) ;
}
}
return polylines ;
}
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
static std : : string get_svg_filename ( std : : string layer_nr_or_z , std : : string tag = " bbl_ts " )
{
static bool rand_init = false ;
if ( ! rand_init ) {
srand ( time ( NULL ) ) ;
rand_init = true ;
}
int rand_num = rand ( ) % 1000000 ;
//makedir("./SVG");
std : : string prefix = " ./SVG/ " ;
std : : string suffix = " .svg " ;
return prefix + tag + " _ " + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix ;
}
static void draw_contours_and_nodes_to_svg
(
2022-12-07 12:32:33 +00:00
std : : string layer_nr_or_z ,
2022-07-15 15:37:19 +00:00
const ExPolygons & overhangs ,
const ExPolygons & overhangs_after_offset ,
const ExPolygons & outlines_below ,
const std : : vector < TreeSupport : : Node * > & layer_nodes ,
const std : : vector < TreeSupport : : Node * > & lower_layer_nodes ,
std : : string name_prefix ,
std : : vector < std : : string > legends = { " overhang " , " avoid " , " outlines " } , std : : vector < std : : string > colors = { " blue " , " red " , " yellow " }
)
{
BoundingBox bbox = get_extents ( overhangs ) ;
bbox . merge ( get_extents ( overhangs_after_offset ) ) ;
bbox . merge ( get_extents ( outlines_below ) ) ;
Points layer_pts ;
for ( TreeSupport : : Node * node : layer_nodes ) {
layer_pts . push_back ( node - > position ) ;
}
bbox . merge ( get_extents ( layer_pts ) ) ;
bbox . inflated ( scale_ ( 1 ) ) ;
bbox . max . x ( ) = std : : max ( bbox . max . x ( ) , ( coord_t ) scale_ ( 10 ) ) ;
bbox . max . y ( ) = std : : max ( bbox . max . y ( ) , ( coord_t ) scale_ ( 10 ) ) ;
2022-07-22 09:46:10 +00:00
SVG svg ;
2022-12-07 12:32:33 +00:00
if ( ! layer_nr_or_z . empty ( ) )
svg . open ( get_svg_filename ( layer_nr_or_z , name_prefix ) , bbox ) ;
2022-07-22 09:46:10 +00:00
else
svg . open ( name_prefix , bbox ) ;
2022-07-15 15:37:19 +00:00
if ( ! svg . is_opened ( ) ) return ;
// draw grid
svg . draw_grid ( bbox , " gray " , coord_t ( scale_ ( 0.05 ) ) ) ;
// draw overhang areas
2022-07-22 09:46:10 +00:00
svg . draw_outline ( union_ex ( overhangs ) , colors [ 0 ] ) ;
2022-07-15 15:37:19 +00:00
svg . draw_outline ( union_ex ( overhangs_after_offset ) , colors [ 1 ] ) ;
svg . draw_outline ( outlines_below , colors [ 2 ] ) ;
// draw legend
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 0 ) ) , ( " nPoints: " + std : : to_string ( layer_nodes . size ( ) ) + " -> " ) . c_str ( ) , " green " , 4 ) ;
svg . draw_text ( bbox . min + Point ( scale_ ( 15 ) , scale_ ( 0 ) ) , std : : to_string ( lower_layer_nodes . size ( ) ) . c_str ( ) , " black " , 4 ) ;
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 1 ) ) , legends [ 0 ] . c_str ( ) , colors [ 0 ] . c_str ( ) , 4 ) ;
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 2 ) ) , legends [ 1 ] . c_str ( ) , colors [ 1 ] . c_str ( ) , 4 ) ;
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 3 ) ) , legends [ 2 ] . c_str ( ) , colors [ 2 ] . c_str ( ) , 4 ) ;
// draw layer nodes
svg . draw ( layer_pts , " green " , coord_t ( scale_ ( 0.1 ) ) ) ;
// lower layer points
layer_pts . clear ( ) ;
for ( TreeSupport : : Node * node : lower_layer_nodes ) {
layer_pts . push_back ( node - > position ) ;
}
svg . draw ( layer_pts , " black " , coord_t ( scale_ ( 0.1 ) ) ) ;
// higher layer points
layer_pts . clear ( ) ;
for ( TreeSupport : : Node * node : layer_nodes ) {
if ( node - > parent )
layer_pts . push_back ( node - > parent - > position ) ;
}
svg . draw ( layer_pts , " blue " , coord_t ( scale_ ( 0.1 ) ) ) ;
}
static void draw_layer_mst
2022-12-07 12:32:33 +00:00
( const std : : string & layer_nr_or_z ,
2022-07-15 15:37:19 +00:00
const std : : vector < MinimumSpanningTree > & spanning_trees ,
const ExPolygons & outline
)
{
auto lines = spanning_tree_to_lines ( spanning_trees ) ;
BoundingBox bbox = get_extents ( lines ) ;
for ( auto & poly : outline )
{
BoundingBox bb = poly . contour . bounding_box ( ) ;
bbox . merge ( bb ) ;
}
2022-12-07 12:32:33 +00:00
SVG svg ( get_svg_filename ( layer_nr_or_z , " mstree " ) . c_str ( ) , bbox ) ;
2022-07-15 15:37:19 +00:00
if ( ! svg . is_opened ( ) ) return ;
svg . draw ( lines , " blue " , coord_t ( scale_ ( 0.05 ) ) ) ;
svg . draw_outline ( outline , " yellow " ) ;
2022-12-07 12:32:33 +00:00
for ( auto & spanning_tree : spanning_trees )
svg . draw ( spanning_tree . vertices ( ) , " black " , coord_t ( scale_ ( 0.1 ) ) ) ;
2022-07-15 15:37:19 +00:00
}
2022-07-06 04:16:37 +00:00
static void draw_two_overhangs_to_svg ( TreeSupportLayer * ts_layer , const ExPolygons & overhangs1 , const ExPolygons & overhangs2 )
{
if ( overhangs1 . empty ( ) & & overhangs2 . empty ( ) )
return ;
BoundingBox bbox1 = get_extents ( overhangs1 ) ;
BoundingBox bbox2 = get_extents ( overhangs2 ) ;
bbox1 . merge ( bbox2 ) ;
SVG svg ( get_svg_filename ( std : : to_string ( ts_layer - > print_z ) , " two_overhangs " ) , bbox1 ) ;
if ( ! svg . is_opened ( ) ) return ;
svg . draw ( union_ex ( overhangs1 ) , " blue " ) ;
svg . draw ( union_ex ( overhangs2 ) , " red " ) ;
}
static void draw_polylines ( TreeSupportLayer * ts_layer , Polylines & polylines )
{
if ( polylines . empty ( ) )
return ;
BoundingBox bbox = get_extents ( polylines ) ;
SVG svg ( get_svg_filename ( std : : to_string ( ts_layer - > print_z ) , " lightnings " ) , bbox ) ;
if ( ! svg . is_opened ( ) ) return ;
int id = 0 ;
for ( Polyline & pline : polylines )
{
int i1 , i2 ;
for ( size_t i = 0 ; i < pline . size ( ) - 1 ; i + + )
{
i1 = i ;
i2 = i + 1 ;
svg . draw ( Line ( pline . points [ i1 ] , pline . points [ i2 ] ) , " blue " ) ;
svg . draw ( pline . points [ i1 ] , " red " ) ;
id + + ;
svg . draw_text ( pline . points [ i1 ] , std : : to_string ( id ) . c_str ( ) , " black " , 1 ) ;
}
svg . draw ( pline . points [ i2 ] , " red " ) ;
id + + ;
svg . draw_text ( pline . points [ i2 ] , std : : to_string ( id ) . c_str ( ) , " black " , 1 ) ;
}
}
2022-07-15 15:37:19 +00:00
# endif
// Move point from inside polygon if distance>0, outside if distance<0.
// Special case: distance=0 means find the nearest point of from on the polygon contour.
// The max move distance should not excceed max_move_distance.
static unsigned int move_inside_expoly ( const ExPolygon & polygon , Point & from , double distance = 0 , double max_move_distance = std : : numeric_limits < double > : : max ( ) )
{
//TODO: This is copied from the moveInside of Polygons.
/*
We ' d like to use this function as subroutine in moveInside ( Polygons . . . ) , but
then we ' d need to recompute the distance of the point to the polygon , which
is expensive . Or we need to return the distance . We need the distance there
to compare with the distance to other polygons .
*/
Point ret = from ;
double bestDist2 = std : : numeric_limits < double > : : max ( ) ;
bool is_already_on_correct_side_of_boundary = false ; // whether [from] is already on the right side of the boundary
const Polygon & contour = polygon . contour ;
if ( contour . points . size ( ) < 2 )
{
return 0 ;
}
Point p0 = contour . points [ polygon . contour . size ( ) - 2 ] ;
Point p1 = contour . points . back ( ) ;
// because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop
// to avoid integer rounding edge cases
bool projected_p_beyond_prev_segment = dot_with_unscale ( p1 - p0 , from - p0 ) > = vsize2_with_unscale ( p1 - p0 ) ;
for ( const Point & p2 : polygon . contour . points )
{
// X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A));
// = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A);
// X = P projected on AB
const Point & a = p1 ;
const Point & b = p2 ;
const Point & p = from ;
Point ab = b - a ;
Point ap = p - a ;
double ab_length2 = vsize2_with_unscale ( ab ) ;
if ( ab_length2 < = 0 ) //A = B, i.e. the input polygon had two adjacent points on top of each other.
{
p1 = p2 ; //Skip only one of the points.
continue ;
}
double dot_prod = dot_with_unscale ( ab , ap ) ;
if ( dot_prod < = 0 ) // x is projected to before ab
{
if ( projected_p_beyond_prev_segment )
{ // case which looks like: > .
projected_p_beyond_prev_segment = false ;
Point & x = p1 ;
double dist2 = vsize2_with_unscale ( x - p ) ;
if ( dist2 < bestDist2 )
{
bestDist2 = dist2 ;
if ( distance = = 0 )
{
ret = x ;
}
else
{
// TODO: check whether it needs scale_()
Point inward_dir = turn90_ccw ( normal ( ab , 10.0 ) + normal ( p1 - p0 , 10.0 ) ) ; // inward direction irrespective of sign of [distance]
// MM2INT(10.0) to retain precision for the eventual normalization
ret = x + normal ( inward_dir , scale_ ( distance ) ) ;
is_already_on_correct_side_of_boundary = dot_with_unscale ( inward_dir , p - x ) * distance > = 0 ;
}
}
}
else
{
projected_p_beyond_prev_segment = false ;
p0 = p1 ;
p1 = p2 ;
continue ;
}
}
else if ( dot_prod > = ab_length2 ) // x is projected to beyond ab
{
projected_p_beyond_prev_segment = true ;
p0 = p1 ;
p1 = p2 ;
continue ;
}
else
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
projected_p_beyond_prev_segment = false ;
Point x = a + ab * ( dot_prod / ab_length2 ) ;
double dist2 = vsize2_with_unscale ( p - x ) ;
if ( dist2 < bestDist2 )
{
bestDist2 = dist2 ;
if ( distance = = 0 )
{
ret = x ;
}
else
{
Point inward_dir = turn90_ccw ( normal ( ab , scale_ ( distance ) ) ) ; // inward or outward depending on the sign of [distance]
ret = x + inward_dir ;
is_already_on_correct_side_of_boundary = dot_with_unscale ( inward_dir , p - x ) > = 0 ;
}
}
}
p0 = p1 ;
p1 = p2 ;
}
if ( is_already_on_correct_side_of_boundary ) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
{
// BBS. Remove this condition.
if ( bestDist2 < distance * distance )
{
from = ret ;
}
}
else if ( bestDist2 < max_move_distance * max_move_distance )
{
from = ret ;
}
return 0 ;
}
/*
* Implementation assumes moving inside , but moving outside should just as well be possible .
*/
static bool move_inside_expolys ( const ExPolygons & polygons , Point & from , double distance , double max_move_distance )
{
Point from0 = from ;
Point ret = from ;
std : : vector < Point > valid_pts ;
double bestDist2 = std : : numeric_limits < double > : : max ( ) ;
unsigned int bestPoly = NO_INDEX ;
bool is_already_on_correct_side_of_boundary = false ; // whether [from] is already on the right side of the boundary
Point inward_dir ;
for ( unsigned int poly_idx = 0 ; poly_idx < polygons . size ( ) ; poly_idx + + )
{
const ExPolygon poly = polygons [ poly_idx ] ;
if ( poly . contour . size ( ) < 2 )
continue ;
Point p0 = poly . contour [ poly . contour . size ( ) - 2 ] ;
Point p1 = poly . contour . points . back ( ) ;
// because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop
// to avoid integer rounding edge cases
bool projected_p_beyond_prev_segment = dot_with_unscale ( p1 - p0 , from - p0 ) > = vsize2_with_unscale ( p1 - p0 ) ;
for ( const Point p2 : poly . contour . points )
{
// X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A));
// = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A);
// X = P projected on AB
Point a = p1 ;
Point b = p2 ;
Point p = from ;
Point ab = b - a ;
Point ap = p - a ;
double ab_length2 = vsize2_with_unscale ( ab ) ;
if ( ab_length2 < = 0 ) //A = B, i.e. the input polygon had two adjacent points on top of each other.
{
p1 = p2 ; //Skip only one of the points.
continue ;
}
double dot_prod = dot_with_unscale ( ab , ap ) ;
if ( dot_prod < = 0 ) // x is projected to before ab
{
if ( projected_p_beyond_prev_segment )
{ // case which looks like: > .
projected_p_beyond_prev_segment = false ;
Point & x = p1 ;
double dist2 = vsize2_with_unscale ( x - p ) ;
if ( dist2 < bestDist2 )
{
bestDist2 = dist2 ;
bestPoly = poly_idx ;
if ( distance = = 0 ) { ret = x ; }
else
{
inward_dir = turn90_ccw ( normal ( ab , 10.0 ) + normal ( p1 - p0 , 10.0 ) ) ; // inward direction irrespective of sign of [distance]
// MM2INT(10.0) to retain precision for the eventual normalization
ret = x + normal ( inward_dir , scale_ ( distance ) ) ;
is_already_on_correct_side_of_boundary = dot_with_unscale ( inward_dir , p - x ) * distance > = 0 ;
if ( is_already_on_correct_side_of_boundary & & dist2 < distance * distance )
valid_pts . push_back ( ret - from0 ) ;
}
}
}
else
{
projected_p_beyond_prev_segment = false ;
p0 = p1 ;
p1 = p2 ;
continue ;
}
}
else if ( dot_prod > = ab_length2 ) // x is projected to beyond ab
{
projected_p_beyond_prev_segment = true ;
p0 = p1 ;
p1 = p2 ;
continue ;
}
else
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
projected_p_beyond_prev_segment = false ;
Point x = a + ab * ( dot_prod / ab_length2 ) ;
double dist2 = vsize2_with_unscale ( p - x ) ;
if ( dist2 < bestDist2 )
{
bestDist2 = dist2 ;
bestPoly = poly_idx ;
if ( distance = = 0 ) { ret = x ; }
else
{
inward_dir = turn90_ccw ( normal ( ab , scale_ ( distance ) ) ) ; // inward or outward depending on the sign of [distance]
ret = x + inward_dir ;
is_already_on_correct_side_of_boundary = dot_with_unscale ( inward_dir , p - x ) > = 0 ;
if ( is_already_on_correct_side_of_boundary & & dist2 < distance * distance )
valid_pts . push_back ( ret - from0 ) ;
}
}
}
p0 = p1 ;
p1 = p2 ;
}
}
//if (valid_pts.size() > 1) {
// std::sort(valid_pts.begin(), valid_pts.end());
// Point v_combine = valid_pts[0] + valid_pts[1];
// if(vsize2_with_unscale(v_combine)<distance*distance)
// v_combine = normal(v_combine, scale_(distance));
// ret = v_combine + from0;
//}
if ( is_already_on_correct_side_of_boundary ) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
{
if ( bestDist2 < distance * distance )
{
from = ret ;
}
return true ;
}
else if ( bestDist2 < max_move_distance * max_move_distance )
{
from = ret ;
return true ;
}
return false ;
}
static Point find_closest_ex ( Point from , const ExPolygons & polygons )
{
Point closest_pt ;
double min_dist2 = std : : numeric_limits < double > : : max ( ) ;
for ( const ExPolygon & poly : polygons ) {
for ( int i = 0 ; i < poly . num_contours ( ) ; i + + ) {
const Point * candidate = poly . contour_or_hole ( i ) . closest_point ( from ) ;
double dist2 = vsize2_with_unscale ( * candidate - from ) ;
if ( dist2 < min_dist2 ) {
closest_pt = * candidate ;
min_dist2 = dist2 ;
}
}
}
return closest_pt ;
}
static bool move_outside_expolys ( const ExPolygons & polygons , Point & from , double distance , double max_move_distance )
{
return move_inside_expolys ( polygons , from , - distance , - max_move_distance ) ;
}
static bool is_inside_ex ( const ExPolygon & polygon , const Point & pt )
{
if ( ! get_extents ( polygon ) . contains ( pt ) )
return false ;
return polygon . contains ( pt ) ;
}
static bool is_inside_ex ( const ExPolygons & polygons , const Point & pt )
{
for ( const ExPolygon & poly : polygons ) {
if ( is_inside_ex ( poly , pt ) )
return true ;
}
return false ;
}
Point projection_onto_ex ( const ExPolygons & polygons , Point from )
{
profiler . tic ( ) ;
Point projected_pt ;
double min_dist = std : : numeric_limits < double > : : max ( ) ;
for ( auto poly : polygons ) {
for ( int i = 0 ; i < poly . num_contours ( ) ; i + + ) {
Point p = from . projection_onto ( poly . contour_or_hole ( i ) ) ;
double dist = ( from - p ) . cast < double > ( ) . squaredNorm ( ) ;
if ( dist < min_dist ) {
projected_pt = p ;
min_dist = dist ;
}
}
}
profiler . stage_add ( STAGE_projection_onto_ex , true ) ;
return projected_pt ;
}
static bool move_out_expolys ( const ExPolygons & polygons , Point & from , double distance , double max_move_distance )
{
Point from0 = from ;
ExPolygons polys_dilated = union_ex ( offset_ex ( polygons , scale_ ( distance ) ) ) ;
Point pt = projection_onto_ex ( polys_dilated , from ) ; // find_closest_ex(from, polys_dilated);
Point outward_dir = pt - from ;
Point pt_max = from + normal ( outward_dir , scale_ ( max_move_distance ) ) ;
double dist2 = vsize2_with_unscale ( outward_dir ) ;
if ( dist2 > SQ ( max_move_distance ) )
pt = pt_max ;
// case 5: already outside and far enough, no need to move
if ( ! is_inside_ex ( polys_dilated , from ) )
return true ;
else if ( ! is_inside_ex ( polygons , from ) ) {
// case 4: already outside but not far enough
from = pt ;
return true ;
}
else {
bool pt_max_in_poly = is_inside_ex ( polygons , pt_max ) ;
if ( ! pt_max_in_poly ) {
from = pt_max ;
return true ;
}
else {
return false ;
}
}
}
static Point bounding_box_middle ( const BoundingBox & bbox )
{
return ( bbox . max + bbox . min ) / 2 ;
}
TreeSupport : : TreeSupport ( PrintObject & object , const SlicingParameters & slicing_params )
: m_object ( & object ) , m_slicing_params ( slicing_params ) , m_object_config ( & object . config ( ) )
{
m_raft_layers = slicing_params . base_raft_layers + slicing_params . interface_raft_layers ;
SupportMaterialPattern support_pattern = m_object_config - > support_base_pattern ;
2022-07-06 04:16:37 +00:00
m_support_params . base_fill_pattern =
2022-12-01 13:15:13 +00:00
support_pattern = = smpLightning ? ipLightning :
2022-07-06 04:16:37 +00:00
support_pattern = = smpHoneycomb ? ipHoneycomb :
2022-07-15 15:37:19 +00:00
m_support_params . support_density > 0.95 | | m_support_params . with_sheath ? ipRectilinear :
ipSupportBase ;
2022-11-25 07:17:52 +00:00
2022-07-15 15:37:19 +00:00
m_support_params . interface_fill_pattern = ( m_support_params . interface_density > 0.95 ? ipRectilinear : ipSupportBase ) ;
m_support_params . contact_fill_pattern = ( m_object_config - > support_interface_pattern = = smipAuto & & m_slicing_params . soluble_interface ) | |
m_object_config - > support_interface_pattern = = smipConcentric ?
ipConcentric :
( m_support_params . interface_density > 0.95 ? ipRectilinear : ipSupportBase ) ;
2022-12-07 12:32:33 +00:00
m_support_params . support_extrusion_width = m_object_config - > support_line_width . value > 0 ? m_object_config - > support_line_width : m_object_config - > line_width ;
is_slim = is_tree_slim ( m_object_config - > support_type , m_object_config - > support_style ) ;
MAX_BRANCH_RADIUS = is_slim ? 5.0 : 10.0 ;
tree_support_branch_diameter_angle = 5.0 ; //is_slim ? 10.0 : 5.0;
2022-07-15 15:37:19 +00:00
}
# define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
void TreeSupport : : detect_object_overhangs ( )
{
// overhangs are already detected
if ( m_object - > tree_support_layer_count ( ) > = m_object - > layer_count ( ) )
return ;
2022-12-14 13:12:36 +00:00
// Clear and create Tree Support Layers
2022-07-15 15:37:19 +00:00
m_object - > clear_tree_support_layers ( ) ;
2022-07-22 09:46:10 +00:00
m_object - > clear_tree_support_preview_cache ( ) ;
2022-07-15 15:37:19 +00:00
create_tree_support_layers ( ) ;
m_ts_data = m_object - > alloc_tree_support_preview_cache ( ) ;
2022-12-07 12:32:33 +00:00
m_ts_data - > is_slim = is_slim ;
2022-07-15 15:37:19 +00:00
const PrintObjectConfig & config = m_object - > config ( ) ;
SupportType stype = config . support_type . value ;
const coordf_t radius_sample_resolution = m_ts_data - > m_radius_sample_resolution ;
const coordf_t extrusion_width = config . line_width . value ;
const coordf_t extrusion_width_scaled = scale_ ( extrusion_width ) ;
const coordf_t max_bridge_length = scale_ ( config . max_bridge_length . value ) ;
2022-12-14 13:12:36 +00:00
const bool bridge_no_support = max_bridge_length > 0 ;
2022-09-22 03:09:38 +00:00
const bool support_critical_regions_only = config . support_critical_regions_only . value ;
2022-07-15 15:37:19 +00:00
const int enforce_support_layers = config . enforce_support_layers . value ;
2022-12-14 13:12:36 +00:00
const double area_thresh_well_supported = SQ ( scale_ ( 6 ) ) ;
const double length_thresh_well_supported = scale_ ( 6 ) ;
2022-07-15 15:37:19 +00:00
static const double sharp_tail_max_support_height = 8.f ;
// a region is considered well supported if the number of layers below it exceeds this threshold
const int thresh_layers_below = 10 / config . layer_height ;
double obj_height = m_object - > size ( ) . z ( ) ;
struct ExPolygonComp {
bool operator ( ) ( const ExPolygon & a , const ExPolygon & b ) const {
return & a < & b ;
}
} ;
// for small overhang removal
struct OverhangCluster {
std : : map < int , const ExPolygon * > layer_overhangs ;
ExPolygons merged_poly ;
int min_layer = 1e7 ;
int max_layer = 0 ;
coordf_t offset = 0 ;
2022-09-22 03:09:38 +00:00
bool is_cantilever = false ;
2022-07-15 15:37:19 +00:00
OverhangCluster ( const ExPolygon * expoly , int layer_nr ) {
push_back ( expoly , layer_nr ) ;
}
void push_back ( const ExPolygon * expoly , int layer_nr ) {
layer_overhangs . emplace ( layer_nr , expoly ) ;
auto dilate1 = offset_ex ( * expoly , offset ) ;
if ( ! dilate1 . empty ( ) )
merged_poly = union_ex ( merged_poly , dilate1 ) ;
min_layer = std : : min ( min_layer , layer_nr ) ;
max_layer = std : : max ( max_layer , layer_nr ) ;
}
int height ( ) {
return max_layer - min_layer + 1 ;
}
bool intersects ( const ExPolygon & region , int layer_nr , coordf_t offset ) {
if ( layer_nr < 1 ) return false ;
auto it = layer_overhangs . find ( layer_nr - 1 ) ;
if ( it = = layer_overhangs . end ( ) ) return false ;
const ExPolygon * overhang = it - > second ;
this - > offset = offset ;
auto dilate1 = offset_ex ( region , offset ) ;
return ! intersection_ex ( * overhang , dilate1 ) . empty ( ) ;
}
} ;
std : : vector < OverhangCluster > overhangClusters ;
std : : map < const ExPolygon * , int > overhang2clusterInd ;
// for sharp tail detection
struct RegionCluster {
std : : map < int , const ExPolygon * > layer_regions ;
ExPolygon base ;
BoundingBox bbox ;
int min_layer = 1e7 ;
int max_layer = 0 ;
RegionCluster ( const ExPolygon * expoly , int layer_nr ) {
push_back ( expoly , layer_nr ) ;
}
void push_back ( const ExPolygon * expoly , int layer_nr ) {
if ( layer_regions . empty ( ) ) {
base = * expoly ;
bbox = get_extents ( base ) ;
}
layer_regions . emplace ( layer_nr , expoly ) ;
bbox . merge ( get_extents ( * expoly ) ) ;
min_layer = std : : min ( min_layer , layer_nr ) ;
max_layer = std : : max ( max_layer , layer_nr ) ;
}
int height ( ) {
return max_layer - min_layer + 1 ;
}
bool intersects ( const ExPolygon & region , int layer_nr , coordf_t offset ) {
if ( layer_nr < 1 ) return false ;
auto it = layer_regions . find ( layer_nr - 1 ) ;
if ( it = = layer_regions . end ( ) ) return false ;
const ExPolygon * region_lower = it - > second ;
return ! intersection_ex ( * region_lower , region ) . empty ( ) ;
}
} ;
std : : vector < RegionCluster > regionClusters ;
std : : map < const ExPolygon * , int > region2clusterInd ;
auto find_and_insert_cluster = [ ] ( auto & regionClusters , auto & region2clusterInd , const ExPolygon & region , int layer_nr , coordf_t offset ) {
bool found = false ;
for ( int i = 0 ; i < regionClusters . size ( ) ; i + + ) {
auto & cluster = regionClusters [ i ] ;
if ( cluster . intersects ( region , layer_nr , offset ) ) {
cluster . push_back ( & region , layer_nr ) ;
region2clusterInd . emplace ( & region , i ) ;
found = true ;
break ;
}
}
if ( ! found ) {
regionClusters . emplace_back ( & region , layer_nr ) ;
region2clusterInd . emplace ( & region , regionClusters . size ( ) - 1 ) ;
}
} ;
2022-11-30 04:15:40 +00:00
// main part of sharptail detections
2022-12-07 12:32:33 +00:00
if ( is_tree ( stype ) )
2022-07-15 15:37:19 +00:00
{
double threshold_rad = ( config . support_threshold_angle . value < EPSILON ? 30 : config . support_threshold_angle . value + 1 ) * M_PI / 180. ;
2022-12-14 13:12:36 +00:00
ExPolygons regions_well_supported ;
std : : map < ExPolygon , int , ExPolygonComp > region_layers_below ;
ExPolygons lower_overhang_dilated ;
for ( size_t layer_nr = 0 ; layer_nr < m_object - > layer_count ( ) ; layer_nr + + ) {
2022-07-15 15:37:19 +00:00
if ( m_object - > print ( ) - > canceled ( ) )
break ;
2022-12-14 13:12:36 +00:00
2022-12-07 12:32:33 +00:00
if ( ! is_auto ( stype ) & & layer_nr > enforce_support_layers )
2022-07-15 15:37:19 +00:00
continue ;
Layer * layer = m_object - > get_layer ( layer_nr ) ;
for ( auto & slice : layer - > lslices )
find_and_insert_cluster ( regionClusters , region2clusterInd , slice , layer_nr , 0 ) ;
if ( layer - > lower_layer = = nullptr ) {
for ( auto & slice : layer - > lslices ) {
auto bbox_size = get_extents ( slice ) . size ( ) ;
2022-12-08 07:11:39 +00:00
if ( /*slice.area() > area_thresh_well_supported || */
( bbox_size . x ( ) > length_thresh_well_supported & & bbox_size . y ( ) > length_thresh_well_supported ) )
2022-07-15 15:37:19 +00:00
regions_well_supported . emplace_back ( slice ) ;
2022-12-08 07:11:39 +00:00
else if ( g_config_support_sharp_tails ) {
layer - > sharp_tails . push_back ( slice ) ;
layer - > sharp_tails_height . insert ( { & slice , layer - > height } ) ;
}
2022-07-15 15:37:19 +00:00
}
continue ;
}
Layer * lower_layer = layer - > lower_layer ;
coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? - 0.15 * extrusion_width : ( float ) lower_layer - > height / tan ( threshold_rad ) ;
coordf_t support_offset_scaled = scale_ ( lower_layer_offset ) ;
// Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose!
ExPolygons lower_polys ; // = offset2_ex(lower_layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled / 2);
for ( const ExPolygon & expoly : lower_layer - > lslices ) {
if ( ! offset_ex ( expoly , - extrusion_width_scaled / 2 ) . empty ( ) ) {
lower_polys . emplace_back ( expoly ) ;
}
}
ExPolygons curr_polys ; // = offset2_ex(layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled / 2);
for ( const ExPolygon & expoly : layer - > lslices ) {
if ( ! offset_ex ( expoly , - extrusion_width_scaled / 2 ) . empty ( ) ) {
curr_polys . emplace_back ( expoly ) ;
}
}
// normal overhang
ExPolygons lower_layer_offseted = offset_ex ( lower_polys , support_offset_scaled , SUPPORT_SURFACES_OFFSET_PARAMETERS ) ;
ExPolygons overhang_areas = std : : move ( diff_ex ( curr_polys , lower_layer_offseted ) ) ;
2022-12-14 13:12:36 +00:00
2022-09-22 03:09:38 +00:00
overhang_areas . erase ( std : : remove_if ( overhang_areas . begin ( ) , overhang_areas . end ( ) ,
[ extrusion_width_scaled ] ( ExPolygon & area ) { return offset_ex ( area , - 0.1 * extrusion_width_scaled ) . empty ( ) ; } ) ,
overhang_areas . end ( ) ) ;
2022-07-15 15:37:19 +00:00
ExPolygons overhangs_sharp_tail ;
2022-12-07 12:32:33 +00:00
if ( is_auto ( stype ) & & g_config_support_sharp_tails )
2022-07-15 15:37:19 +00:00
{
#if 0
// detect sharp tail and add more supports around
for ( auto & lower_region : lower_layer_offseted ) {
auto radius = get_extents ( lower_region ) . radius ( ) ;
auto out_of_well_supported_region = offset_ex ( diff_ex ( { lower_region } , regions_well_supported ) , - extrusion_width_scaled ) ;
auto bbox_size = get_extents ( out_of_well_supported_region ) . size ( ) ;
double area_inter = area ( intersection_ex ( { lower_region } , regions_well_supported ) ) ;
if ( ( area_inter = = 0 | |
( ( bbox_size . x ( ) > extrusion_width_scaled & & bbox_size . y ( ) > extrusion_width_scaled ) & & area_inter < area_thresh_well_supported ) )
/*&& (obj_height - scale_(layer->slice_z)) > get_extents(lower_region).radius() * 5*/ ) {
auto lower_region_unoffseted = offset_ex ( lower_region , - support_offset_scaled ) ;
if ( ! lower_region_unoffseted . empty ( ) )
lower_region = lower_region_unoffseted . front ( ) ;
}
}
if ( ! lower_layer_offseted . empty ( ) ) {
overhangs_sharp_tail = std : : move ( diff_ex ( curr_polys , lower_layer_offseted ) ) ;
//overhangs_sharp_tail = std::move(offset2_ex(overhangs_sharp_tail, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
overhangs_sharp_tail = diff_ex ( overhangs_sharp_tail , overhang_areas ) ;
}
if ( ! overhangs_sharp_tail . empty ( ) ) {
append ( layer - > sharp_tails , overhangs_sharp_tail ) ;
overhang_areas = union_ex ( overhang_areas , overhangs_sharp_tail ) ;
}
# else
2022-09-22 03:09:38 +00:00
// BBS detect sharp tail
2022-07-15 15:37:19 +00:00
const ExPolygons & lower_layer_sharptails = lower_layer - > sharp_tails ;
auto & lower_layer_sharptails_height = lower_layer - > sharp_tails_height ;
for ( ExPolygon & expoly : layer - > lslices ) {
bool is_sharp_tail = false ;
float accum_height = layer - > height ;
do {
// 1. nothing below
// check whether this is a sharp tail region
if ( intersection_ex ( { expoly } , lower_polys ) . empty ( ) ) {
is_sharp_tail = expoly . area ( ) < area_thresh_well_supported ;
break ;
}
// 2. something below
// check whether this is above a sharp tail region.
// 2.1 If no sharp tail below, this is considered as common region.
ExPolygons supported_by_lower = intersection_ex ( { expoly } , lower_layer_sharptails ) ;
if ( supported_by_lower . empty ( ) ) {
is_sharp_tail = false ;
break ;
}
// 2.2 If sharp tail below, check whether it support this region enough.
float supported_area = area ( supported_by_lower ) ;
BoundingBox bbox = get_extents ( supported_by_lower ) ;
2022-12-08 07:11:39 +00:00
#if 0
// judge by area isn't reliable, failure cases include 45 degree rotated cube
2022-07-15 15:37:19 +00:00
if ( supported_area > area_thresh_well_supported ) {
is_sharp_tail = false ;
break ;
}
2022-12-08 07:11:39 +00:00
# endif
2022-07-15 15:37:19 +00:00
if ( bbox . size ( ) . x ( ) > length_thresh_well_supported & & bbox . size ( ) . y ( ) > length_thresh_well_supported ) {
is_sharp_tail = false ;
break ;
}
// 2.3 check whether sharp tail exceed the max height
for ( auto & lower_sharp_tail_height : lower_layer_sharptails_height ) {
if ( ! intersection_ex ( * lower_sharp_tail_height . first , expoly ) . empty ( ) ) {
accum_height + = lower_sharp_tail_height . second ;
break ;
}
}
if ( accum_height > = sharp_tail_max_support_height ) {
is_sharp_tail = false ;
break ;
}
// 2.4 if the area grows fast than threshold, it get connected to other part or
// it has a sharp slop and will be auto supported.
2022-12-14 13:12:36 +00:00
ExPolygons new_overhang_expolys = diff_ex ( { expoly } , lower_layer_sharptails ) ;
2022-07-15 15:37:19 +00:00
if ( ! offset_ex ( new_overhang_expolys , - 5.0 * extrusion_width_scaled ) . empty ( ) ) {
is_sharp_tail = false ;
break ;
}
// 2.5 mark the expoly as sharptail
is_sharp_tail = true ;
} while ( 0 ) ;
if ( is_sharp_tail ) {
ExPolygons overhang = diff_ex ( { expoly } , lower_layer - > lslices ) ;
layer - > sharp_tails . push_back ( expoly ) ;
layer - > sharp_tails_height . insert ( { & expoly , accum_height } ) ;
append ( overhang_areas , overhang ) ;
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
SVG svg ( get_svg_filename ( std : : to_string ( layer - > print_z ) , " sharp_tail " ) , m_object - > bounding_box ( ) ) ;
if ( svg . is_opened ( ) ) svg . draw ( overhang , " yellow " ) ;
# endif
}
}
# endif
}
if ( bridge_no_support & & overhang_areas . size ( ) > 0 ) {
2022-07-22 09:46:10 +00:00
m_object - > remove_bridges_from_contacts ( lower_layer , layer , extrusion_width_scaled , & overhang_areas , max_bridge_length , true ) ;
2022-07-15 15:37:19 +00:00
}
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
for ( ExPolygon & poly : overhang_areas ) {
if ( ! offset_ex ( poly , - 0.1 * extrusion_width_scaled ) . empty ( ) )
ts_layer - > overhang_areas . emplace_back ( poly ) ;
}
2022-12-07 12:32:33 +00:00
if ( is_auto ( stype ) & & g_config_remove_small_overhangs ) {
2022-07-15 15:37:19 +00:00
for ( auto & overhang : ts_layer - > overhang_areas ) {
find_and_insert_cluster ( overhangClusters , overhang2clusterInd , overhang , layer_nr , extrusion_width_scaled ) ;
}
}
2022-12-07 12:32:33 +00:00
if ( is_auto ( stype ) & & /*g_config_support_sharp_tails*/ 0 )
2022-07-15 15:37:19 +00:00
{ // update well supported regions
ExPolygons regions_well_supported2 ;
// regions intersects with lower regions_well_supported or large support are also well supported
auto inters = intersection_ex ( layer - > lslices , regions_well_supported ) ;
auto inters2 = intersection_ex ( layer - > lslices , ts_layer - > overhang_areas ) ;
inters . insert ( inters . end ( ) , inters2 . begin ( ) , inters2 . end ( ) ) ;
for ( auto inter : inters ) {
auto bbox_size = get_extents ( inter ) . size ( ) ;
if ( inter . area ( ) > = area_thresh_well_supported
| | ( bbox_size . x ( ) > length_thresh_well_supported & & bbox_size . y ( ) > length_thresh_well_supported ) )
{
auto tmp = offset_ex ( inter , support_offset_scaled ) ;
if ( ! tmp . empty ( ) ) {
// if inter is a single line with only 2 valid points, clipper will return empty
regions_well_supported2 . emplace_back ( std : : move ( tmp [ 0 ] ) ) ;
}
}
}
// experimental: regions high enough is also well supported
for ( auto & region : layer - > lslices ) {
auto cluster = regionClusters [ region2clusterInd [ & region ] ] ;
if ( layer_nr - cluster . min_layer > thresh_layers_below )
regions_well_supported2 . push_back ( region ) ;
}
regions_well_supported = union_ex ( regions_well_supported2 ) ;
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
if ( ! regions_well_supported . empty ( ) ) {
SVG svg ( get_svg_filename ( std : : to_string ( layer - > print_z ) , " regions_well_supported " ) , m_object - > bounding_box ( ) ) ;
if ( svg . is_opened ( ) )
svg . draw ( regions_well_supported , " yellow " ) ;
}
# endif
}
}
}
auto enforcers = m_object - > slice_support_enforcers ( ) ;
auto blockers = m_object - > slice_support_blockers ( ) ;
m_object - > project_and_append_custom_facets ( false , EnforcerBlockerType : : ENFORCER , enforcers ) ;
m_object - > project_and_append_custom_facets ( false , EnforcerBlockerType : : BLOCKER , blockers ) ;
2022-09-22 03:09:38 +00:00
// check whether the overhang cluster is cantilever (far awary from main body)
for ( auto & cluster : overhangClusters ) {
Layer * layer = m_object - > get_layer ( cluster . min_layer ) ;
if ( layer - > lower_layer = = NULL ) continue ;
Layer * lower_layer = layer - > lower_layer ;
auto cluster_boundary = intersection ( cluster . merged_poly , offset ( lower_layer - > lslices , scale_ ( 0.5 ) ) ) ;
double dist_max = 0 ;
Points cluster_pts ;
for ( auto & poly : cluster . merged_poly )
append ( cluster_pts , poly . contour . points ) ;
for ( auto & pt : cluster_pts ) {
double dist_pt = std : : numeric_limits < double > : : max ( ) ;
for ( auto & poly : cluster_boundary ) {
double d = poly . distance_to ( pt ) ;
dist_pt = std : : min ( dist_pt , d ) ;
}
dist_max = std : : max ( dist_max , dist_pt ) ;
}
2022-12-08 07:11:39 +00:00
if ( dist_max > scale_ ( 3 ) ) { // this cluster is cantilever if the farmost point is larger than 3mm away from base
2022-09-22 03:09:38 +00:00
for ( auto it = cluster . layer_overhangs . begin ( ) ; it ! = cluster . layer_overhangs . end ( ) ; it + + ) {
int layer_nr = it - > first ;
auto p_overhang = it - > second ;
m_object - > get_layer ( layer_nr ) - > cantilevers . emplace_back ( * p_overhang ) ;
}
cluster . is_cantilever = true ;
}
}
2022-12-07 12:32:33 +00:00
if ( is_auto ( stype ) & & g_config_remove_small_overhangs ) {
2022-07-15 15:37:19 +00:00
if ( blockers . size ( ) < m_object - > layer_count ( ) )
blockers . resize ( m_object - > layer_count ( ) ) ;
for ( auto & cluster : overhangClusters ) {
auto erode1 = offset_ex ( cluster . merged_poly , - 1.5 * extrusion_width_scaled ) ;
//DEBUG
bool save_poly = false ;
if ( save_poly ) {
erode1 = offset_ex ( cluster . merged_poly , - 1 * extrusion_width_scaled ) ;
double a = area ( erode1 ) ;
erode1 [ 0 ] . contour . remove_duplicate_points ( ) ;
a = area ( erode1 ) ;
auto tmp = offset_ex ( erode1 , extrusion_width_scaled ) ;
SVG svg ( " SVG/merged_poly_ " + std : : to_string ( cluster . min_layer ) + " .svg " , m_object - > bounding_box ( ) ) ;
if ( svg . is_opened ( ) ) {
svg . draw_outline ( cluster . merged_poly , " yellow " ) ;
svg . draw_outline ( erode1 , " yellow " ) ;
}
}
// 1. check overhang span size is smaller than 3mm
//auto bbox_size = get_extents(cluster.merged_poly).size();
//const double dimension_limit = scale_(3.0) + 2 * extrusion_width_scaled;
//if (bbox_size.x() > dimension_limit || bbox_size.y() > dimension_limit)
// continue;
// 2. check overhang cluster size is smaller than 3.0 * fw_scaled
if ( area ( erode1 ) > SQ ( scale_ ( 0.1 ) ) )
continue ;
// 3. check whether the small overhang is sharp tail
bool is_sharp_tail = false ;
for ( size_t layer_id = cluster . min_layer ; layer_id < cluster . max_layer ; layer_id + + ) {
Layer * layer = m_object - > get_layer ( layer_id ) ;
if ( ! intersection_ex ( layer - > sharp_tails , cluster . merged_poly ) . empty ( ) ) {
is_sharp_tail = true ;
break ;
}
}
if ( is_sharp_tail ) continue ;
2022-09-22 03:09:38 +00:00
// 4. check whether the overhang cluster is cantilever
if ( cluster . is_cantilever ) continue ;
2022-07-15 15:37:19 +00:00
for ( auto it = cluster . layer_overhangs . begin ( ) ; it ! = cluster . layer_overhangs . end ( ) ; it + + ) {
int layer_nr = it - > first ;
auto p_overhang = it - > second ;
blockers [ layer_nr ] . push_back ( p_overhang - > contour ) ;
// auto dilate1 = offset_ex(*p_overhang, extrusion_width_scaled);
// auto erode1 = offset_ex(*p_overhang, -extrusion_width_scaled);
// Layer* layer = m_object->get_layer(layer_nr);
// auto inter_with_others = intersection_ex(dilate1, diff_ex(layer->lslices, *p_overhang));
//// the following cases are small overhangs:
//// 1) overhang is single line (erode1.empty()==true)
//// 2) overhang is not island (intersects with others)
// if (erode1.empty() && !inter_with_others.empty())
// blockers[layer_nr].push_back(p_overhang->contour);
}
}
}
2022-11-30 04:15:40 +00:00
has_overhangs = false ;
2022-07-15 15:37:19 +00:00
for ( int layer_nr = 0 ; layer_nr < m_object - > layer_count ( ) ; layer_nr + + ) {
if ( m_object - > print ( ) - > canceled ( ) )
break ;
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
2022-09-22 03:09:38 +00:00
if ( support_critical_regions_only ) {
auto layer = m_object - > get_layer ( layer_nr ) ;
2022-11-24 03:45:17 +00:00
auto lower_layer = layer - > lower_layer ;
if ( lower_layer = = nullptr )
ts_layer - > overhang_areas = layer - > sharp_tails ;
else
ts_layer - > overhang_areas = diff_ex ( layer - > sharp_tails , lower_layer - > lslices ) ;
2022-09-22 03:09:38 +00:00
append ( ts_layer - > overhang_areas , layer - > cantilevers ) ;
}
2022-07-15 15:37:19 +00:00
if ( layer_nr < blockers . size ( ) ) {
Polygons & blocker = blockers [ layer_nr ] ;
ts_layer - > overhang_areas = diff_ex ( ts_layer - > overhang_areas , offset_ex ( blocker , scale_ ( radius_sample_resolution ) ) ) ;
}
2022-07-22 09:46:10 +00:00
for ( auto & area : ts_layer - > overhang_areas ) {
ts_layer - > overhang_types . emplace ( & area , TreeSupportLayer : : Detected ) ;
}
2022-12-14 13:12:36 +00:00
// enforcers
2022-07-15 15:37:19 +00:00
if ( layer_nr < enforcers . size ( ) ) {
Polygons & enforcer = enforcers [ layer_nr ] ;
// coconut: enforcer can't do offset2_ex, otherwise faces with angle near 90 degrees can't have enforcers, which
// is not good. For example: tails of animals needs extra support except the lowest tip.
//enforcer = std::move(offset2_ex(enforcer, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
2022-12-14 13:12:36 +00:00
enforcer = offset ( enforcer , 0.1 * extrusion_width_scaled ) ;
2022-07-15 15:37:19 +00:00
for ( const Polygon & poly : enforcer ) {
ts_layer - > overhang_areas . emplace_back ( poly ) ;
2022-07-22 09:46:10 +00:00
ts_layer - > overhang_types . emplace ( & ts_layer - > overhang_areas . back ( ) , TreeSupportLayer : : Enforced ) ;
2022-07-15 15:37:19 +00:00
}
}
2022-11-30 04:15:40 +00:00
if ( ! ts_layer - > overhang_areas . empty ( ) ) has_overhangs = true ;
2022-07-15 15:37:19 +00:00
}
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
for ( const TreeSupportLayer * layer : m_object - > tree_support_layers ( ) ) {
if ( layer - > overhang_areas . empty ( ) )
continue ;
SVG svg ( get_svg_filename ( std : : to_string ( layer - > print_z ) , " overhang_areas " ) , m_object - > bounding_box ( ) ) ;
if ( svg . is_opened ( ) ) {
svg . draw_outline ( m_object - > get_layer ( layer - > id ( ) ) - > lslices , " yellow " ) ;
svg . draw ( layer - > overhang_areas , " red " ) ;
for ( auto & overhang : layer - > overhang_areas ) {
double aarea = overhang . area ( ) / area_thresh_well_supported ;
auto pt = get_extents ( overhang ) . center ( ) ;
char x [ 20 ] ; sprintf ( x , " %.2f " , aarea ) ;
svg . draw_text ( pt , x , " red " ) ;
}
}
}
# endif
}
void TreeSupport : : create_tree_support_layers ( )
{
int layer_id = 0 ;
coordf_t raft_print_z = 0.f ;
coordf_t raft_slice_z = 0.f ;
for ( ; layer_id < m_slicing_params . base_raft_layers ; layer_id + + ) {
raft_print_z + = m_slicing_params . base_raft_layer_height ;
raft_slice_z = raft_print_z - m_slicing_params . base_raft_layer_height / 2 ;
m_object - > add_tree_support_layer ( layer_id , m_slicing_params . base_raft_layer_height , raft_print_z , raft_slice_z ) ;
}
for ( ; layer_id < m_slicing_params . base_raft_layers + m_slicing_params . interface_raft_layers ; layer_id + + ) {
raft_print_z + = m_slicing_params . interface_raft_layer_height ;
raft_slice_z = raft_print_z - m_slicing_params . interface_raft_layer_height / 2 ;
m_object - > add_tree_support_layer ( layer_id , m_slicing_params . base_raft_layer_height , raft_print_z , raft_slice_z ) ;
}
for ( Layer * layer : m_object - > layers ( ) ) {
TreeSupportLayer * ts_layer = m_object - > add_tree_support_layer ( layer - > id ( ) , layer - > height , layer - > print_z , layer - > slice_z ) ;
if ( ts_layer - > id ( ) > m_raft_layers ) {
TreeSupportLayer * lower_layer = m_object - > get_tree_support_layer ( ts_layer - > id ( ) - 1 ) ;
lower_layer - > upper_layer = ts_layer ;
ts_layer - > lower_layer = lower_layer ;
}
}
}
static inline BoundingBox fill_expolygon_generate_paths (
ExtrusionEntitiesPtr & dst ,
ExPolygon & & expolygon ,
Fill * filler ,
const FillParams & fill_params ,
ExtrusionRole role ,
const Flow & flow )
{
Surface surface ( stInternal , std : : move ( expolygon ) ) ;
Polylines polylines ;
try {
polylines = filler - > fill_surface ( & surface , fill_params ) ;
} catch ( InfillFailedException & ) {
}
BoundingBox fill_bbox ;
if ( ! polylines . empty ( ) ) {
fill_bbox = polylines [ 0 ] . bounding_box ( ) ;
for ( auto & polyline : polylines )
fill_bbox . merge ( polyline . bounding_box ( ) ) ;
}
extrusion_entities_append_paths ( dst , std : : move ( polylines ) , role , flow . mm3_per_mm ( ) , flow . width ( ) , flow . height ( ) ) ;
return fill_bbox ;
}
static inline std : : vector < BoundingBox > fill_expolygons_generate_paths (
ExtrusionEntitiesPtr & dst ,
ExPolygons & & expolygons ,
Fill * filler ,
const FillParams & fill_params ,
ExtrusionRole role ,
const Flow & flow )
{
std : : vector < BoundingBox > fill_boxes ;
for ( ExPolygon & expoly : expolygons ) {
auto box = fill_expolygon_generate_paths ( dst , std : : move ( expoly ) , filler , fill_params , role , flow ) ;
fill_boxes . emplace_back ( box ) ;
}
return fill_boxes ;
}
static void make_perimeter_and_inner_brim ( ExtrusionEntitiesPtr & dst , const Print & print , const ExPolygon & support_area , size_t wall_count , const Flow & flow , bool is_interface )
{
Polygons loops ;
ExPolygons support_area_new = offset_ex ( support_area , - 0.5f * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
std : : map < ExPolygon * , int > depth_per_expoly ;
std : : list < ExPolygon > expoly_list ;
for ( ExPolygon & expoly : support_area_new ) {
expoly_list . emplace_back ( std : : move ( expoly ) ) ;
depth_per_expoly . insert ( { & expoly_list . back ( ) , 0 } ) ;
}
while ( ! expoly_list . empty ( ) ) {
polygons_append ( loops , to_polygons ( expoly_list . front ( ) ) ) ;
auto first_iter = expoly_list . begin ( ) ;
auto depth_iter = depth_per_expoly . find ( & expoly_list . front ( ) ) ;
if ( depth_iter - > second + 1 < wall_count ) {
// shrink and then dilate to prevent overlapping and overflow
ExPolygons expolys_new = offset_ex ( expoly_list . front ( ) , - 1.4 * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
expolys_new = offset_ex ( expolys_new , .4 * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
for ( ExPolygon & expoly : expolys_new ) {
auto new_iter = expoly_list . insert ( expoly_list . begin ( ) , expoly ) ;
depth_per_expoly . insert ( { & * new_iter , depth_iter - > second + 1 } ) ;
}
}
depth_per_expoly . erase ( depth_iter ) ;
expoly_list . erase ( first_iter ) ;
}
ExtrusionRole role = is_interface ? erSupportMaterialInterface : erSupportMaterial ;
extrusion_entities_append_loops ( dst , std : : move ( loops ) , role ,
float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
}
2022-11-24 06:37:19 +00:00
static void make_perimeter_and_infill ( ExtrusionEntitiesPtr & dst , const Print & print , const ExPolygon & support_area , size_t wall_count , const Flow & flow , ExtrusionRole role , Fill * filler_support , double support_density , bool infill_first = true )
2022-07-15 15:37:19 +00:00
{
Polygons loops ;
ExPolygons support_area_new = offset_ex ( support_area , - 0.5f * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
// draw infill
FillParams fill_params ;
fill_params . density = support_density ;
fill_params . dont_adjust = true ;
ExPolygons to_infill = offset_ex ( support_area , - 0.5f * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
std : : vector < BoundingBox > fill_boxes = fill_expolygons_generate_paths ( dst , std : : move ( to_infill ) , filler_support , fill_params , role , flow ) ;
// allow wall_count to be zero, which means only draw infill
if ( wall_count = = 0 ) {
for ( auto fill_bbox : fill_boxes )
{
if ( filler_support - > angle = = 0 ) {
fill_bbox . min [ 0 ] - = scale_ ( 10 ) ;
fill_bbox . max [ 0 ] + = scale_ ( 10 ) ;
}
else {
fill_bbox . min [ 1 ] - = scale_ ( 10 ) ;
fill_bbox . max [ 1 ] + = scale_ ( 10 ) ;
}
support_area_new = diff_ex ( support_area_new , offset_ex ( to_expolygons ( { fill_bbox . polygon ( ) } ) , 0.5 * flow . scaled_width ( ) ) ) ;
}
// filter out small areas
for ( auto it = support_area_new . begin ( ) ; it ! = support_area_new . end ( ) ; ) {
if ( offset_ex ( * it , - flow . scaled_width ( ) ) . empty ( ) )
it = support_area_new . erase ( it ) ;
else
it + + ;
}
}
{
std : : map < ExPolygon * , int > depth_per_expoly ;
std : : list < ExPolygon > expoly_list ;
for ( ExPolygon & expoly : support_area_new ) {
expoly_list . emplace_back ( std : : move ( expoly ) ) ;
depth_per_expoly . insert ( { & expoly_list . back ( ) , 0 } ) ;
}
while ( ! expoly_list . empty ( ) ) {
polygons_append ( loops , to_polygons ( expoly_list . front ( ) ) ) ;
auto first_iter = expoly_list . begin ( ) ;
auto depth_iter = depth_per_expoly . find ( & expoly_list . front ( ) ) ;
if ( depth_iter - > second + 1 < wall_count ) {
ExPolygons expolys_new = offset_ex ( expoly_list . front ( ) , - float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
for ( ExPolygon & expoly : expolys_new ) {
auto new_iter = expoly_list . insert ( expoly_list . begin ( ) , expoly ) ;
depth_per_expoly . insert ( { & * new_iter , depth_iter - > second + 1 } ) ;
}
}
depth_per_expoly . erase ( depth_iter ) ;
expoly_list . erase ( first_iter ) ;
}
2022-11-24 06:37:19 +00:00
if ( infill_first )
extrusion_entities_append_loops ( dst , std : : move ( loops ) , role ,
float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
else { // loops first
ExtrusionEntitiesPtr loops_entities ;
extrusion_entities_append_loops ( loops_entities , std : : move ( loops ) , role ,
float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
loops_entities . insert ( loops_entities . end ( ) , dst . begin ( ) , dst . end ( ) ) ;
dst = std : : move ( loops_entities ) ;
}
}
if ( infill_first ) {
// sort regions to reduce travel
Points ordering_points ;
for ( const auto & area : dst )
ordering_points . push_back ( area - > first_point ( ) ) ;
std : : vector < Points : : size_type > order = chain_points ( ordering_points ) ;
ExtrusionEntitiesPtr new_dst ;
new_dst . reserve ( ordering_points . size ( ) ) ;
for ( size_t i : order )
new_dst . emplace_back ( dst [ i ] ) ;
dst = new_dst ;
2022-07-15 15:37:19 +00:00
}
}
void TreeSupport : : generate_toolpaths ( )
{
const PrintConfig & print_config = m_object - > print ( ) - > config ( ) ;
const PrintObjectConfig & object_config = m_object - > config ( ) ;
2022-12-07 12:32:33 +00:00
coordf_t support_extrusion_width = m_support_params . support_extrusion_width ;
2022-07-15 15:37:19 +00:00
coordf_t nozzle_diameter = print_config . nozzle_diameter . get_at ( object_config . support_filament - 1 ) ;
2022-12-01 13:15:13 +00:00
coordf_t layer_height = object_config . layer_height . value ;
2022-07-15 15:37:19 +00:00
const size_t wall_count = object_config . tree_support_wall_count . value ;
2022-12-01 13:15:13 +00:00
const bool with_infill = object_config . support_base_pattern ! = smpNone & & object_config . support_base_pattern ! = smpDefault ;
2022-07-15 15:37:19 +00:00
// coconut: use same intensity settings as SupportMaterial.cpp
auto m_support_material_interface_flow = support_material_interface_flow ( m_object , float ( m_slicing_params . layer_height ) ) ;
coordf_t interface_spacing = object_config . support_interface_spacing . value + m_support_material_interface_flow . spacing ( ) ;
coordf_t bottom_interface_spacing = object_config . support_bottom_interface_spacing . value + m_support_material_interface_flow . spacing ( ) ;
coordf_t interface_density = std : : min ( 1. , m_support_material_interface_flow . spacing ( ) / interface_spacing ) ;
coordf_t bottom_interface_density = std : : min ( 1. , m_support_material_interface_flow . spacing ( ) / bottom_interface_spacing ) ;
const coordf_t branch_radius = object_config . tree_support_branch_diameter . value / 2 ;
const coordf_t branch_radius_scaled = scale_ ( branch_radius ) ;
if ( m_object - > tree_support_layers ( ) . empty ( ) )
return ;
// calculate fill areas for raft layers
ExPolygons raft_areas ;
if ( m_object - > layer_count ( ) > 0 ) {
const Layer * layer = m_object - > layers ( ) . front ( ) ;
for ( const ExPolygon & expoly : layer - > lslices ) {
raft_areas . push_back ( expoly ) ;
}
}
if ( m_object - > tree_support_layer_count ( ) > m_raft_layers ) {
const TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( m_raft_layers ) ;
for ( const ExPolygon expoly : ts_layer - > floor_areas )
raft_areas . push_back ( expoly ) ;
for ( const ExPolygon expoly : ts_layer - > roof_areas )
raft_areas . push_back ( expoly ) ;
for ( const ExPolygon expoly : ts_layer - > base_areas )
raft_areas . push_back ( expoly ) ;
}
raft_areas = std : : move ( offset_ex ( raft_areas , scale_ ( 3. ) ) ) ;
// generate raft tool path
if ( m_raft_layers > 0 )
{
ExtrusionRole raft_contour_er = m_slicing_params . base_raft_layers > 0 ? erSupportMaterial : erSupportMaterialInterface ;
TreeSupportLayer * ts_layer = m_object - > tree_support_layers ( ) . front ( ) ;
Flow flow = m_object - > print ( ) - > brim_flow ( ) ;
Polygons loops ;
for ( const ExPolygon & expoly : raft_areas ) {
loops . push_back ( expoly . contour ) ;
loops . insert ( loops . end ( ) , expoly . holes . begin ( ) , expoly . holes . end ( ) ) ;
}
extrusion_entities_append_loops ( ts_layer - > support_fills . entities , std : : move ( loops ) , raft_contour_er ,
float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
raft_areas = offset_ex ( raft_areas , - flow . scaled_spacing ( ) / 2. ) ;
}
for ( size_t layer_nr = 0 ; layer_nr < m_slicing_params . base_raft_layers ; layer_nr + + ) {
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr ) ;
coordf_t expand_offset = ( layer_nr = = 0 ? 0. : - 1. ) ;
Flow support_flow = layer_nr = = 0 ? m_object - > print ( ) - > brim_flow ( ) : Flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
Fill * filler_interface = Fill : : new_from_type ( ipRectilinear ) ;
filler_interface - > angle = layer_nr = = 0 ? 90 : 0 ;
filler_interface - > spacing = support_extrusion_width ;
FillParams fill_params ;
fill_params . density = interface_density ;
fill_params . dont_adjust = true ;
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , std : : move ( offset_ex ( raft_areas , scale_ ( expand_offset ) ) ) ,
filler_interface , fill_params , erSupportMaterial , support_flow ) ;
}
for ( size_t layer_nr = m_slicing_params . base_raft_layers ;
layer_nr < m_slicing_params . base_raft_layers + m_slicing_params . interface_raft_layers ;
layer_nr + + )
{
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr ) ;
coordf_t expand_offset = ( layer_nr = = 0 ? 0. : - 1. ) ;
Flow support_flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
Fill * filler_interface = Fill : : new_from_type ( ipRectilinear ) ;
filler_interface - > angle = 0 ;
filler_interface - > spacing = support_extrusion_width ;
FillParams fill_params ;
fill_params . density = interface_density ;
fill_params . dont_adjust = true ;
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , std : : move ( offset_ex ( raft_areas , scale_ ( expand_offset ) ) ) ,
filler_interface , fill_params , erSupportMaterialInterface , support_flow ) ;
}
BoundingBox bbox_object ( Point ( - scale_ ( 1. ) , - scale_ ( 1.0 ) ) , Point ( scale_ ( 1. ) , scale_ ( 1. ) ) ) ;
auto obj_size = m_object - > size ( ) ;
bool obj_is_vertical = obj_size . x ( ) < obj_size . y ( ) ;
int num_layers_to_change_infill_direction = int ( HEIGHT_TO_SWITCH_INFILL_DIRECTION / object_config . layer_height . value ) ; // change direction every 30mm
2022-08-11 06:27:59 +00:00
std : : shared_ptr < Fill > filler_interface = std : : shared_ptr < Fill > ( Fill : : new_from_type ( m_support_params . contact_fill_pattern ) ) ;
std : : shared_ptr < Fill > filler_Roof1stLayer = std : : shared_ptr < Fill > ( Fill : : new_from_type ( ipRectilinear ) ) ;
std : : shared_ptr < Fill > filler_support = std : : shared_ptr < Fill > ( Fill : : new_from_type ( m_support_params . base_fill_pattern ) ) ;
filler_interface - > set_bounding_box ( bbox_object ) ;
filler_Roof1stLayer - > set_bounding_box ( bbox_object ) ;
filler_support - > set_bounding_box ( bbox_object ) ;
filler_interface - > angle = Geometry : : deg2rad ( object_config . support_angle . value + 90. ) ; //(1 - obj_is_vertical) * M_PI_2;//((1-obj_is_vertical) + int(layer_id / num_layers_to_change_infill_direction)) * M_PI_2;;//layer_id % 2 ? 0 : M_PI_2;
filler_Roof1stLayer - > angle = Geometry : : deg2rad ( object_config . support_angle . value + 90. ) ;
2022-07-15 15:37:19 +00:00
// generate tree support tool paths
tbb : : parallel_for (
tbb : : blocked_range < size_t > ( m_raft_layers , m_object - > tree_support_layer_count ( ) ) ,
[ & ] ( const tbb : : blocked_range < size_t > & range )
{
for ( size_t layer_id = range . begin ( ) ; layer_id < range . end ( ) ; layer_id + + ) {
if ( m_object - > print ( ) - > canceled ( ) )
break ;
m_object - > print ( ) - > set_status ( 70 , ( boost : : format ( _L ( " Support: generate toolpath at layer %d " ) ) % layer_id ) . str ( ) ) ;
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_id ) ;
Flow support_flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
2022-11-30 03:51:25 +00:00
coordf_t support_spacing = object_config . support_base_pattern_spacing . value + support_flow . spacing ( ) ;
coordf_t support_density = std : : min ( 1. , support_flow . spacing ( ) / support_spacing ) ;
2022-11-24 06:37:19 +00:00
ts_layer - > support_fills . no_sort = false ;
2022-07-15 15:37:19 +00:00
for ( auto & area_group : ts_layer - > area_groups ) {
2022-12-01 13:15:13 +00:00
ExPolygon & poly = * area_group . area ;
2022-07-15 15:37:19 +00:00
ExPolygons polys ;
FillParams fill_params ;
2022-12-01 13:15:13 +00:00
if ( area_group . type ! = TreeSupportLayer : : BaseType ) {
2022-07-15 15:37:19 +00:00
// interface
if ( layer_id = = 0 ) {
Flow flow = m_raft_layers = = 0 ? m_object - > print ( ) - > brim_flow ( ) : support_flow ;
make_perimeter_and_inner_brim ( ts_layer - > support_fills . entities , * m_object - > print ( ) , poly , wall_count , flow ,
2022-12-01 13:15:13 +00:00
area_group . type = = TreeSupportLayer : : RoofType ) ;
2022-07-15 15:37:19 +00:00
polys = std : : move ( offset_ex ( poly , - flow . scaled_spacing ( ) ) ) ;
2022-12-01 13:15:13 +00:00
} else if ( area_group . type = = TreeSupportLayer : : Roof1stLayer ) {
2022-07-15 15:37:19 +00:00
polys = std : : move ( offset_ex ( poly , 0.5 * support_flow . scaled_width ( ) ) ) ;
}
else {
polys . push_back ( poly ) ;
}
fill_params . density = interface_density ;
fill_params . dont_adjust = true ;
}
2022-12-01 13:15:13 +00:00
if ( area_group . type = = TreeSupportLayer : : Roof1stLayer ) {
2022-07-15 15:37:19 +00:00
// roof_1st_layer
fill_params . density = interface_density ;
// Note: spacing means the separation between two lines as if they are tightly extruded
2022-08-11 06:27:59 +00:00
filler_Roof1stLayer - > spacing = m_support_material_interface_flow . spacing ( ) ;
2022-11-24 06:37:19 +00:00
// generate a perimeter first to support interface better
2022-12-09 02:37:45 +00:00
ExtrusionEntityCollection * temp_support_fills = new ExtrusionEntityCollection ( ) ;
make_perimeter_and_infill ( temp_support_fills - > entities , * m_object - > print ( ) , poly , 1 , m_support_material_interface_flow , erSupportMaterial ,
2022-11-24 06:37:19 +00:00
filler_Roof1stLayer . get ( ) , interface_density , false ) ;
2022-12-09 02:37:45 +00:00
temp_support_fills - > no_sort = true ; // make sure loops are first
if ( ! temp_support_fills - > entities . empty ( ) )
ts_layer - > support_fills . entities . push_back ( temp_support_fills ) ;
else
delete temp_support_fills ;
2022-12-01 13:15:13 +00:00
} else if ( area_group . type = = TreeSupportLayer : : FloorType ) {
2022-07-15 15:37:19 +00:00
// floor_areas
fill_params . density = bottom_interface_density ;
filler_interface - > spacing = m_support_material_interface_flow . spacing ( ) ;
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , std : : move ( polys ) ,
filler_interface . get ( ) , fill_params , erSupportMaterialInterface , m_support_material_interface_flow ) ;
2022-12-01 13:15:13 +00:00
} else if ( area_group . type = = TreeSupportLayer : : RoofType ) {
2022-07-15 15:37:19 +00:00
// roof_areas
2022-12-01 13:15:13 +00:00
fill_params . density = interface_density ;
2022-07-15 15:37:19 +00:00
filler_interface - > spacing = m_support_material_interface_flow . spacing ( ) ;
2022-12-01 13:15:13 +00:00
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , std : : move ( polys ) , filler_interface . get ( ) , fill_params , erSupportMaterialInterface ,
m_support_material_interface_flow ) ;
2022-07-15 15:37:19 +00:00
}
else {
// base_areas
2022-11-30 03:51:25 +00:00
filler_support - > spacing = support_flow . spacing ( ) ;
Flow flow = ( layer_id = = 0 & & m_raft_layers = = 0 ) ? m_object - > print ( ) - > brim_flow ( ) : support_flow ;
if ( area_group . dist_to_top < 10 / layer_height & & ! with_infill ) {
// at least 2 walls for the top tips
make_perimeter_and_inner_brim ( ts_layer - > support_fills . entities , * m_object - > print ( ) , poly , std : : max ( wall_count , size_t ( 2 ) ) , flow , false ) ;
2022-12-01 13:15:13 +00:00
} else {
if ( with_infill & & layer_id > 0 & & m_support_params . base_fill_pattern ! = ipLightning ) {
filler_support - > angle = Geometry : : deg2rad ( object_config . support_angle . value ) ;
// allow infill-only mode if support is thick enough
if ( offset ( poly , - scale_ ( support_spacing * 1.5 ) ) . empty ( ) = = false ) {
make_perimeter_and_infill ( ts_layer - > support_fills . entities , * m_object - > print ( ) , poly , wall_count , flow , erSupportMaterial ,
filler_support . get ( ) , support_density ) ;
} else { // otherwise must draw 1 wall
make_perimeter_and_infill ( ts_layer - > support_fills . entities , * m_object - > print ( ) , poly , std : : max ( size_t ( 1 ) , wall_count ) , flow ,
erSupportMaterial , filler_support . get ( ) , support_density ) ;
}
} else {
2022-07-15 15:37:19 +00:00
make_perimeter_and_inner_brim ( ts_layer - > support_fills . entities , * m_object - > print ( ) , poly ,
2022-12-01 13:15:13 +00:00
layer_id > 0 ? wall_count : std : : numeric_limits < size_t > : : max ( ) , flow , false ) ;
2022-07-15 15:37:19 +00:00
}
}
}
}
2022-11-25 07:17:52 +00:00
if ( m_support_params . base_fill_pattern = = ipLightning )
2022-07-06 04:16:37 +00:00
{
double print_z = ts_layer - > print_z ;
if ( printZ_to_lightninglayer . find ( print_z ) = = printZ_to_lightninglayer . end ( ) )
continue ;
//TODO:
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
// whether current value works correctly remained to be tested
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
auto & lightning_layer = generator - > getTreesForLayer ( printZ_to_lightninglayer [ print_z ] ) ;
2022-11-25 07:17:52 +00:00
Flow flow = ( layer_id = = 0 & & m_raft_layers = = 0 ) ? m_object - > print ( ) - > brim_flow ( ) : support_flow ;
2022-07-06 04:16:37 +00:00
ExPolygons areas = offset_ex ( ts_layer - > base_areas , - flow . scaled_spacing ( ) ) ;
for ( auto & area : areas )
{
Polylines polylines = lightning_layer . convertToLines ( to_polygons ( area ) , 0 ) ;
for ( auto itr = polylines . begin ( ) ; itr ! = polylines . end ( ) ; )
{
if ( itr - > length ( ) < scale_ ( 1.0 ) )
itr = polylines . erase ( itr ) ;
else
itr + + ;
}
Polylines opt_polylines ;
# if 1
//this wont create connection patterns along contours
append ( opt_polylines , chain_polylines ( std : : move ( polylines ) ) ) ;
# else
//this will create connection patterns along contours
FillParams params ;
params . anchor_length = float ( Fill : : infill_anchor * 0.01 * flow . spacing ( ) ) ;
params . anchor_length_max = Fill : : infill_anchor_max ;
params . anchor_length = std : : min ( params . anchor_length , params . anchor_length_max ) ;
Fill : : connect_infill ( std : : move ( polylines ) , area , opt_polylines , flow . spacing ( ) , params ) ;
# endif
extrusion_entities_append_paths ( ts_layer - > support_fills . entities , opt_polylines , erSupportMaterial ,
float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2022-12-01 13:15:13 +00:00
std : : string name = " ./SVG/trees_polyline_ " + std : : to_string ( ts_layer - > print_z ) /*+ "_" + std::to_string(rand_num)*/ + " .svg " ;
2022-07-06 04:16:37 +00:00
BoundingBox bbox = get_extents ( ts_layer - > base_areas ) ;
SVG svg ( name , bbox ) ;
2022-12-01 13:15:13 +00:00
if ( svg . is_opened ( ) ) {
svg . draw ( ts_layer - > base_areas , " blue " ) ;
svg . draw ( generator - > Overhangs ( ) [ printZ_to_lightninglayer [ print_z ] ] , " red " ) ;
for ( auto & line : opt_polylines ) svg . draw ( line , " yellow " ) ;
2022-07-06 04:16:37 +00:00
}
# endif
}
}
2022-07-15 15:37:19 +00:00
// sort extrusions to reduce travel, also make sure walls go before infills
2022-11-24 06:37:19 +00:00
if ( ts_layer - > support_fills . no_sort = = false )
chain_and_reorder_extrusion_entities ( ts_layer - > support_fills . entities ) ;
2022-07-15 15:37:19 +00:00
}
}
) ;
}
Polygons TreeSupport : : spanning_tree_to_polygon ( const std : : vector < MinimumSpanningTree > & spanning_trees , Polygons layer_contours , int layer_nr )
{
Polygons polys ;
auto & mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches [ layer_nr ] ;
for ( MinimumSpanningTree mst : spanning_trees ) {
std : : vector < Point > points = mst . vertices ( ) ;
if ( points . size ( ) = = 0 )
continue ;
std : : map < Point , bool > visited ;
for ( int i = 0 ; i < points . size ( ) ; i + + )
visited . emplace ( points [ i ] , false ) ;
std : : unordered_set < Line , LineHash > to_ignore ;
for ( int i = 0 ; i < points . size ( ) ; i + + ) {
if ( visited [ points [ i ] ] = = true )
continue ;
Polygon poly ;
bool has_next = true ;
Point pt1 = points [ i ] ;
poly . points . push_back ( pt1 ) ;
visited [ pt1 ] = true ;
while ( has_next ) {
const std : : vector < Point > & neighbours = mst . adjacent_nodes ( pt1 ) ;
if ( neighbours . empty ( ) )
{
break ;
}
double min_ccw = std : : numeric_limits < double > : : max ( ) ;
Point pt_selected = neighbours [ 0 ] ;
has_next = false ;
for ( Point pt2 : neighbours ) {
if ( to_ignore . find ( Line ( pt1 , pt2 ) ) = = to_ignore . end ( ) ) {
auto iter = mst_line_x_layer_contour_cache . find ( { pt1 , pt2 } ) ;
if ( iter ! = mst_line_x_layer_contour_cache . end ( ) ) {
if ( iter - > second )
continue ;
}
else {
Polylines pls ;
pls . emplace_back ( pt1 , pt2 ) ;
Polylines pls_intersect = intersection_pl ( pls , layer_contours ) ;
mst_line_x_layer_contour_cache . insert ( { { pt1 , pt2 } , ! pls_intersect . empty ( ) } ) ;
mst_line_x_layer_contour_cache . insert ( { { pt2 , pt1 } , ! pls_intersect . empty ( ) } ) ;
if ( ! pls_intersect . empty ( ) )
continue ;
}
if ( poly . points . size ( ) < 2 | | visited [ pt2 ] = = false )
{
pt_selected = pt2 ;
has_next = true ;
break ;
}
double curr_ccw = pt2 . ccw ( pt1 , poly . points . back ( ) ) ;
if ( curr_ccw < min_ccw )
{
min_ccw = curr_ccw ;
pt_selected = pt2 ;
has_next = true ;
}
}
}
if ( has_next ) {
poly . points . push_back ( pt_selected ) ;
to_ignore . insert ( Line ( pt1 , pt_selected ) ) ;
visited [ pt_selected ] = true ;
pt1 = pt_selected ;
}
}
polys . emplace_back ( std : : move ( poly ) ) ;
}
}
return polys ;
}
Polygons TreeSupport : : contact_nodes_to_polygon ( const std : : vector < Node * > & contact_nodes , Polygons layer_contours , int layer_nr , std : : vector < double > & radiis , std : : vector < bool > & is_interface )
{
Polygons polys ;
std : : vector < MinimumSpanningTree > spanning_trees ;
std : : vector < double > radiis_mtree ;
std : : vector < bool > is_interface_mtree ;
// generate minimum spanning trees
{
std : : map < Node * , bool > visited ;
for ( int i = 0 ; i < contact_nodes . size ( ) ; i + + )
visited . emplace ( contact_nodes [ i ] , false ) ;
std : : unordered_set < Line , LineHash > to_ignore ;
// generate minimum spaning trees
for ( int i = 0 ; i < contact_nodes . size ( ) ; i + + ) {
Node * node = contact_nodes [ i ] ;
if ( visited [ node ] )
continue ;
std : : vector < Point > points_to_mstree ;
double radius = 0 ;
Point pt1 = node - > position ;
points_to_mstree . push_back ( pt1 ) ;
visited [ node ] = true ;
radius + = node - > radius ;
for ( int j = i + 1 ; j < contact_nodes . size ( ) ; j + + ) {
Node * node2 = contact_nodes [ j ] ;
Point pt2 = node2 - > position ;
// connect to this neighbor if:
// 1) both are interface or both are not
// 3) not readly added
// 4) won't cross perimeters: this is not right since we need to check all possible connections
if ( ( node - > support_roof_layers_below > 0 ) = = ( node2 - > support_roof_layers_below > 0 )
& & to_ignore . find ( Line ( pt1 , pt2 ) ) = = to_ignore . end ( ) )
{
points_to_mstree . emplace_back ( pt2 ) ;
visited [ node2 ] = true ;
radius + = node2 - > radius ;
}
}
spanning_trees . emplace_back ( points_to_mstree ) ;
radiis_mtree . push_back ( radius / points_to_mstree . size ( ) ) ;
is_interface_mtree . push_back ( node - > support_roof_layers_below > 0 ) ;
}
}
auto lines = spanning_tree_to_lines ( spanning_trees ) ;
# if 1
// convert mtree to polygon
for ( int k = 0 ; k < spanning_trees . size ( ) ; k + + ) {
auto & mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches [ layer_nr ] ;
MinimumSpanningTree mst = spanning_trees [ k ] ;
std : : vector < Point > points = mst . vertices ( ) ;
std : : map < Point , bool > visited ;
for ( int i = 0 ; i < points . size ( ) ; i + + )
visited . emplace ( points [ i ] , false ) ;
std : : unordered_set < Line , LineHash > to_ignore ;
for ( int i = 0 ; i < points . size ( ) ; i + + ) {
if ( visited [ points [ i ] ] )
continue ;
Polygon poly ;
Point pt1 = points [ i ] ;
poly . points . push_back ( pt1 ) ;
visited [ pt1 ] = true ;
bool has_next = true ;
while ( has_next )
{
const std : : vector < Point > & neighbours = mst . adjacent_nodes ( pt1 ) ;
double min_ccw = - std : : numeric_limits < double > : : max ( ) ;
Point pt_selected ;
has_next = false ;
for ( Point pt2 : neighbours ) {
if ( to_ignore . find ( Line ( pt1 , pt2 ) ) = = to_ignore . end ( ) ) {
auto iter = mst_line_x_layer_contour_cache . find ( { pt1 , pt2 } ) ;
if ( iter ! = mst_line_x_layer_contour_cache . end ( ) ) {
if ( iter - > second )
continue ;
}
else {
Polylines pls ;
pls . emplace_back ( pt1 , pt2 ) ;
Polylines pls_intersect = intersection_pl ( pls , layer_contours ) ;
mst_line_x_layer_contour_cache . insert ( { { pt1 , pt2 } , ! pls_intersect . empty ( ) } ) ;
mst_line_x_layer_contour_cache . insert ( { { pt2 , pt1 } , ! pls_intersect . empty ( ) } ) ;
if ( ! pls_intersect . empty ( ) )
continue ;
}
if ( poly . points . size ( ) < 2 )
{
pt_selected = pt2 ;
has_next = true ;
break ;
}
double curr_ccw = pt2 . ccw ( pt1 , poly . points . rbegin ( ) [ 1 ] ) ;
if ( curr_ccw > min_ccw )
{
has_next = true ;
min_ccw = curr_ccw ;
pt_selected = pt2 ;
}
}
}
if ( ! has_next )
break ;
poly . points . push_back ( pt_selected ) ;
to_ignore . insert ( Line ( pt1 , pt_selected ) ) ;
visited [ pt_selected ] = true ;
pt1 = pt_selected ;
}
polys . emplace_back ( std : : move ( poly ) ) ;
radiis . push_back ( radiis_mtree [ k ] ) ;
is_interface . push_back ( is_interface_mtree [ k ] ) ;
}
}
# else
polys = spanning_tree_to_polygon ( spanning_trees , layer_contours , layer_nr , radiis ) ;
# endif
return polys ;
}
void TreeSupport : : generate_support_areas ( )
{
const PrintObjectConfig & config = m_object - > config ( ) ;
2022-12-07 12:32:33 +00:00
bool tree_support_enable = config . enable_support . value & & is_tree ( config . support_type . value ) ;
2022-07-15 15:37:19 +00:00
if ( ! tree_support_enable )
return ;
2022-12-14 13:12:36 +00:00
std : : vector < std : : vector < Node * > > contact_nodes ( m_object - > layers ( ) . size ( ) ) ;
2022-07-15 15:37:19 +00:00
profiler . stage_start ( STAGE_total ) ;
// Generate overhang areas
profiler . stage_start ( STAGE_DETECT_OVERHANGS ) ;
m_object - > print ( ) - > set_status ( 55 , _L ( " Support: detect overhangs " ) ) ;
2022-11-02 10:34:45 +00:00
detect_object_overhangs ( ) ;
2022-07-15 15:37:19 +00:00
profiler . stage_finish ( STAGE_DETECT_OVERHANGS ) ;
// Generate contact points of tree support
profiler . stage_start ( STAGE_GENERATE_CONTACT_NODES ) ;
m_object - > print ( ) - > set_status ( 56 , _L ( " Support: generate contact points " ) ) ;
2022-11-02 10:34:45 +00:00
generate_contact_points ( contact_nodes ) ;
2022-07-15 15:37:19 +00:00
profiler . stage_finish ( STAGE_GENERATE_CONTACT_NODES ) ;
//Drop nodes to lower layers.
profiler . stage_start ( STAGE_DROP_DOWN_NODES ) ;
m_object - > print ( ) - > set_status ( 60 , _L ( " Support: propagate branches " ) ) ;
2022-11-02 10:34:45 +00:00
drop_nodes ( contact_nodes ) ;
2022-07-15 15:37:19 +00:00
profiler . stage_finish ( STAGE_DROP_DOWN_NODES ) ;
2022-12-07 12:32:33 +00:00
// smooth_nodes(contact_nodes);
# if !USE_PLAN_LAYER_HEIGHTS
2022-07-15 15:37:19 +00:00
// Adjust support layer heights
2022-12-07 12:32:33 +00:00
adjust_layer_heights ( contact_nodes ) ;
# endif
2022-07-15 15:37:19 +00:00
//Generate support areas.
profiler . stage_start ( STAGE_DRAW_CIRCLES ) ;
m_object - > print ( ) - > set_status ( 65 , _L ( " Support: draw polygons " ) ) ;
draw_circles ( contact_nodes ) ;
profiler . stage_finish ( STAGE_DRAW_CIRCLES ) ;
for ( auto & layer : contact_nodes )
{
for ( Node * p_node : layer )
{
delete p_node ;
}
layer . clear ( ) ;
}
contact_nodes . clear ( ) ;
profiler . stage_start ( STAGE_GENERATE_TOOLPATHS ) ;
m_object - > print ( ) - > set_status ( 69 , _L ( " Support: generate toolpath " ) ) ;
generate_toolpaths ( ) ;
profiler . stage_finish ( STAGE_GENERATE_TOOLPATHS ) ;
profiler . stage_finish ( STAGE_total ) ;
BOOST_LOG_TRIVIAL ( debug ) < < " tree support time " < < profiler . report ( ) ;
}
2022-12-07 12:32:33 +00:00
coordf_t TreeSupport : : calc_branch_radius ( coordf_t base_radius , size_t layers_to_top , size_t tip_layers , double diameter_angle_scale_factor )
2022-07-15 15:37:19 +00:00
{
double radius ;
2022-12-07 12:32:33 +00:00
if ( ! is_slim ) {
if ( ( layers_to_top + 1 ) > tip_layers ) {
radius = base_radius + base_radius * ( layers_to_top + 1 ) * diameter_angle_scale_factor ;
} else {
radius = base_radius * ( layers_to_top + 1 ) / tip_layers ;
}
} else {
if ( ( layers_to_top + 1 ) > tip_layers * 2 ) {
radius = base_radius + base_radius * ( layers_to_top + 1 ) * diameter_angle_scale_factor ;
} else {
radius = base_radius * ( layers_to_top + 1 ) / ( tip_layers * 2 ) ;
}
radius = std : : max ( radius , MIN_BRANCH_RADIUS ) ;
}
radius = std : : min ( radius , MAX_BRANCH_RADIUS ) ;
return radius ;
}
coordf_t TreeSupport : : calc_branch_radius ( coordf_t base_radius , coordf_t mm_to_top , double diameter_angle_scale_factor )
{
double radius ;
if ( mm_to_top > base_radius )
2022-07-15 15:37:19 +00:00
{
2022-12-07 12:32:33 +00:00
radius = base_radius + mm_to_top * diameter_angle_scale_factor ;
2022-07-15 15:37:19 +00:00
}
else
{
2022-12-07 12:32:33 +00:00
radius = mm_to_top * diameter_angle_scale_factor ;
2022-07-15 15:37:19 +00:00
}
2022-12-07 12:32:33 +00:00
radius = std : : max ( radius , MIN_BRANCH_RADIUS ) ;
2022-07-15 15:37:19 +00:00
radius = std : : min ( radius , MAX_BRANCH_RADIUS ) ;
return radius ;
}
2022-09-23 12:55:29 +00:00
ExPolygons avoid_object_remove_extra_small_parts ( ExPolygons & expolys , const ExPolygons & avoid_region ) {
ExPolygons expolys_out ;
for ( auto expoly : expolys ) {
auto expolys_avoid = diff_ex ( expoly , avoid_region ) ;
int idx_max_area = - 1 ;
float max_area = 0 ;
for ( int i = 0 ; i < expolys_avoid . size ( ) ; + + i ) {
auto a = expolys_avoid [ i ] . area ( ) ;
if ( a > max_area ) {
max_area = a ;
idx_max_area = i ;
}
}
if ( idx_max_area > = 0 ) expolys_out . emplace_back ( std : : move ( expolys_avoid [ idx_max_area ] ) ) ;
}
return expolys_out ;
}
2022-07-15 15:37:19 +00:00
void TreeSupport : : draw_circles ( const std : : vector < std : : vector < Node * > > & contact_nodes )
{
const PrintObjectConfig & config = m_object - > config ( ) ;
2022-12-07 02:54:43 +00:00
const Print * print = m_object - > print ( ) ;
bool has_brim = print - > has_brim ( ) ;
2022-12-01 13:15:13 +00:00
bool has_infill = config . support_base_pattern . value ! = smpNone & & config . support_base_pattern ! = smpDefault ;
2022-07-15 15:37:19 +00:00
int bottom_gap_layers = round ( m_slicing_params . gap_object_support / m_slicing_params . layer_height ) ;
const coordf_t branch_radius = config . tree_support_branch_diameter . value / 2 ;
const coordf_t branch_radius_scaled = scale_ ( branch_radius ) ;
Polygon branch_circle ; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time.
2022-07-22 09:46:10 +00:00
// Use square support if there are too many nodes per layer because circle support needs much longer time to compute
// Hower circle support can be printed faster, so we prefer circle for fewer nodes case.
const bool SQUARE_SUPPORT = avg_node_per_layer > 200 ;
const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 100 ; // The number of vertices in each circle.
2022-10-18 02:46:54 +00:00
for ( int i = 0 ; i < CIRCLE_RESOLUTION ; i + + )
2022-07-15 15:37:19 +00:00
{
2022-07-22 09:46:10 +00:00
double angle ;
if ( SQUARE_SUPPORT )
angle = ( double ) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle ;
else
angle = ( double ) i / CIRCLE_RESOLUTION * TAU ;
2022-07-15 15:37:19 +00:00
branch_circle . append ( Point ( cos ( angle ) * branch_radius_scaled , sin ( angle ) * branch_radius_scaled ) ) ;
}
// Performance optimization. Only generate lslices for brim and skirt.
size_t brim_skirt_layers = has_brim ? 1 : 0 ;
const PrintConfig & print_config = print - > config ( ) ;
for ( const PrintObject * object : print - > objects ( ) )
{
size_t skirt_layers = print - > has_infinite_skirt ( ) ? object - > layer_count ( ) : std : : min ( size_t ( print_config . skirt_height . value ) , object - > layer_count ( ) ) ;
brim_skirt_layers = std : : max ( brim_skirt_layers , skirt_layers ) ;
}
// generate areas
const coordf_t layer_height = config . layer_height . value ;
const size_t top_interface_layers = config . support_interface_top_layers . value ;
const size_t bottom_interface_layers = config . support_interface_bottom_layers . value ;
const size_t tip_layers = branch_radius / layer_height ; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle.
2022-12-07 12:32:33 +00:00
const double diameter_angle_scale_factor = tan ( tree_support_branch_diameter_angle * M_PI / 180. ) ; // * layer_height / branch_radius; //Scale factor per layer to produce the desired angle.
2022-07-15 15:37:19 +00:00
const coordf_t line_width = config . support_line_width ;
const coordf_t line_width_scaled = scale_ ( line_width ) ;
2022-11-25 07:17:52 +00:00
const bool with_lightning_infill = m_support_params . base_fill_pattern = = ipLightning ;
2022-12-07 12:32:33 +00:00
coordf_t support_extrusion_width = m_support_params . support_extrusion_width ;
2022-07-06 04:16:37 +00:00
const size_t wall_count = config . tree_support_wall_count . value ;
const PrintObjectConfig & object_config = m_object - > config ( ) ;
auto m_support_material_flow = support_material_flow ( m_object , float ( m_slicing_params . layer_height ) ) ;
coordf_t support_spacing = object_config . support_base_pattern_spacing . value + m_support_material_flow . spacing ( ) ;
coordf_t support_density = std : : min ( 1. , m_support_material_flow . spacing ( ) / support_spacing ) ;
2022-12-01 13:15:13 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " draw_circles for object: " < < m_object - > model_object ( ) - > name ;
2022-07-06 04:16:37 +00:00
2022-07-15 15:37:19 +00:00
// coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map
tbb : : parallel_for (
tbb : : blocked_range < size_t > ( 0 , m_object - > layer_count ( ) ) ,
[ & ] ( const tbb : : blocked_range < size_t > & range )
{
for ( size_t layer_nr = range . begin ( ) ; layer_nr < range . end ( ) ; layer_nr + + )
{
if ( print - > canceled ( ) )
break ;
2022-11-30 03:51:25 +00:00
2022-07-15 15:37:19 +00:00
const std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
assert ( ts_layer ! = nullptr ) ;
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
2022-11-30 03:51:25 +00:00
if ( curr_layer_nodes . empty ( ) ) {
ts_layer - > print_z = 0.0 ;
ts_layer - > height = 0.0 ;
2022-07-15 15:37:19 +00:00
continue ;
2022-11-30 03:51:25 +00:00
}
2022-07-15 15:37:19 +00:00
Node * first_node = curr_layer_nodes . front ( ) ;
ts_layer - > print_z = first_node - > print_z ;
ts_layer - > height = first_node - > height ;
2022-11-30 03:51:25 +00:00
if ( ts_layer - > height < EPSILON ) {
2022-07-15 15:37:19 +00:00
continue ;
2022-11-30 03:51:25 +00:00
}
2022-07-15 15:37:19 +00:00
ExPolygons & base_areas = ts_layer - > base_areas ;
ExPolygons & roof_areas = ts_layer - > roof_areas ;
ExPolygons & roof_1st_layer = ts_layer - > roof_1st_layer ;
ExPolygons & floor_areas = ts_layer - > floor_areas ;
2022-11-25 07:17:52 +00:00
ExPolygons & roof_gap_areas = ts_layer - > roof_gap_areas ;
2022-12-01 13:15:13 +00:00
int max_layers_above_base = 0 ;
int max_layers_above_roof = 0 ;
int max_layers_above_roof1 = 0 ;
2022-07-15 15:37:19 +00:00
2022-10-18 02:46:54 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " circles at layer " < < layer_nr < < " contact nodes size= " < < contact_nodes [ layer_nr ] . size ( ) ;
2022-07-15 15:37:19 +00:00
//Draw the support areas and add the roofs appropriately to the support roof instead of normal areas.
ts_layer - > lslices . reserve ( contact_nodes [ layer_nr ] . size ( ) ) ;
for ( const Node * p_node : contact_nodes [ layer_nr ] )
{
if ( print - > canceled ( ) )
break ;
const Node & node = * p_node ;
2022-12-07 12:32:33 +00:00
ExPolygons area ;
2022-12-16 00:07:22 +00:00
// Generate directly from overhang polygon if one of the following is true:
// 1) node is a normal part of hybrid support
2022-12-07 12:32:33 +00:00
// 2) node is virtual
if ( node . type = = ePolygon | | node . distance_to_top < 0 ) {
2022-12-01 13:15:13 +00:00
if ( node . overhang - > contour . size ( ) > 100 | | node . overhang - > holes . size ( ) > 1 )
2022-12-07 12:32:33 +00:00
area . emplace_back ( * node . overhang ) ;
2022-12-01 13:15:13 +00:00
else {
2022-12-07 12:32:33 +00:00
area = offset_ex ( { * node . overhang } , scale_ ( m_ts_data - > m_xy_distance ) ) ;
2022-12-01 13:15:13 +00:00
}
2022-07-15 15:37:19 +00:00
}
else {
Polygon circle ;
size_t layers_to_top = node . distance_to_top ;
double scale ;
2022-11-25 07:17:52 +00:00
if ( top_interface_layers > 0 ) { // if has interface, branch circles should be larger
2022-07-15 15:37:19 +00:00
scale = static_cast < double > ( layers_to_top + 1 ) / tip_layers ;
scale = layers_to_top < tip_layers ? ( 0.5 + scale / 2 ) : ( 1 + static_cast < double > ( layers_to_top - tip_layers ) * diameter_angle_scale_factor ) ;
2022-12-07 12:32:33 +00:00
} else { // directly calc scale from the radius used in drop_nodes
scale = calc_branch_radius ( branch_radius , node . dist_mm_to_top , diameter_angle_scale_factor ) / branch_radius ;
2022-07-15 15:37:19 +00:00
}
2022-12-07 12:32:33 +00:00
if ( is_slim & & 0 ) {
double moveX = node . movement . x ( ) / ( scale * branch_radius_scaled ) ;
double moveY = node . movement . y ( ) / ( scale * branch_radius_scaled ) ;
const double vsize_inv = 0.5 / ( 0.01 + std : : sqrt ( moveX * moveX + moveY * moveY ) ) ;
double matrix [ 2 * 2 ] = {
scale * ( 1 + moveX * moveX * vsize_inv ) , scale * ( 0 + moveX * moveY * vsize_inv ) ,
scale * ( 0 + moveX * moveY * vsize_inv ) , scale * ( 1 + moveY * moveY * vsize_inv ) ,
} ;
for ( auto vertex : branch_circle . points ) {
vertex = Point ( matrix [ 0 ] * vertex . x ( ) + matrix [ 1 ] * vertex . y ( ) , matrix [ 2 ] * vertex . x ( ) + matrix [ 3 ] * vertex . y ( ) ) ;
circle . append ( node . position + vertex ) ;
}
} else {
for ( auto iter = branch_circle . points . begin ( ) ; iter ! = branch_circle . points . end ( ) ; iter + + ) {
Point corner = ( * iter ) * scale ;
circle . append ( node . position + corner ) ;
}
2022-07-15 15:37:19 +00:00
}
if ( layer_nr = = 0 & & m_raft_layers = = 0 ) {
double brim_width = layers_to_top * layer_height / ( scale * branch_radius ) * 0.5 ;
circle = offset ( circle , scale_ ( brim_width ) ) [ 0 ] ;
}
2022-12-07 12:32:33 +00:00
area . emplace_back ( ExPolygon ( circle ) ) ;
// merge overhang to get a smoother interface surface
if ( top_interface_layers > 0 & & node . support_roof_layers_below > 0 ) {
ExPolygons overhang_expanded ;
if ( node . overhang - > contour . size ( ) > 100 | | node . overhang - > holes . size ( ) > 1 )
overhang_expanded . emplace_back ( * node . overhang ) ;
else {
// 对于有缺陷的模型, overhang膨胀以后可能是空的!
overhang_expanded = offset_ex ( { * node . overhang } , scale_ ( m_ts_data - > m_xy_distance ) ) ;
}
append ( area , overhang_expanded ) ;
}
2022-07-15 15:37:19 +00:00
}
2022-11-25 07:17:52 +00:00
if ( node . distance_to_top < 0 )
2022-12-07 12:32:33 +00:00
append ( roof_gap_areas , area ) ;
2022-11-25 07:17:52 +00:00
else if ( node . support_roof_layers_below = = 1 )
2022-07-15 15:37:19 +00:00
{
2022-12-07 12:32:33 +00:00
append ( roof_1st_layer , area ) ;
2022-12-01 13:15:13 +00:00
max_layers_above_roof1 = std : : max ( max_layers_above_roof1 , node . distance_to_top ) ;
2022-07-15 15:37:19 +00:00
}
else if ( node . support_roof_layers_below > 0 )
{
2022-12-07 12:32:33 +00:00
append ( roof_areas , area ) ;
2022-12-01 13:15:13 +00:00
max_layers_above_roof = std : : max ( max_layers_above_roof , node . distance_to_top ) ;
2022-07-15 15:37:19 +00:00
}
else
{
2022-12-07 12:32:33 +00:00
append ( base_areas , area ) ;
2022-12-01 13:15:13 +00:00
max_layers_above_base = std : : max ( max_layers_above_base , node . distance_to_top ) ;
2022-07-15 15:37:19 +00:00
}
if ( layer_nr < brim_skirt_layers )
2022-12-07 12:32:33 +00:00
append ( ts_layer - > lslices , area ) ;
2022-07-15 15:37:19 +00:00
}
ts_layer - > lslices = std : : move ( union_ex ( ts_layer - > lslices ) ) ;
//Must update bounding box which is used in avoid crossing perimeter
ts_layer - > lslices_bboxes . clear ( ) ;
ts_layer - > lslices_bboxes . reserve ( ts_layer - > lslices . size ( ) ) ;
for ( const ExPolygon & expoly : ts_layer - > lslices )
ts_layer - > lslices_bboxes . emplace_back ( get_extents ( expoly ) ) ;
ts_layer - > backup_untyped_slices ( ) ;
m_object - > print ( ) - > set_status ( 65 , ( boost : : format ( _L ( " Support: generate polygons at layer %d " ) ) % layer_nr ) . str ( ) ) ;
// join roof segments
2022-12-07 12:32:33 +00:00
double contact_dist_scaled = scale_ ( 0.5 ) ; // scale_(m_slicing_params.gap_support_object);
2022-07-15 15:37:19 +00:00
roof_areas = std : : move ( offset2_ex ( roof_areas , contact_dist_scaled , - contact_dist_scaled ) ) ;
roof_1st_layer = std : : move ( offset2_ex ( roof_1st_layer , contact_dist_scaled , - contact_dist_scaled ) ) ;
// avoid object
auto avoid_region_interface = m_ts_data - > get_collision ( m_ts_data - > m_xy_distance , layer_nr ) ;
2022-09-23 12:55:29 +00:00
//roof_areas = std::move(diff_ex(roof_areas, avoid_region_interface));
//roof_1st_layer = std::move(diff_ex(roof_1st_layer, avoid_region_interface));
roof_areas = avoid_object_remove_extra_small_parts ( roof_areas , avoid_region_interface ) ;
roof_1st_layer = avoid_object_remove_extra_small_parts ( roof_1st_layer , avoid_region_interface ) ;
2022-07-15 15:37:19 +00:00
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
roof_1st_layer = std : : move ( diff_ex ( roof_1st_layer , roof_areas ) ) ;
// let supports touch objects when brim is on
auto avoid_region = m_ts_data - > get_collision ( ( layer_nr = = 0 & & has_brim ) ? config . brim_object_gap : m_ts_data - > m_xy_distance , layer_nr ) ;
2022-09-23 12:55:29 +00:00
// base_areas = std::move(diff_ex(base_areas, avoid_region));
base_areas = avoid_object_remove_extra_small_parts ( base_areas , avoid_region ) ;
2022-07-15 15:37:19 +00:00
base_areas = std : : move ( diff_ex ( base_areas , roof_areas ) ) ;
base_areas = std : : move ( diff_ex ( base_areas , roof_1st_layer ) ) ;
2022-11-25 07:17:52 +00:00
base_areas = std : : move ( diff_ex ( base_areas , roof_gap_areas ) ) ;
2022-07-15 15:37:19 +00:00
2022-07-22 09:46:10 +00:00
if ( SQUARE_SUPPORT ) {
// simplify support contours
2022-07-15 15:37:19 +00:00
ExPolygons base_areas_simplified ;
2022-07-22 09:46:10 +00:00
for ( auto & area : base_areas ) { area . simplify ( scale_ ( line_width / 2 ) , & base_areas_simplified , SimplifyMethodDP ) ; }
2022-07-15 15:37:19 +00:00
base_areas = std : : move ( base_areas_simplified ) ;
}
//Subtract support floors. We can only compute floor_areas here instead of with roof_areas,
// or we'll get much wider floor than necessary.
if ( bottom_interface_layers + bottom_gap_layers > 0 )
{
if ( layer_nr > = bottom_interface_layers + bottom_gap_layers )
{
2022-11-20 14:45:29 +00:00
for ( size_t i = 0 ; i < = bottom_gap_layers ; i + + )
{
const Layer * below_layer = m_object - > get_layer ( layer_nr - bottom_interface_layers - i ) ;
ExPolygons bottom_interface = std : : move ( intersection_ex ( base_areas , below_layer - > lslices ) ) ;
floor_areas . insert ( floor_areas . end ( ) , bottom_interface . begin ( ) , bottom_interface . end ( ) ) ;
}
2022-07-15 15:37:19 +00:00
}
if ( floor_areas . empty ( ) = = false ) {
floor_areas = std : : move ( diff_ex ( floor_areas , avoid_region_interface ) ) ;
floor_areas = std : : move ( offset2_ex ( floor_areas , contact_dist_scaled , - contact_dist_scaled ) ) ;
base_areas = std : : move ( diff_ex ( base_areas , offset_ex ( floor_areas , 10 ) ) ) ;
}
}
if ( bottom_gap_layers > 0 & & layer_nr > bottom_gap_layers ) {
const Layer * below_layer = m_object - > get_layer ( layer_nr - bottom_gap_layers ) ;
2022-12-07 12:32:33 +00:00
ExPolygons bottom_gap_area = std : : move ( intersection_ex ( floor_areas , below_layer - > lslices ) ) ;
if ( ! bottom_gap_area . empty ( ) ) {
floor_areas = std : : move ( diff_ex ( floor_areas , bottom_gap_area ) ) ;
2022-07-15 15:37:19 +00:00
}
}
auto & area_groups = ts_layer - > area_groups ;
2022-12-01 13:15:13 +00:00
for ( auto & area : ts_layer - > base_areas ) area_groups . emplace_back ( & area , TreeSupportLayer : : BaseType , max_layers_above_base ) ;
for ( auto & area : ts_layer - > roof_areas ) area_groups . emplace_back ( & area , TreeSupportLayer : : RoofType , max_layers_above_roof ) ;
for ( auto & area : ts_layer - > floor_areas ) area_groups . emplace_back ( & area , TreeSupportLayer : : FloorType , 10000 ) ;
for ( auto & area : ts_layer - > roof_1st_layer ) area_groups . emplace_back ( & area , TreeSupportLayer : : Roof1stLayer , max_layers_above_roof1 ) ;
2022-07-15 15:37:19 +00:00
for ( auto & area_group : area_groups ) {
2022-12-01 13:15:13 +00:00
auto & expoly = area_group . area ;
2022-07-15 15:37:19 +00:00
expoly - > holes . erase ( std : : remove_if ( expoly - > holes . begin ( ) , expoly - > holes . end ( ) ,
[ ] ( auto & hole ) {
auto bbox_size = get_extents ( hole ) . size ( ) ;
return bbox_size [ 0 ] < scale_ ( 2 ) & & bbox_size [ 1 ] < scale_ ( 2 ) ;
} ) ,
expoly - > holes . end ( ) ) ;
}
2022-07-22 09:46:10 +00:00
2022-07-15 15:37:19 +00:00
}
} ) ;
# if 1
2022-07-06 04:16:37 +00:00
if ( with_lightning_infill )
{
const bool global_lightning_infill = true ;
std : : vector < Polygons > contours ;
std : : vector < Polygons > overhangs ;
for ( int layer_nr = 1 ; layer_nr < m_object - > layer_count ( ) ; layer_nr + + ) {
if ( print - > canceled ( ) ) break ;
const std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
assert ( ts_layer ! = nullptr ) ;
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
if ( curr_layer_nodes . empty ( ) ) continue ;
if ( ts_layer - > height < EPSILON ) continue ;
if ( ts_layer - > area_groups . empty ( ) ) continue ;
ExPolygons & base_areas = ts_layer - > base_areas ;
int layer_nr_lower = layer_nr - 1 ;
for ( layer_nr_lower ; layer_nr_lower > = 0 ; layer_nr_lower - - ) {
if ( ! m_object - > get_tree_support_layer ( layer_nr_lower + m_raft_layers ) - > area_groups . empty ( ) ) break ;
2022-07-15 15:37:19 +00:00
}
2022-07-06 04:16:37 +00:00
TreeSupportLayer * lower_layer = m_object - > get_tree_support_layer ( layer_nr_lower + m_raft_layers ) ;
ExPolygons & base_areas_lower = m_object - > get_tree_support_layer ( layer_nr_lower + m_raft_layers ) - > base_areas ;
ExPolygons overhang ;
if ( layer_nr_lower = = 0 )
continue ;
if ( global_lightning_infill )
{
//search overhangs globally
overhang = std : : move ( diff_ex ( offset_ex ( base_areas_lower , - 2.0 * scale_ ( support_extrusion_width ) ) , base_areas ) ) ;
}
else
{
//search overhangs only on floating islands
for ( auto & base_area : base_areas )
for ( auto & hole : base_area . holes )
{
Polygon rev_hole = hole ;
rev_hole . make_counter_clockwise ( ) ;
ExPolygons ex_hole = to_expolygons ( ExPolygon ( rev_hole ) ) ;
for ( auto & other_area : base_areas )
//if (&other_area != &base_area)
ex_hole = std : : move ( diff_ex ( ex_hole , other_area ) ) ;
overhang = std : : move ( union_ex ( overhang , ex_hole ) ) ;
}
overhang = std : : move ( intersection_ex ( overhang , offset_ex ( base_areas_lower , - 0.5 * scale_ ( support_extrusion_width ) ) ) ) ;
}
overhangs . emplace_back ( to_polygons ( overhang ) ) ;
contours . emplace_back ( to_polygons ( base_areas_lower ) ) ; //cant guarantee for 100% success probability, infill fails sometimes
printZ_to_lightninglayer [ lower_layer - > print_z ] = overhangs . size ( ) - 1 ;
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
draw_two_overhangs_to_svg ( m_object - > get_tree_support_layer ( layer_nr_lower + m_raft_layers ) , base_areas_lower , to_expolygons ( overhangs . back ( ) ) ) ;
# endif
2022-07-15 15:37:19 +00:00
}
2022-07-06 04:16:37 +00:00
2022-12-07 02:54:43 +00:00
generator = std : : make_unique < FillLightning : : Generator > ( m_object , contours , overhangs , [ ] ( ) { } , support_density ) ;
2022-07-06 04:16:37 +00:00
}
else if ( ! has_infill ) {
// move the holes to contour so they can be well supported
// check if poly's contour intersects with expoly's contour
auto intersects_contour = [ ] ( Polygon poly , ExPolygon expoly , Point & pt_on_poly , Point & pt_on_expoly , Point & pt_far_on_poly , float dist_thresh = 0.01 ) {
float min_dist = std : : numeric_limits < float > : : max ( ) ;
float max_dist = 0 ;
for ( auto from : poly . points ) {
for ( int i = 0 ; i < expoly . num_contours ( ) ; i + + ) {
const Point * candidate = expoly . contour_or_hole ( i ) . closest_point ( from ) ;
double dist2 = vsize2_with_unscale ( * candidate - from ) ;
if ( dist2 < min_dist ) {
min_dist = dist2 ;
pt_on_poly = from ;
pt_on_expoly = * candidate ;
2022-07-15 15:37:19 +00:00
}
2022-07-06 04:16:37 +00:00
if ( dist2 > max_dist ) {
max_dist = dist2 ;
pt_far_on_poly = from ;
2022-07-15 15:37:19 +00:00
}
2022-07-06 04:16:37 +00:00
if ( dist2 < dist_thresh ) { return true ; }
2022-07-15 15:37:19 +00:00
}
}
2022-07-06 04:16:37 +00:00
return false ;
} ;
// polygon pointer: depth, direction, farPoint
std : : map < const Polygon * , std : : tuple < int , Point , Point > > holePropagationInfos ;
for ( int layer_nr = m_object - > layer_count ( ) - 1 ; layer_nr > 0 ; layer_nr - - ) {
if ( print - > canceled ( ) ) break ;
m_object - > print ( ) - > set_status ( 66 , ( boost : : format ( _L ( " Support: fix holes at layer %d " ) ) % layer_nr ) . str ( ) ) ;
const std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
assert ( ts_layer ! = nullptr ) ;
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
if ( curr_layer_nodes . empty ( ) ) continue ;
if ( ts_layer - > height < EPSILON ) continue ;
if ( ts_layer - > area_groups . empty ( ) ) continue ;
int layer_nr_lower = layer_nr - 1 ;
for ( layer_nr_lower ; layer_nr_lower > = 0 ; layer_nr_lower - - ) {
if ( ! m_object - > get_tree_support_layer ( layer_nr_lower + m_raft_layers ) - > area_groups . empty ( ) ) break ;
2022-10-18 02:46:54 +00:00
}
if ( layer_nr_lower < 0 ) continue ;
2022-07-06 04:16:37 +00:00
auto & area_groups_lower = m_object - > get_tree_support_layer ( layer_nr_lower + m_raft_layers ) - > area_groups ;
for ( const auto & area_group : ts_layer - > area_groups ) {
2022-12-01 13:15:13 +00:00
if ( area_group . type ! = TreeSupportLayer : : BaseType ) continue ;
const auto & area = area_group . area ;
2022-07-06 04:16:37 +00:00
for ( const auto & hole : area - > holes ) {
// auto hole_bbox = get_extents(hole).polygon();
for ( auto & area_group_lower : area_groups_lower ) {
2022-12-01 13:15:13 +00:00
if ( area_group . type ! = TreeSupportLayer : : BaseType ) continue ;
auto & base_area_lower = * area_group_lower . area ;
2022-07-06 04:16:37 +00:00
Point pt_on_poly , pt_on_expoly , pt_far_on_poly ;
// if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
if ( base_area_lower . contour . contains ( hole . points . front ( ) ) & & ! intersects_contour ( hole , base_area_lower , pt_on_poly , pt_on_expoly , pt_far_on_poly ) ) {
Polygon hole_lower = hole ;
Point direction = normal ( pt_on_expoly - pt_on_poly , line_width_scaled / 2 ) ;
hole_lower . translate ( direction ) ;
// note to expand a hole, we need to do negative offset
auto hole_expanded = offset ( hole_lower , - line_width_scaled / 4 , ClipperLib : : JoinType : : jtSquare ) ;
if ( ! hole_expanded . empty ( ) ) {
base_area_lower . holes . push_back ( std : : move ( hole_expanded [ 0 ] ) ) ;
holePropagationInfos . insert ( { & base_area_lower . holes . back ( ) , { 25 , direction , pt_far_on_poly } } ) ;
}
break ;
}
else if ( holePropagationInfos . find ( & hole ) ! = holePropagationInfos . end ( ) & & std : : get < 0 > ( holePropagationInfos [ & hole ] ) > 0 & &
base_area_lower . contour . contains ( std : : get < 2 > ( holePropagationInfos [ & hole ] ) ) ) {
Polygon hole_lower = hole ;
auto & & direction = std : : get < 1 > ( holePropagationInfos [ & hole ] ) ;
hole_lower . translate ( direction ) ;
// note to shrink a hole, we need to do positive offset
auto hole_expanded = offset ( hole_lower , line_width_scaled / 2 , ClipperLib : : JoinType : : jtSquare ) ;
Point farPoint = std : : get < 2 > ( holePropagationInfos [ & hole ] ) + direction * 2 ;
if ( ! hole_expanded . empty ( ) ) {
base_area_lower . holes . push_back ( std : : move ( hole_expanded [ 0 ] ) ) ;
holePropagationInfos . insert ( { & base_area_lower . holes . back ( ) , { std : : get < 0 > ( holePropagationInfos [ & hole ] ) - 1 , direction , farPoint } } ) ;
}
break ;
}
}
{
2022-07-22 09:46:10 +00:00
// if roof1 interface is inside a hole, need to expand the interface
2022-07-06 04:16:37 +00:00
for ( auto & roof1 : ts_layer - > roof_1st_layer ) {
2022-07-22 09:46:10 +00:00
//if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center()))
2022-07-06 04:16:37 +00:00
bool is_inside_hole = std : : all_of ( roof1 . contour . points . begin ( ) , roof1 . contour . points . end ( ) , [ & hole ] ( Point & pt ) { return hole . contains ( pt ) ; } ) ;
2022-07-22 09:46:10 +00:00
if ( is_inside_hole ) {
Polygon hole_reoriented = hole ;
if ( roof1 . contour . is_counter_clockwise ( ) )
hole_reoriented . make_counter_clockwise ( ) ;
else if ( roof1 . contour . is_clockwise ( ) )
hole_reoriented . make_clockwise ( ) ;
2022-07-06 04:16:37 +00:00
auto tmp = union_ ( { roof1 . contour } , { hole_reoriented } ) ;
2022-07-22 09:46:10 +00:00
if ( ! tmp . empty ( ) ) roof1 . contour = tmp [ 0 ] ;
// make sure 1) roof1 and object 2) roof1 and roof, won't intersect
// Note: We can't replace roof1 directly, as we have recorded its address.
// So instead we need to replace its members one by one.
auto tmp1 = diff_ex ( roof1 , m_ts_data - > get_collision ( ( layer_nr = = 0 & & has_brim ) ? config . brim_object_gap : m_ts_data - > m_xy_distance , layer_nr ) ) ;
tmp1 = diff_ex ( tmp1 , ts_layer - > roof_areas ) ;
if ( ! tmp1 . empty ( ) ) {
roof1 . contour = std : : move ( tmp1 [ 0 ] . contour ) ;
2022-07-06 04:16:37 +00:00
roof1 . holes = std : : move ( tmp1 [ 0 ] . holes ) ;
}
2022-07-22 09:46:10 +00:00
break ;
2022-07-06 04:16:37 +00:00
}
2022-07-22 09:46:10 +00:00
}
}
2022-07-06 04:16:37 +00:00
}
2022-07-22 09:46:10 +00:00
}
2022-07-15 15:37:19 +00:00
}
}
2022-07-22 09:46:10 +00:00
# endif
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2022-07-06 04:16:37 +00:00
for ( int layer_nr = m_object - > layer_count ( ) - 1 ; layer_nr > 0 ; layer_nr - - ) {
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
ExPolygons & base_areas = ts_layer - > base_areas ;
ExPolygons & roof_areas = ts_layer - > roof_areas ;
ExPolygons & roof_1st_layer = ts_layer - > roof_1st_layer ;
ExPolygons & floor_areas = ts_layer - > floor_areas ;
if ( base_areas . empty ( ) & & roof_areas . empty ( ) & & roof_1st_layer . empty ( ) ) continue ;
char fname [ 10 ] ; sprintf ( fname , " %d_%.2f " , layer_nr , ts_layer - > print_z ) ;
2022-12-07 12:32:33 +00:00
draw_contours_and_nodes_to_svg ( " " , base_areas , roof_areas , roof_1st_layer , { } , { } , get_svg_filename ( fname , " circles " ) , { " base " , " roof " , " roof1st " } ) ;
2022-07-06 04:16:37 +00:00
}
2022-11-30 03:51:25 +00:00
// export layer & print_z log
std : : ofstream draw_circles_layer_out ;
draw_circles_layer_out . open ( " ./SVG/layer_heights_draw_circles.txt " ) ;
if ( draw_circles_layer_out . is_open ( ) ) {
for ( int layer_nr = m_object - > layer_count ( ) - 1 ; layer_nr > 0 ; layer_nr - - ) {
TreeSupportLayer * ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
ExPolygons & base_areas = ts_layer - > base_areas ;
ExPolygons & roof_areas = ts_layer - > roof_areas ;
ExPolygons & roof_1st_layer = ts_layer - > roof_1st_layer ;
ExPolygons & floor_areas = ts_layer - > floor_areas ;
if ( base_areas . empty ( ) & & roof_areas . empty ( ) & & roof_1st_layer . empty ( ) ) continue ;
draw_circles_layer_out < < layer_nr < < " " < < ts_layer - > print_z < < " " < < ts_layer - > height < < std : : endl ;
}
}
# endif // SUPPORT_TREE_DEBUG_TO_SVG
2022-07-15 15:37:19 +00:00
2022-07-06 04:16:37 +00:00
TreeSupportLayerPtrs & ts_layers = m_object - > tree_support_layers ( ) ;
auto iter = std : : remove_if ( ts_layers . begin ( ) , ts_layers . end ( ) , [ ] ( TreeSupportLayer * ts_layer ) { return ts_layer - > height < EPSILON ; } ) ;
ts_layers . erase ( iter , ts_layers . end ( ) ) ;
for ( int layer_nr = 0 ; layer_nr < ts_layers . size ( ) ; layer_nr + + ) {
ts_layers [ layer_nr ] - > upper_layer = layer_nr ! = ts_layers . size ( ) - 1 ? ts_layers [ layer_nr + 1 ] : nullptr ;
ts_layers [ layer_nr ] - > lower_layer = layer_nr > 0 ? ts_layers [ layer_nr - 1 ] : nullptr ;
}
2022-07-15 15:37:19 +00:00
}
void TreeSupport : : drop_nodes ( std : : vector < std : : vector < Node * > > & contact_nodes )
{
const PrintObjectConfig & config = m_object - > config ( ) ;
2022-11-30 03:51:25 +00:00
// Use Minimum Spanning Tree to connect the points on each layer and move them while dropping them down.
2022-12-07 12:32:33 +00:00
const coordf_t support_extrusion_width = m_support_params . support_extrusion_width ;
2022-07-15 15:37:19 +00:00
const coordf_t layer_height = config . layer_height . value ;
const double angle = config . tree_support_branch_angle . value * M_PI / 180. ;
2022-12-01 13:15:13 +00:00
const int wall_count = std : : max ( 1 , config . tree_support_wall_count . value ) ;
2022-12-07 12:32:33 +00:00
double tan_angle = tan ( angle ) ; // when nodes are thick, they can move further. this is the max angle
2022-12-01 13:15:13 +00:00
const coordf_t max_move_distance = ( angle < M_PI / 2 ) ? ( coordf_t ) ( tan_angle * layer_height ) * wall_count : std : : numeric_limits < coordf_t > : : max ( ) ;
2022-07-15 15:37:19 +00:00
const double max_move_distance2 = max_move_distance * max_move_distance ;
const coordf_t branch_radius = config . tree_support_branch_diameter . value / 2 ;
const size_t tip_layers = branch_radius / layer_height ; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle.
2022-12-07 12:32:33 +00:00
const double diameter_angle_scale_factor = tan ( tree_support_branch_diameter_angle * M_PI / 180. ) ; //*layer_height / branch_radius; // Scale factor per layer to produce the desired angle.
2022-07-15 15:37:19 +00:00
const coordf_t radius_sample_resolution = m_ts_data - > m_radius_sample_resolution ;
const bool support_on_buildplate_only = config . support_on_build_plate_only . value ;
const size_t bottom_interface_layers = config . support_interface_bottom_layers . value ;
const size_t top_interface_layers = config . support_interface_top_layers . value ;
2022-12-07 12:32:33 +00:00
float DO_NOT_MOVER_UNDER_MM = is_slim ? 0 : 5 ; // do not move contact points under 5mm
auto get_branch_angle = [ this , & config ] ( coordf_t radius ) {
if ( config . tree_support_branch_angle . value < 30.0 ) return config . tree_support_branch_angle . value ;
return ( radius - MIN_BRANCH_RADIUS ) / ( MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS ) * ( config . tree_support_branch_angle . value - 30.0 ) + 30.0 ;
} ;
auto get_max_move_dist = [ this , & config , branch_radius , tip_layers , diameter_angle_scale_factor , wall_count , support_extrusion_width ] ( const Node * node , int power = 1 ) {
double move_dist = node - > max_move_dist ;
if ( node - > max_move_dist = = 0 ) {
if ( node - > radius = = 0 ) node - > radius = calc_branch_radius ( branch_radius , node - > dist_mm_to_top , diameter_angle_scale_factor ) ;
double angle = config . tree_support_branch_angle . value ;
if ( angle > 30.0 & & node - > radius > MIN_BRANCH_RADIUS )
angle = ( node - > radius - MIN_BRANCH_RADIUS ) / ( MAX_BRANCH_RADIUS - MIN_BRANCH_RADIUS ) * ( config . tree_support_branch_angle . value - 30.0 ) + 30.0 ;
double tan_angle = tan ( angle * M_PI / 180 ) ;
int wall_count_ = node - > radius > 2 * config . support_line_width ? wall_count : 1 ;
node - > max_move_dist = ( angle < 90 ) ? ( coordf_t ) ( tan_angle * node - > height ) * wall_count_ : std : : numeric_limits < coordf_t > : : max ( ) ;
node - > max_move_dist = std : : min ( node - > max_move_dist , support_extrusion_width ) ;
move_dist = node - > max_move_dist ;
}
if ( power = = 2 ) move_dist = SQ ( move_dist ) ;
return move_dist ;
} ;
2022-11-30 03:51:25 +00:00
std : : vector < std : : pair < coordf_t , coordf_t > > layer_heights = plan_layer_heights ( contact_nodes ) ;
2022-12-07 12:32:33 +00:00
if ( layer_heights . empty ( ) ) return ;
2022-07-15 15:37:19 +00:00
std : : unordered_set < Node * > to_free_node_set ;
m_spanning_trees . resize ( contact_nodes . size ( ) ) ;
//m_mst_line_x_layer_contour_caches.resize(contact_nodes.size());
2022-12-07 12:32:33 +00:00
if ( ! is_slim )
2022-07-15 15:37:19 +00:00
{ // get outlines below and avoidance area using tbb
//m_object->print()->set_status(59, "Support: preparing avoidance regions ");
// get all the possible radiis
std : : vector < std : : set < coordf_t > > all_layer_radius ( m_highest_overhang_layer + 1 ) ;
2022-11-25 07:17:52 +00:00
std : : vector < std : : set < int > > all_layer_node_dist ( m_highest_overhang_layer + 1 ) ;
2022-07-15 15:37:19 +00:00
for ( size_t layer_nr = m_highest_overhang_layer ; layer_nr > 0 ; layer_nr - - )
{
auto & layer_contact_nodes = contact_nodes [ layer_nr ] ;
auto & layer_radius = all_layer_radius [ layer_nr ] ;
auto & layer_node_dist = all_layer_node_dist [ layer_nr ] ;
if ( ! layer_contact_nodes . empty ( ) ) {
for ( Node * p_node : layer_contact_nodes ) {
layer_node_dist . emplace ( p_node - > distance_to_top ) ;
}
}
if ( layer_nr < m_highest_overhang_layer ) {
for ( auto node_dist : all_layer_node_dist [ layer_nr + 1 ] )
layer_node_dist . emplace ( node_dist + 1 ) ;
}
for ( auto node_dist : layer_node_dist ) {
layer_radius . emplace ( calc_branch_radius ( branch_radius , node_dist , tip_layers , diameter_angle_scale_factor ) ) ;
}
}
// parallel pre-compute avoidance
tbb : : parallel_for ( tbb : : blocked_range < size_t > ( 1 , m_highest_overhang_layer ) ,
[ & ] ( const tbb : : blocked_range < size_t > & range ) {
for ( size_t layer_nr = range . begin ( ) ; layer_nr < range . end ( ) ; layer_nr + + ) {
for ( auto node_dist : all_layer_node_dist [ layer_nr ] )
{
m_ts_data - > get_avoidance ( 0 , layer_nr - 1 ) ;
m_ts_data - > get_avoidance ( calc_branch_radius ( branch_radius , node_dist , tip_layers , diameter_angle_scale_factor ) , layer_nr - 1 ) ;
}
}
} ) ;
BOOST_LOG_TRIVIAL ( debug ) < < " before m_avoidance_cache.size()= " < < m_ts_data - > m_avoidance_cache . size ( ) ;
}
2022-11-30 03:51:25 +00:00
for ( size_t layer_nr = contact_nodes . size ( ) - 1 ; layer_nr > 0 ; layer_nr - - ) // Skip layer 0, since we can't drop down the vertices there.
2022-07-15 15:37:19 +00:00
{
if ( m_object - > print ( ) - > canceled ( ) )
break ;
auto & layer_contact_nodes = contact_nodes [ layer_nr ] ;
if ( layer_contact_nodes . empty ( ) )
continue ;
2022-11-30 03:51:25 +00:00
2022-12-07 12:32:33 +00:00
int layer_nr_next = layer_nr - 1 ;
while ( layer_nr_next > = 0 & & layer_heights [ layer_nr_next ] . second < EPSILON )
layer_nr_next - - ;
coordf_t print_z_next = layer_heights [ layer_nr_next ] . first ;
coordf_t height_next = layer_heights [ layer_nr_next ] . second ;
2022-11-30 03:51:25 +00:00
std : : deque < std : : pair < size_t , Node * > > unsupported_branch_leaves ; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches.
const Layer * ts_layer = m_object - > get_tree_support_layer ( layer_nr ) ;
2022-07-15 15:37:19 +00:00
m_object - > print ( ) - > set_status ( 60 , ( boost : : format ( _L ( " Support: propagate branches at layer %d " ) ) % layer_nr ) . str ( ) ) ;
Polygons layer_contours = std : : move ( m_ts_data - > get_contours_with_holes ( layer_nr ) ) ;
//std::unordered_map<Line, bool, LineHash>& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
std : : unordered_map < Line , bool , LineHash > mst_line_x_layer_contour_cache ;
auto is_line_cut_by_contour = [ & mst_line_x_layer_contour_cache , & layer_contours ] ( Point a , Point b )
{
auto iter = mst_line_x_layer_contour_cache . find ( { a , b } ) ;
if ( iter ! = mst_line_x_layer_contour_cache . end ( ) ) {
if ( iter - > second )
return true ;
}
else {
profiler . tic ( ) ;
Line ln ( b , a ) ;
Lines pls_intersect = intersection_ln ( ln , layer_contours ) ;
mst_line_x_layer_contour_cache . insert ( { { a , b } , ! pls_intersect . empty ( ) } ) ;
mst_line_x_layer_contour_cache . insert ( { ln , ! pls_intersect . empty ( ) } ) ;
profiler . stage_add ( STAGE_intersection_ln , true ) ;
if ( ! pls_intersect . empty ( ) )
return true ;
}
return false ;
} ;
//Group together all nodes for each part.
const ExPolygons & parts = m_ts_data - > get_avoidance ( 0 , layer_nr ) ;
std : : vector < std : : unordered_map < Point , Node * , PointHash > > nodes_per_part ( 1 + parts . size ( ) ) ; //All nodes that aren't inside a part get grouped together in the 0th part.
for ( Node * p_node : layer_contact_nodes )
{
const Node & node = * p_node ;
2022-11-25 07:17:52 +00:00
if ( node . distance_to_top < 0 ) {
2022-12-07 12:32:33 +00:00
// gap nodes do not merge or move
2022-11-25 07:17:52 +00:00
Node * next_node = new Node ( p_node - > position , p_node - > distance_to_top + 1 , p_node - > skin_direction , p_node - > support_roof_layers_below - 1 , p_node - > to_buildplate , p_node ,
2022-12-07 12:32:33 +00:00
print_z_next , height_next ) ;
get_max_move_dist ( next_node ) ;
next_node - > is_merged = false ;
contact_nodes [ layer_nr_next ] . emplace_back ( next_node ) ;
2022-11-25 07:17:52 +00:00
continue ;
}
2022-07-15 15:37:19 +00:00
if ( support_on_buildplate_only & & ! node . to_buildplate ) //Can't rest on model and unable to reach the build plate. Then we must drop the node and leave parts unsupported.
{
unsupported_branch_leaves . push_front ( { layer_nr , p_node } ) ;
continue ;
}
if ( node . to_buildplate | | parts . empty ( ) ) //It's outside, so make it go towards the build plate.
{
nodes_per_part [ 0 ] [ node . position ] = p_node ;
continue ;
}
2022-07-22 09:46:10 +00:00
if ( node . type = = ePolygon ) {
// polygon node do not merge or move
2022-10-14 10:34:50 +00:00
const bool to_buildplate = ! is_inside_ex ( m_ts_data - > m_layer_outlines [ layer_nr ] , p_node - > position ) ;
Node * next_node = new Node ( p_node - > position , p_node - > distance_to_top + 1 , p_node - > skin_direction , p_node - > support_roof_layers_below - 1 , to_buildplate , p_node ,
2022-12-07 12:32:33 +00:00
print_z_next , height_next ) ;
next_node - > max_move_dist = 0 ;
next_node - > is_merged = false ;
contact_nodes [ layer_nr_next ] . emplace_back ( next_node ) ;
2022-07-22 09:46:10 +00:00
continue ;
}
2022-07-15 15:37:19 +00:00
/* Find which part this node is located in and group the nodes in
* the same part together . Since nodes have a radius and the
* avoidance areas are offset by that radius , the set of parts may
* be different per node . Here we consider a node to be inside the
* part that is closest . The node may be inside a bigger part that
* is actually two parts merged together due to an offset . In that
* case we may incorrectly keep two nodes separate , but at least
* every node falls into some group .
*/
coordf_t closest_part_distance2 = std : : numeric_limits < coordf_t > : : max ( ) ;
size_t closest_part = - 1 ;
for ( size_t part_index = 0 ; part_index < parts . size ( ) ; part_index + + )
{
//constexpr bool border_result = true;
if ( is_inside_ex ( parts [ part_index ] , node . position ) ) //If it's inside, the distance is 0 and this part is considered the best.
{
closest_part = part_index ;
closest_part_distance2 = 0 ;
break ;
}
Point closest_point = * parts [ part_index ] . contour . closest_point ( node . position ) ;
const coordf_t distance2 = vsize2_with_unscale ( node . position - closest_point ) ;
if ( distance2 < closest_part_distance2 )
{
closest_part_distance2 = distance2 ;
closest_part = part_index ;
}
}
//Put it in the best one.
nodes_per_part [ closest_part + 1 ] [ node . position ] = p_node ; //Index + 1 because the 0th index is the outside part.
}
//Create a MST for every part.
profiler . tic ( ) ;
//std::vector<MinimumSpanningTree>& spanning_trees = m_spanning_trees[layer_nr];
std : : vector < MinimumSpanningTree > spanning_trees ;
for ( const std : : unordered_map < Point , Node * , PointHash > & group : nodes_per_part )
{
std : : vector < Point > points_to_buildplate ;
for ( const std : : pair < const Point , Node * > & entry : group )
{
points_to_buildplate . emplace_back ( entry . first ) ; //Just the position of the node.
}
spanning_trees . emplace_back ( points_to_buildplate ) ;
}
profiler . stage_add ( STAGE_MinimumSpanningTree , true ) ;
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
coordf_t branch_radius_temp = 0 ;
coordf_t max_y = std : : numeric_limits < coordf_t > : : min ( ) ;
2022-12-07 12:32:33 +00:00
draw_layer_mst ( std : : to_string ( layer_nr ) , spanning_trees , m_object - > get_layer ( layer_nr ) - > lslices ) ;
2022-07-15 15:37:19 +00:00
# endif
for ( size_t group_index = 0 ; group_index < nodes_per_part . size ( ) ; group_index + + )
{
const MinimumSpanningTree & mst = spanning_trees [ group_index ] ;
//In the first pass, merge all nodes that are close together.
std : : unordered_set < Node * > to_delete ;
for ( const std : : pair < const Point , Node * > & entry : nodes_per_part [ group_index ] )
{
Node * p_node = entry . second ;
Node & node = * p_node ;
if ( to_delete . find ( p_node ) ! = to_delete . end ( ) )
{
continue ; //Delete this node (don't create a new node for it on the next layer).
}
const std : : vector < Point > & neighbours = mst . adjacent_nodes ( node . position ) ;
if ( neighbours . size ( ) = = 1 & & vsize2_with_unscale ( neighbours [ 0 ] - node . position ) < max_move_distance2 & & mst . adjacent_nodes ( neighbours [ 0 ] ) . size ( ) = = 1 ) //We have just two nodes left, and they're very close!
{
//Insert a completely new node and let both original nodes fade.
Point next_position = ( node . position + neighbours [ 0 ] ) / 2 ; //Average position of the two nodes.
2022-12-07 12:32:33 +00:00
const coordf_t branch_radius_node = calc_branch_radius ( branch_radius , node . dist_mm_to_top , diameter_angle_scale_factor ) ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
auto avoid_layer = m_ts_data - > get_avoidance ( branch_radius_node , layer_nr_next ) ;
2022-07-15 15:37:19 +00:00
if ( group_index = = 0 )
{
//Avoid collisions.
const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON ; //100 micron extra for rounding errors.
move_out_expolys ( avoid_layer , next_position , radius_sample_resolution + EPSILON , max_move_between_samples ) ;
}
Node * neighbour = nodes_per_part [ group_index ] [ neighbours [ 0 ] ] ;
size_t new_distance_to_top = std : : max ( node . distance_to_top , neighbour - > distance_to_top ) + 1 ;
size_t new_support_roof_layers_below = std : : max ( node . support_roof_layers_below , neighbour - > support_roof_layers_below ) - 1 ;
2022-12-07 12:32:33 +00:00
double new_dist_mm_to_top = std : : max ( node . dist_mm_to_top , neighbour - > dist_mm_to_top ) + node . height ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
const bool to_buildplate = ! is_inside_ex ( m_ts_data - > get_avoidance ( 0 , layer_nr_next ) , next_position ) ;
2022-10-18 02:46:54 +00:00
Node * next_node = new Node ( next_position , new_distance_to_top , node . skin_direction , new_support_roof_layers_below , to_buildplate , p_node ,
2022-12-07 12:32:33 +00:00
layer_heights [ layer_nr_next ] . first , layer_heights [ layer_nr_next ] . second , new_dist_mm_to_top ) ;
2022-07-15 15:37:19 +00:00
next_node - > movement = next_position - node . position ;
2022-12-07 12:32:33 +00:00
get_max_move_dist ( next_node ) ;
next_node - > is_merged = true ;
contact_nodes [ layer_nr_next ] . push_back ( next_node ) ;
2022-07-15 15:37:19 +00:00
// Make sure the next pass doesn't drop down either of these (since that already happened).
node . merged_neighbours . push_front ( neighbour ) ;
to_delete . insert ( neighbour ) ;
to_delete . insert ( p_node ) ;
}
else if ( neighbours . size ( ) > 1 ) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance.
{
//Remove all neighbours that are too close and merge them into this node.
for ( const Point & neighbour : neighbours )
{
2022-12-07 12:32:33 +00:00
if ( vsize2_with_unscale ( neighbour - node . position ) < /*max_move_distance2*/ get_max_move_dist ( & node , 2 ) )
2022-07-15 15:37:19 +00:00
{
Node * neighbour_node = nodes_per_part [ group_index ] [ neighbour ] ;
node . distance_to_top = std : : max ( node . distance_to_top , neighbour_node - > distance_to_top ) ;
node . support_roof_layers_below = std : : max ( node . support_roof_layers_below , neighbour_node - > support_roof_layers_below ) ;
2022-12-07 12:32:33 +00:00
node . dist_mm_to_top = std : : max ( node . dist_mm_to_top , neighbour_node - > dist_mm_to_top ) ;
2022-07-15 15:37:19 +00:00
node . merged_neighbours . push_front ( neighbour_node ) ;
2022-12-07 12:32:33 +00:00
node . merged_neighbours . insert ( node . merged_neighbours . end ( ) , neighbour_node - > merged_neighbours . begin ( ) , neighbour_node - > merged_neighbours . end ( ) ) ;
node . is_merged = true ;
2022-07-15 15:37:19 +00:00
to_delete . insert ( neighbour_node ) ;
}
}
}
}
//In the second pass, move all middle nodes.
for ( const std : : pair < const Point , Node * > & entry : nodes_per_part [ group_index ] )
{
Node * p_node = entry . second ;
const Node & node = * p_node ;
if ( to_delete . find ( p_node ) ! = to_delete . end ( ) )
{
continue ;
}
//If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it.
if ( group_index > 0 & & is_inside_ex ( m_ts_data - > get_collision ( m_ts_data - > m_xy_distance , layer_nr ) , node . position ) )
{
2022-12-07 12:32:33 +00:00
const coordf_t branch_radius_node = calc_branch_radius ( branch_radius , node . dist_mm_to_top , diameter_angle_scale_factor ) ;
2022-07-15 15:37:19 +00:00
Point to_outside = projection_onto_ex ( m_ts_data - > get_collision ( m_ts_data - > m_xy_distance , layer_nr ) , node . position ) ;
double dist2_to_outside = vsize2_with_unscale ( node . position - to_outside ) ;
if ( dist2_to_outside > = branch_radius_node * branch_radius_node ) //Too far inside.
{
if ( support_on_buildplate_only )
{
unsupported_branch_leaves . push_front ( { layer_nr , p_node } ) ;
}
2022-10-18 02:46:54 +00:00
else {
2022-07-15 15:37:19 +00:00
Node * pn = p_node ;
2022-10-18 02:46:54 +00:00
for ( int i = 0 ; i < = bottom_interface_layers & & pn ; i + + , pn = pn - > parent )
pn - > support_floor_layers_above = bottom_interface_layers - i + 1 ; // +1 so the parent node has support_floor_layers_above=2
2022-07-15 15:37:19 +00:00
to_delete . insert ( p_node ) ;
2022-10-18 02:46:54 +00:00
}
2022-07-15 15:37:19 +00:00
continue ;
}
2022-10-18 02:46:54 +00:00
// if the link between parent and current is cut by contours, mark current as bottom contact node
2022-07-15 15:37:19 +00:00
if ( p_node - > parent & & intersection_ln ( { p_node - > position , p_node - > parent - > position } , layer_contours ) . empty ( ) = = false )
{
Node * pn = p_node - > parent ;
2022-10-18 02:46:54 +00:00
for ( int i = 0 ; i < = bottom_interface_layers & & pn ; i + + , pn = pn - > parent )
pn - > support_floor_layers_above = bottom_interface_layers - i + 1 ;
2022-07-15 15:37:19 +00:00
to_delete . insert ( p_node ) ;
continue ;
}
}
Point next_layer_vertex = node . position ;
Point move_to_neighbor_center ;
2022-12-07 12:32:33 +00:00
std : : vector < Point > moves ;
std : : vector < float > weights ;
2022-07-15 15:37:19 +00:00
const std : : vector < Point > neighbours = mst . adjacent_nodes ( node . position ) ;
// 1. do not merge neighbors under 5mm
// 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height]
float dist2_to_first_neighbor = neighbours . empty ( ) ? 0 : vsize2_with_unscale ( neighbours [ 0 ] - node . position ) ;
if ( ts_layer - > print_z > DO_NOT_MOVER_UNDER_MM & &
2022-12-07 12:32:33 +00:00
( neighbours . size ( ) > 1 | | ( neighbours . size ( ) = = 1 & & dist2_to_first_neighbor > = max_move_distance2 ) ) ) // Only nodes that aren't about to collapse.
2022-07-15 15:37:19 +00:00
{
2022-12-07 12:32:33 +00:00
// Move towards the average position of all neighbours.
2022-07-15 15:37:19 +00:00
Point sum_direction ( 0 , 0 ) ;
2022-12-07 12:32:33 +00:00
for ( const Point & neighbour : neighbours ) {
// do not move to the neighbor to be deleted
Node * neighbour_node = nodes_per_part [ group_index ] [ neighbour ] ;
if ( to_delete . find ( neighbour_node ) ! = to_delete . end ( ) ) continue ;
2022-12-01 13:15:13 +00:00
2022-07-15 15:37:19 +00:00
Point direction = neighbour - node . position ;
2022-12-07 12:32:33 +00:00
// do not move to neighbor that's too far away (即使以最大速度移动,在接触热床之前都无法汇聚)
float dist2_to_neighbor = vsize2_with_unscale ( direction ) ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
coordf_t branch_bottom_radius = calc_branch_radius ( branch_radius , node . dist_mm_to_top + node . print_z , diameter_angle_scale_factor ) ;
coordf_t neighbour_bottom_radius = calc_branch_radius ( branch_radius , neighbour_node - > dist_mm_to_top + neighbour_node - > print_z , diameter_angle_scale_factor ) ;
double max_converge_distance = tan_angle * ( ts_layer - > print_z - DO_NOT_MOVER_UNDER_MM ) + std : : max ( branch_bottom_radius , neighbour_bottom_radius ) ;
if ( dist2_to_neighbor > max_converge_distance * max_converge_distance ) continue ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
if ( is_line_cut_by_contour ( node . position , neighbour ) ) continue ;
if ( is_slim )
sum_direction + = direction * ( 1 / dist2_to_neighbor ) ;
else
sum_direction + = direction ;
2022-07-15 15:37:19 +00:00
}
2022-12-07 12:32:33 +00:00
if ( is_slim )
2022-07-15 15:37:19 +00:00
move_to_neighbor_center = sum_direction ;
2022-12-07 12:32:33 +00:00
else {
if ( vsize2_with_unscale ( sum_direction ) < = max_move_distance2 ) {
move_to_neighbor_center = sum_direction ;
} else {
move_to_neighbor_center = normal ( sum_direction , scale_ ( get_max_move_dist ( & node ) ) ) ;
}
2022-07-15 15:37:19 +00:00
}
}
2022-12-07 12:32:33 +00:00
const coordf_t branch_radius_node = calc_branch_radius ( branch_radius , node . dist_mm_to_top /*+node.print_z*/ , diameter_angle_scale_factor ) ;
2022-07-15 15:37:19 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
if ( node . position ( 1 ) > max_y ) {
2022-12-07 12:32:33 +00:00
max_y = node . position ( 1 ) ;
2022-07-15 15:37:19 +00:00
branch_radius_temp = branch_radius_node ;
}
# endif
2022-12-07 12:32:33 +00:00
auto avoid_layer = m_ts_data - > get_avoidance ( branch_radius_node , layer_nr_next ) ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
Point to_outside = projection_onto_ex ( avoid_layer , node . position ) ;
Point direction_to_outer = to_outside - node . position ;
double dist2_to_outer = vsize2_with_unscale ( direction_to_outer ) ;
2022-07-15 15:37:19 +00:00
// don't move if
// 1) line of node and to_outside is cut by contour (means supports may intersect with object)
// 2) it's impossible to move to build plate
2022-12-07 12:32:33 +00:00
if ( is_line_cut_by_contour ( node . position , to_outside ) | | dist2_to_outer > max_move_distance2 * SQ ( layer_nr ) | |
! is_inside_ex ( avoid_layer , node . position ) ) {
// try move to outside of lower layer instead
Point candidate_vertex = node . position ;
const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON ; // 100 micron extra for rounding errors.
bool is_outside = move_out_expolys ( avoid_layer , candidate_vertex , max_move_between_samples , max_move_between_samples ) ;
if ( is_outside ) {
direction_to_outer = candidate_vertex - node . position ;
dist2_to_outer = vsize2_with_unscale ( direction_to_outer ) ;
} else {
direction_to_outer = Point ( 0 , 0 ) ;
dist2_to_outer = 0 ;
}
2022-07-15 15:37:19 +00:00
}
// move to the averaged direction of neighbor center and contour edge if they are roughly same direction
2022-12-07 12:32:33 +00:00
Point movement ;
if ( is_slim )
movement = move_to_neighbor_center * 2 + ( dist2_to_outer > EPSILON ? direction_to_outer * ( 1 / dist2_to_outer ) : Point ( 0 , 0 ) ) ;
else {
if ( movement . dot ( move_to_neighbor_center ) > = 0.2 | | move_to_neighbor_center = = Point ( 0 , 0 ) )
movement = direction_to_outer + move_to_neighbor_center ;
else
movement = move_to_neighbor_center ; // otherwise move to neighbor center first
}
2022-12-01 13:15:13 +00:00
2022-12-07 12:32:33 +00:00
if ( vsize2_with_unscale ( movement ) > get_max_move_dist ( & node , 2 ) )
movement = normal ( movement , scale_ ( get_max_move_dist ( & node ) ) ) ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
// add momentum to force smooth movement
//movement = movement * 0.5 + p_node->movement * 0.5;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
next_layer_vertex + = movement ;
2022-07-15 15:37:19 +00:00
const bool to_buildplate = ! is_inside_ex ( m_ts_data - > m_layer_outlines [ layer_nr ] , next_layer_vertex ) ; // !is_inside_ex(m_ts_data->get_avoidance(m_ts_data->m_xy_distance, layer_nr - 1), next_layer_vertex);
Node * next_node = new Node ( next_layer_vertex , node . distance_to_top + 1 , node . skin_direction , node . support_roof_layers_below - 1 , to_buildplate , p_node ,
2022-12-07 12:32:33 +00:00
print_z_next , height_next ) ;
2022-07-15 15:37:19 +00:00
next_node - > movement = movement ;
2022-12-07 12:32:33 +00:00
get_max_move_dist ( next_node ) ;
next_node - > is_merged = false ;
contact_nodes [ layer_nr_next ] . push_back ( next_node ) ;
2022-07-15 15:37:19 +00:00
}
}
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2022-12-07 12:32:33 +00:00
draw_contours_and_nodes_to_svg ( std : : to_string ( ts_layer - > print_z ) , m_ts_data - > get_avoidance ( 0 , layer_nr ) , m_ts_data - > get_avoidance ( branch_radius_temp , layer_nr ) , m_ts_data - > m_layer_outlines_below [ layer_nr ] ,
contact_nodes [ layer_nr ] , contact_nodes [ layer_nr_next ] , " contact_points " , { " overhang " , " avoid " , " outline " } , { " blue " , " red " , " yellow " } ) ;
if ( contact_nodes [ layer_nr ] . empty ( ) = = false ) {
BOOST_LOG_TRIVIAL ( debug ) < < " drop_nodes layer " < < layer_nr < < " , print_z= " < < ts_layer - > print_z ;
for ( size_t i = 0 ; i < std : : min ( size_t ( 5 ) , contact_nodes [ layer_nr ] . size ( ) ) ; i + + ) {
auto & node = contact_nodes [ layer_nr ] [ i ] ;
BOOST_LOG_TRIVIAL ( debug ) < < " \t node " < < i < < " , pos= " < < node - > position < < " , move = " < < node - > movement < < " , is_merged= " < < node - > is_merged ;
}
}
2022-07-15 15:37:19 +00:00
# endif
// Prune all branches that couldn't find support on either the model or the buildplate (resulting in 'mid-air' branches).
for ( ; ! unsupported_branch_leaves . empty ( ) ; unsupported_branch_leaves . pop_back ( ) )
{
const auto & entry = unsupported_branch_leaves . back ( ) ;
Node * i_node = entry . second ;
for ( size_t i_layer = entry . first ; i_node ! = nullptr ; + + i_layer , i_node = i_node - > parent )
{
std : : vector < Node * > : : iterator to_erase = std : : find ( contact_nodes [ i_layer ] . begin ( ) , contact_nodes [ i_layer ] . end ( ) , i_node ) ;
if ( to_erase ! = contact_nodes [ i_layer ] . end ( ) )
{
to_free_node_set . insert ( * to_erase ) ;
contact_nodes [ i_layer ] . erase ( to_erase ) ;
to_free_node_set . insert ( i_node ) ;
for ( Node * neighbour : i_node - > merged_neighbours )
{
unsupported_branch_leaves . push_front ( { i_layer , neighbour } ) ;
}
}
}
}
}
2022-09-30 08:30:10 +00:00
// delete nodes with no children (means either it's a single layer nodes, or the branch has been deleted but not completely)
for ( size_t layer_nr = contact_nodes . size ( ) - 1 ; layer_nr > 0 ; layer_nr - - ) {
auto layer_contact_nodes = contact_nodes [ layer_nr ] ;
for ( Node * p_node : layer_contact_nodes ) {
if ( p_node - > child = = nullptr ) {
std : : vector < Node * > : : iterator to_erase = std : : find ( contact_nodes [ layer_nr ] . begin ( ) , contact_nodes [ layer_nr ] . end ( ) , p_node ) ;
if ( to_erase ! = contact_nodes [ layer_nr ] . end ( ) ) {
to_free_node_set . insert ( * to_erase ) ;
contact_nodes [ layer_nr ] . erase ( to_erase ) ;
}
}
}
}
2022-07-15 15:37:19 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " after m_avoidance_cache.size()= " < < m_ts_data - > m_avoidance_cache . size ( ) ;
for ( Node * node : to_free_node_set )
{
delete node ;
}
to_free_node_set . clear ( ) ;
2022-11-30 03:51:25 +00:00
// Merge empty contact_nodes layers
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
// export all print_z and layer height into .txt
std : : ofstream layer_heights_out ;
layer_heights_out . open ( " ./SVG/layer_heights_drop_nodes.txt " ) ;
//layer_heights_out.open("layer_heights_out.txt");
if ( layer_heights_out . is_open ( ) ) {
for ( int i = 0 ; i < layer_heights . size ( ) ; i + + ) {
if ( contact_nodes [ i ] . empty ( ) ) {
layer_heights_out < < 0 < < " " < < 0 < < std : : endl ;
}
else {
layer_heights_out < < contact_nodes [ i ] [ 0 ] - > print_z < < " " < < contact_nodes [ i ] [ 0 ] - > height < < std : : endl ;
}
}
layer_heights_out . close ( ) ;
}
# endif
2022-07-15 15:37:19 +00:00
}
2022-12-07 12:32:33 +00:00
void TreeSupport : : smooth_nodes ( std : : vector < std : : vector < Node * > > & contact_nodes )
{
std : : map < Node * , bool > is_processed ;
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
if ( curr_layer_nodes . empty ( ) ) continue ;
for ( Node * node : curr_layer_nodes ) {
is_processed . emplace ( node , false ) ;
if ( layer_nr = = 0 ) node - > is_merged = true ; // nodes on plate are also merged nodes
}
}
for ( int layer_nr = contact_nodes . size ( ) - 1 ; layer_nr > = 0 ; layer_nr - - ) {
std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
if ( curr_layer_nodes . empty ( ) ) continue ;
for ( Node * node : curr_layer_nodes ) {
if ( ! is_processed [ node ] ) {
std : : vector < Point > pts ;
std : : vector < Node * > branch ;
Node * p_node = node ;
// add head for second path from the merged node if there are multiple ones
if ( ! node - > is_merged & & node - > parent ) {
pts . push_back ( p_node - > parent - > position ) ;
branch . push_back ( p_node - > parent ) ;
}
do {
pts . push_back ( p_node - > position ) ;
//is_processed[p_node] = true;
branch . push_back ( p_node ) ;
p_node = p_node - > child ;
} while ( p_node & & ! p_node - > is_merged & & ! is_processed [ p_node ] ) ;
// TODO is it necessary to add tail also?
if ( p_node & & p_node - > is_merged ) {
pts . push_back ( p_node - > position ) ;
branch . push_back ( p_node ) ;
}
if ( pts . size ( ) < 3 ) continue ;
std : : vector < Point > pts1 = pts ;
// TODO here we assume layer height gap is constant. If not true, need to consider height jump
for ( size_t k = 0 ; k < 2 ; k + + ) {
for ( size_t i = 1 ; i < pts . size ( ) - 1 ; i + + ) {
size_t i2 = i > = 2 ? i - 2 : 0 ;
size_t i3 = i < pts . size ( ) - 2 ? i + 2 : pts . size ( ) - 1 ;
Point pt = ( pts [ i2 ] + pts [ i - 1 ] + pts [ i ] + pts [ i + 1 ] + pts [ i3 ] ) / 5 ;
pts1 [ i ] = pt ;
}
std : : swap ( pts , pts1 ) ;
}
for ( size_t i = 1 ; i < pts . size ( ) - 1 ; i + + ) {
if ( ! is_processed [ branch [ i ] ] ) {
branch [ i ] - > position = pts [ i ] ;
is_processed [ branch [ i ] ] = true ;
}
}
}
}
}
}
2022-07-15 15:37:19 +00:00
void TreeSupport : : adjust_layer_heights ( std : : vector < std : : vector < Node * > > & contact_nodes )
{
if ( contact_nodes . empty ( ) )
return ;
2022-11-09 10:13:39 +00:00
const PrintConfig & print_config = m_object - > print ( ) - > config ( ) ;
2022-07-15 15:37:19 +00:00
const PrintObjectConfig & config = m_object - > config ( ) ;
2022-12-07 12:32:33 +00:00
// don't merge layers for Vine support, or the branches will be unsmooth
// TODO can we merge layers in a way that guaranttees smoothness?
if ( ! print_config . independent_support_layer_height | | is_slim ) {
2022-07-15 15:37:19 +00:00
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
for ( Node * node : curr_layer_nodes ) {
node - > print_z = m_object - > get_layer ( layer_nr ) - > print_z ;
node - > height = m_object - > get_layer ( layer_nr ) - > height ;
}
}
return ;
}
// extreme layer_id
std : : vector < int > extremes ;
const coordf_t layer_height = config . layer_height . value ;
const coordf_t max_layer_height = m_slicing_params . max_layer_height ;
const size_t bot_intf_layers = config . support_interface_bottom_layers . value ;
const size_t top_intf_layers = config . support_interface_top_layers . value ;
// if already using max layer height, no need to adjust
if ( layer_height = = max_layer_height ) return ;
extremes . push_back ( 0 ) ;
for ( Node * node : contact_nodes [ 0 ] ) {
node - > print_z = m_object - > get_layer ( 0 ) - > print_z ;
node - > height = m_object - > get_layer ( 0 ) - > height ;
}
for ( int layer_nr = 1 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
for ( Node * node : curr_layer_nodes ) {
2022-08-11 06:27:59 +00:00
if ( node - > support_roof_layers_below > 0 | | node - > support_floor_layers_above = = bot_intf_layers ) {
2022-07-15 15:37:19 +00:00
extremes . push_back ( layer_nr ) ;
break ;
}
}
if ( extremes . back ( ) = = layer_nr ) {
// contact layer use the same print_z and layer height with object layer
for ( Node * node : curr_layer_nodes ) {
node - > print_z = m_object - > get_layer ( layer_nr ) - > print_z ;
node - > height = m_object - > get_layer ( layer_nr ) - > height ;
}
}
}
// schedule new layer heights and print_z
for ( size_t idx_extreme = 0 ; idx_extreme < extremes . size ( ) ; idx_extreme + + ) {
int extr2_layer_nr = extremes [ idx_extreme ] ;
coordf_t extr2z = m_object - > get_layer ( extr2_layer_nr ) - > bottom_z ( ) ;
int extr1_layer_nr = idx_extreme = = 0 ? - 1 : extremes [ idx_extreme - 1 ] ;
coordf_t extr1z = idx_extreme = = 0 ? 0.f : m_object - > get_layer ( extr1_layer_nr ) - > print_z ;
coordf_t dist = extr2z - extr1z ;
// Insert intermediate layers.
size_t n_layers_extra = size_t ( ceil ( dist / m_slicing_params . max_suport_layer_height ) ) ;
if ( n_layers_extra < = 1 )
continue ;
coordf_t step = dist / coordf_t ( n_layers_extra ) ;
coordf_t print_z = extr1z + step ;
assert ( step > = layer_height - EPSILON ) ;
for ( int layer_nr = extr1_layer_nr + 1 ; layer_nr < extr2_layer_nr ; layer_nr + + ) {
std : : vector < Node * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
if ( curr_layer_nodes . empty ( ) ) continue ;
if ( std : : abs ( print_z - curr_layer_nodes [ 0 ] - > print_z ) < step / 2 + EPSILON ) {
for ( Node * node : curr_layer_nodes ) {
node - > print_z = print_z ;
node - > height = step ;
}
print_z + = step ;
}
else {
// can't clear curr_layer_nodes, or the model will have empty layers
for ( Node * node : curr_layer_nodes ) {
node - > print_z = 0.0 ;
node - > height = 0.0 ;
}
}
}
}
}
2022-11-30 03:51:25 +00:00
std : : vector < std : : pair < coordf_t , coordf_t > > TreeSupport : : plan_layer_heights ( std : : vector < std : : vector < Node * > > & contact_nodes )
{
const PrintObjectConfig & config = m_object - > config ( ) ;
const coordf_t max_layer_height = m_slicing_params . max_layer_height ;
const coordf_t layer_height = config . layer_height . value ;
coordf_t z_distance_top = m_slicing_params . gap_support_object ;
// BBS: add extra distance if thick bridge is enabled
// Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height
if ( ! m_slicing_params . soluble_interface & & m_object_config - > thick_bridges ) {
z_distance_top + = m_object - > layers ( ) [ 0 ] - > regions ( ) [ 0 ] - > region ( ) . bridging_height_avg ( m_object - > print ( ) - > config ( ) ) - layer_height ;
}
const size_t support_roof_layers = config . support_interface_top_layers . value ;
const int z_distance_top_layers = round_up_divide ( scale_ ( z_distance_top ) , scale_ ( layer_height ) ) + 1 ;
std : : vector < std : : pair < coordf_t , coordf_t > > layer_heights ( contact_nodes . size ( ) , std : : pair < coordf_t , coordf_t > ( 0.0 , 0.0 ) ) ;
std : : vector < int > bounds ;
2022-12-07 12:32:33 +00:00
if ( ! USE_PLAN_LAYER_HEIGHTS | | layer_height = = max_layer_height ) {
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
layer_heights [ layer_nr ] . first = m_object - > get_layer ( layer_nr ) - > print_z ;
layer_heights [ layer_nr ] . second = m_object - > get_layer ( layer_nr ) - > height ;
}
return layer_heights ;
}
2022-11-30 03:51:25 +00:00
bounds . push_back ( 0 ) ;
// Keep first layer still
layer_heights [ 0 ] . first = m_object - > get_layer ( 0 ) - > print_z ;
layer_heights [ 0 ] . second = m_object - > get_layer ( 0 ) - > height ;
// Collect top contact layers
for ( int layer_nr = 1 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + )
{
if ( ! contact_nodes [ layer_nr ] . empty ( ) )
for ( int i = 0 ; i < support_roof_layers + z_distance_top_layers + 1 ; i + + ) {
if ( layer_nr - i > 0 ) {
bounds . push_back ( layer_nr - i ) ;
layer_heights [ layer_nr - i ] . first = m_object - > get_layer ( layer_nr - i ) - > print_z ;
layer_heights [ layer_nr - i ] . second = m_object - > get_layer ( layer_nr - i ) - > height ;
}
else {
break ;
}
}
}
std : : set < int > s ( bounds . begin ( ) , bounds . end ( ) ) ;
bounds . assign ( s . begin ( ) , s . end ( ) ) ;
for ( size_t idx_extreme = 0 ; idx_extreme < bounds . size ( ) ; idx_extreme + + ) {
int extr2_layer_nr = bounds [ idx_extreme ] ;
coordf_t extr2z = m_object - > get_layer ( extr2_layer_nr ) - > bottom_z ( ) ;
int extr1_layer_nr = idx_extreme = = 0 ? - 1 : bounds [ idx_extreme - 1 ] ;
coordf_t extr1z = idx_extreme = = 0 ? 0.f : m_object - > get_layer ( extr1_layer_nr ) - > print_z ;
coordf_t dist = extr2z - extr1z ;
// Insert intermediate layers.
2022-11-30 03:51:25 +00:00
size_t n_layers_extra = size_t ( ceil ( dist / ( m_slicing_params . max_suport_layer_height + EPSILON ) ) ) ;
int actual_internel_layers = extr2_layer_nr - extr1_layer_nr - 1 ;
int extr_layers_left = extr2_layer_nr - extr1_layer_nr - n_layers_extra - 1 ;
2022-11-30 03:51:25 +00:00
if ( n_layers_extra < 1 )
continue ;
coordf_t step = dist / coordf_t ( n_layers_extra ) ;
coordf_t print_z = extr1z + step ;
assert ( step > = layer_height - EPSILON ) ;
for ( int layer_nr = extr1_layer_nr + 1 ; layer_nr < extr2_layer_nr ; layer_nr + + ) {
// if (curr_layer_nodes.empty()) continue;
2022-11-30 03:51:25 +00:00
if ( std : : abs ( print_z - m_object - > get_layer ( layer_nr ) - > print_z ) < step / 2 + EPSILON | | extr_layers_left < 1 ) {
2022-11-30 03:51:25 +00:00
layer_heights [ layer_nr ] . first = print_z ;
layer_heights [ layer_nr ] . second = step ;
print_z + = step ;
}
else {
// can't clear curr_layer_nodes, or the model will have empty layers
layer_heights [ layer_nr ] . first = 0.0 ;
layer_heights [ layer_nr ] . second = 0.0 ;
2022-11-30 03:51:25 +00:00
extr_layers_left - - ;
2022-11-30 03:51:25 +00:00
}
}
}
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
// export all print_z and layer height into .txt
std : : ofstream layer_heights_out ;
layer_heights_out . open ( " ./SVG/layer_heights_out.txt " ) ;
//layer_heights_out.open("layer_heights_out.txt");
if ( layer_heights_out . is_open ( ) ) {
for ( int i = 0 ; i < layer_heights . size ( ) ; i + + ) {
layer_heights_out < < layer_heights [ i ] . first < < " " < < layer_heights [ i ] . second < < std : : endl ;
}
layer_heights_out . close ( ) ;
}
// check bounds
if ( 1 )
{
std : : ofstream bounds_out ;
bounds_out . open ( " bounds.txt " ) ;
if ( bounds_out . is_open ( ) ) {
for ( int i = 0 ; i < bounds . size ( ) ; i + + ) {
bounds_out < < bounds [ i ] < < std : : endl ;
}
}
}
# endif
return layer_heights ;
}
2022-07-15 15:37:19 +00:00
void TreeSupport : : generate_contact_points ( std : : vector < std : : vector < TreeSupport : : Node * > > & contact_nodes )
{
const PrintObjectConfig & config = m_object - > config ( ) ;
const coordf_t point_spread = scale_ ( config . tree_support_branch_distance . value ) ;
//First generate grid points to cover the entire area of the print.
BoundingBox bounding_box = m_object - > bounding_box ( ) ;
const Point bounding_box_size = bounding_box . max - bounding_box . min ;
constexpr double rotate_angle = 22.0 / 180.0 * M_PI ;
constexpr double thresh_big_overhang = SQ ( scale_ ( 10 ) ) ;
const auto center = bounding_box_middle ( bounding_box ) ;
const auto sin_angle = std : : sin ( rotate_angle ) ;
const auto cos_angle = std : : cos ( rotate_angle ) ;
const auto rotated_dims = Point (
bounding_box_size ( 0 ) * cos_angle + bounding_box_size ( 1 ) * sin_angle ,
bounding_box_size ( 0 ) * sin_angle + bounding_box_size ( 1 ) * cos_angle ) / 2 ;
std : : vector < Point > grid_points ;
for ( auto x = - rotated_dims ( 0 ) ; x < rotated_dims ( 0 ) ; x + = point_spread ) {
for ( auto y = - rotated_dims ( 1 ) ; y < rotated_dims ( 1 ) ; y + = point_spread ) {
Point pt ( x , y ) ;
pt . rotate ( cos_angle , sin_angle ) ;
pt + = center ;
if ( bounding_box . contains ( pt ) ) {
grid_points . push_back ( pt ) ;
}
}
}
const coordf_t layer_height = config . layer_height . value ;
coordf_t z_distance_top = m_slicing_params . gap_support_object ;
// BBS: add extra distance if thick bridge is enabled
// Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height
2022-07-22 09:46:10 +00:00
if ( ! m_slicing_params . soluble_interface & & m_object_config - > thick_bridges ) {
2022-07-15 15:37:19 +00:00
z_distance_top + = m_object - > layers ( ) [ 0 ] - > regions ( ) [ 0 ] - > region ( ) . bridging_height_avg ( m_object - > print ( ) - > config ( ) ) - layer_height ;
}
2022-11-25 07:17:52 +00:00
const int z_distance_top_layers = round_up_divide ( scale_ ( z_distance_top ) , scale_ ( layer_height ) ) + 1 ; //Support must always be 1 layer below overhang.
2022-07-15 15:37:19 +00:00
const size_t support_roof_layers = config . support_interface_top_layers . value + 1 ; // BBS: add a normal support layer below interface
coordf_t thresh_angle = config . support_threshold_angle . value < EPSILON ? 30.f : config . support_threshold_angle . value ;
coordf_t half_overhang_distance = scale_ ( tan ( thresh_angle * M_PI / 180.0 ) * layer_height / 2 ) ;
// fix bug of generating support for very thin objects
if ( m_object - > layers ( ) . size ( ) < = z_distance_top_layers + 1 )
return ;
2022-07-22 09:46:10 +00:00
m_highest_overhang_layer = 0 ;
int nonempty_layers = 0 ;
std : : vector < Slic3r : : Vec3f > all_nodes ;
2022-11-25 07:17:52 +00:00
for ( size_t layer_nr = 1 ; layer_nr < m_object - > layers ( ) . size ( ) ; layer_nr + + )
2022-07-15 15:37:19 +00:00
{
if ( m_object - > print ( ) - > canceled ( ) )
break ;
2022-11-25 07:17:52 +00:00
auto ts_layer = m_object - > get_tree_support_layer ( layer_nr + m_raft_layers ) ;
2022-07-22 09:46:10 +00:00
const ExPolygons & overhang = ts_layer - > overhang_areas ;
auto & curr_nodes = contact_nodes [ layer_nr ] ;
2022-07-15 15:37:19 +00:00
if ( overhang . empty ( ) )
continue ;
m_highest_overhang_layer = std : : max ( m_highest_overhang_layer , layer_nr ) ;
auto print_z = m_object - > get_layer ( layer_nr ) - > print_z ;
auto height = m_object - > get_layer ( layer_nr ) - > height ;
for ( const ExPolygon & overhang_part : overhang )
{
BoundingBox overhang_bounds = get_extents ( overhang_part ) ;
2022-12-07 12:32:33 +00:00
if ( config . support_style . value = = smsTreeHybrid & & overhang_part . area ( ) > thresh_big_overhang ) {
2022-07-15 15:37:19 +00:00
Point candidate = overhang_bounds . center ( ) ;
if ( ! overhang_part . contains ( candidate ) )
move_inside_expoly ( overhang_part , candidate ) ;
2022-12-07 12:32:33 +00:00
Node * contact_node = new Node ( candidate , - z_distance_top_layers , ( layer_nr ) % 2 , support_roof_layers + z_distance_top_layers , true , Node : : NO_PARENT , print_z ,
height , z_distance_top ) ;
2022-07-15 15:37:19 +00:00
contact_node - > type = ePolygon ;
contact_node - > overhang = & overhang_part ;
2022-07-22 09:46:10 +00:00
curr_nodes . emplace_back ( contact_node ) ;
2022-07-15 15:37:19 +00:00
continue ;
}
overhang_bounds . inflated ( half_overhang_distance ) ;
bool added = false ; //Did we add a point this way?
for ( Point candidate : grid_points )
{
if ( overhang_bounds . contains ( candidate ) )
{
// BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported!
bool is_inside = is_inside_ex ( overhang_part , candidate ) ;
if ( ! is_inside ) {
constexpr coordf_t distance_inside = 0 ; // Move point towards the border of the polygon if it is closer than half the overhang distance: Catch points that
// fall between overhang areas on constant surfaces.
move_inside_expoly ( overhang_part , candidate , distance_inside , half_overhang_distance ) ;
is_inside = is_inside_ex ( overhang_part , candidate ) ;
}
if ( is_inside )
{
// collision radius has to be 0 or the supports are too few at curved slopes
//if (!is_inside_ex(m_ts_data->get_collision(0, layer_nr), candidate))
{
constexpr bool to_buildplate = true ;
2022-12-07 12:32:33 +00:00
Node * contact_node = new Node ( candidate , - z_distance_top_layers , ( layer_nr ) % 2 , support_roof_layers + z_distance_top_layers , to_buildplate ,
Node : : NO_PARENT , print_z , height , z_distance_top ) ;
2022-10-25 09:11:23 +00:00
contact_node - > overhang = & overhang_part ;
2022-07-22 09:46:10 +00:00
curr_nodes . emplace_back ( contact_node ) ;
2022-07-15 15:37:19 +00:00
added = true ;
}
}
}
}
if ( ! added ) //If we didn't add any points due to bad luck, we want to add one anyway such that loose parts are also supported.
{
auto bbox = overhang_part . contour . bounding_box ( ) ;
2022-07-22 09:46:10 +00:00
Points candidates ;
if ( ts_layer - > overhang_types [ & overhang_part ] = = TreeSupportLayer : : Detected )
candidates = { bbox . min , bounding_box_middle ( bbox ) , bbox . max } ;
else
candidates = { bounding_box_middle ( bbox ) } ;
2022-07-15 15:37:19 +00:00
for ( Point candidate : candidates ) {
if ( ! overhang_part . contains ( candidate ) )
move_inside_expoly ( overhang_part , candidate ) ;
constexpr bool to_buildplate = true ;
2022-12-07 12:32:33 +00:00
Node * contact_node = new Node ( candidate , - z_distance_top_layers , layer_nr % 2 , support_roof_layers + z_distance_top_layers , to_buildplate , Node : : NO_PARENT ,
print_z , height , z_distance_top ) ;
2022-10-25 09:11:23 +00:00
contact_node - > overhang = & overhang_part ;
2022-07-22 09:46:10 +00:00
curr_nodes . emplace_back ( contact_node ) ;
2022-07-15 15:37:19 +00:00
}
}
2022-07-22 09:46:10 +00:00
if ( ts_layer - > overhang_types [ & overhang_part ] = = TreeSupportLayer : : Detected ) {
// add points at corners
auto & points = overhang_part . contour . points ;
for ( int i = 0 ; i < points . size ( ) ; i + + ) {
auto pt = points [ i ] ;
auto v1 = ( pt - points [ ( i - 1 + points . size ( ) ) % points . size ( ) ] ) . normalized ( ) ;
auto v2 = ( pt - points [ ( i + 1 ) % points . size ( ) ] ) . normalized ( ) ;
if ( v1 . dot ( v2 ) > - 0.7 ) {
2022-12-07 12:32:33 +00:00
Node * contact_node = new Node ( pt , - z_distance_top_layers , layer_nr % 2 , support_roof_layers + z_distance_top_layers , true , Node : : NO_PARENT , print_z ,
height , z_distance_top ) ;
2022-10-25 09:11:23 +00:00
contact_node - > overhang = & overhang_part ;
2022-07-22 09:46:10 +00:00
curr_nodes . emplace_back ( contact_node ) ;
}
}
2022-12-07 12:32:33 +00:00
}
if ( ts_layer - > overhang_types [ & overhang_part ] = = TreeSupportLayer : : Enforced | | is_slim ) {
2022-07-22 09:46:10 +00:00
// remove close points in Enforcers
auto above_nodes = contact_nodes [ layer_nr - 1 ] ;
if ( ! curr_nodes . empty ( ) & & ! above_nodes . empty ( ) ) {
for ( auto it = curr_nodes . begin ( ) ; it ! = curr_nodes . end ( ) ; ) {
bool is_duplicate = false ;
Slic3r : : Vec3f curr_pt ( ( * it ) - > position ( 0 ) , ( * it ) - > position ( 1 ) , scale_ ( ( * it ) - > print_z ) ) ;
for ( auto & pt : all_nodes ) {
auto dif = curr_pt - pt ;
if ( dif . norm ( ) < scale_ ( 2 ) ) {
delete ( * it ) ;
it = curr_nodes . erase ( it ) ;
is_duplicate = true ;
break ;
}
}
if ( ! is_duplicate ) it + + ;
}
2022-07-15 15:37:19 +00:00
}
}
}
2022-07-22 09:46:10 +00:00
if ( ! curr_nodes . empty ( ) ) nonempty_layers + + ;
for ( auto node : curr_nodes ) { all_nodes . emplace_back ( node - > position ( 0 ) , node - > position ( 1 ) , scale_ ( node - > print_z ) ) ; }
2022-07-15 15:37:19 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2022-12-07 12:32:33 +00:00
draw_contours_and_nodes_to_svg ( std : : to_string ( print_z ) , overhang , m_ts_data - > m_layer_outlines_below [ layer_nr ] , { } ,
2022-07-15 15:37:19 +00:00
contact_nodes [ layer_nr ] , { } , " init_contact_points " , { " overhang " , " outlines " , " " } ) ;
# endif
}
2022-07-22 09:46:10 +00:00
int nNodes = all_nodes . size ( ) ;
avg_node_per_layer = nodes_angle = 0 ;
if ( nNodes > 0 ) {
avg_node_per_layer = nNodes / nonempty_layers ;
// get orientation of nodes by line fitting
// line: y=kx+b, where
// k=tan(nodes_angle)=(n\sum{xy}-\sum{x}\sum{y})/(n\sum{x^2}-\sum{x}^2)
float mx = 0 , my = 0 , mxy = 0 , mx2 = 0 ;
for ( auto & pt : all_nodes ) {
float x = unscale_ ( pt ( 0 ) ) ;
float y = unscale_ ( pt ( 1 ) ) ;
mx + = x ;
my + = y ;
mxy + = x * y ;
mx2 + = x * x ;
}
nodes_angle = atan2 ( nNodes * mxy - mx * my , nNodes * mx2 - SQ ( mx ) ) ;
BOOST_LOG_TRIVIAL ( info ) < < " avg_node_per_layer= " < < avg_node_per_layer < < " , nodes_angle= " < < nodes_angle ;
}
2022-11-30 03:51:25 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
std : : ofstream contact_nodes_out ;
contact_nodes_out . open ( " ./SVG/contact_nodes.txt " ) ;
if ( contact_nodes_out . is_open ( ) ) {
for ( int i = 0 ; i < contact_nodes . size ( ) ; i + + ) {
if ( ! contact_nodes [ i ] . empty ( ) )
contact_nodes_out < < i < < std : : endl ;
}
}
# endif // SUPPORT_TREE_DEBUG_TO_SVG
2022-07-15 15:37:19 +00:00
}
void TreeSupport : : insert_dropped_node ( std : : vector < Node * > & nodes_layer , Node * p_node )
{
std : : vector < Node * > : : iterator conflicting_node_it = std : : find ( nodes_layer . begin ( ) , nodes_layer . end ( ) , p_node ) ;
if ( conflicting_node_it = = nodes_layer . end ( ) ) //No conflict.
{
nodes_layer . emplace_back ( p_node ) ;
return ;
}
Node * conflicting_node = * conflicting_node_it ;
conflicting_node - > distance_to_top = std : : max ( conflicting_node - > distance_to_top , p_node - > distance_to_top ) ;
conflicting_node - > support_roof_layers_below = std : : max ( conflicting_node - > support_roof_layers_below , p_node - > support_roof_layers_below ) ;
}
TreeSupportData : : TreeSupportData ( const PrintObject & object , coordf_t xy_distance , coordf_t max_move , coordf_t radius_sample_resolution )
: m_xy_distance ( xy_distance ) , m_max_move ( max_move ) , m_radius_sample_resolution ( radius_sample_resolution )
{
for ( std : : size_t layer_nr = 0 ; layer_nr < object . layers ( ) . size ( ) ; + + layer_nr )
{
const Layer * layer = object . get_layer ( layer_nr ) ;
m_layer_outlines . push_back ( ExPolygons ( ) ) ;
ExPolygons & outline = m_layer_outlines . back ( ) ;
for ( const ExPolygon & poly : layer - > lslices ) {
poly . simplify ( scale_ ( m_radius_sample_resolution ) , & outline ) ;
}
if ( layer_nr = = 0 )
m_layer_outlines_below . push_back ( outline ) ;
else
m_layer_outlines_below . push_back ( union_ex ( m_layer_outlines_below . end ( ) [ - 1 ] , outline ) ) ;
}
}
const ExPolygons & TreeSupportData : : get_collision ( coordf_t radius , size_t layer_nr ) const
{
profiler . tic ( ) ;
radius = ceil_radius ( radius ) ;
RadiusLayerPair key { radius , layer_nr } ;
const auto it = m_collision_cache . find ( key ) ;
const ExPolygons & collision = it ! = m_collision_cache . end ( ) ? it - > second : calculate_collision ( key ) ;
profiler . stage_add ( STAGE_get_collision , true ) ;
return collision ;
}
const ExPolygons & TreeSupportData : : get_avoidance ( coordf_t radius , size_t layer_nr ) const
{
profiler . tic ( ) ;
radius = ceil_radius ( radius ) ;
RadiusLayerPair key { radius , layer_nr } ;
const auto it = m_avoidance_cache . find ( key ) ;
const ExPolygons & avoidance = it ! = m_avoidance_cache . end ( ) ? it - > second : calculate_avoidance ( key ) ;
profiler . stage_add ( STAGE_GET_AVOIDANCE , true ) ;
return avoidance ;
}
Polygons TreeSupportData : : get_contours ( size_t layer_nr ) const
{
Polygons contours ;
for ( const ExPolygon expoly : m_layer_outlines [ layer_nr ] ) {
contours . push_back ( expoly . contour ) ;
}
return contours ;
}
Polygons TreeSupportData : : get_contours_with_holes ( size_t layer_nr ) const
{
Polygons contours ;
for ( const ExPolygon expoly : m_layer_outlines [ layer_nr ] ) {
for ( int i = 0 ; i < expoly . num_contours ( ) ; i + + )
contours . push_back ( expoly . contour_or_hole ( i ) ) ;
}
return contours ;
}
coordf_t TreeSupportData : : ceil_radius ( coordf_t radius ) const
{
#if 0
size_t factor = ( size_t ) ( radius / m_radius_sample_resolution ) ;
coordf_t remains = radius - m_radius_sample_resolution * factor ;
if ( remains > EPSILON ) {
return radius + m_radius_sample_resolution - remains ;
}
else {
return radius ;
}
# else
coordf_t resolution = m_radius_sample_resolution ;
return ceil ( radius / resolution ) * resolution ;
# endif
}
const ExPolygons & TreeSupportData : : calculate_collision ( const RadiusLayerPair & key ) const
{
const auto & radius = key . first ;
const auto & layer_nr = key . second ;
assert ( layer_nr < m_layer_outlines . size ( ) ) ;
ExPolygons collision_areas = std : : move ( offset_ex ( m_layer_outlines [ layer_nr ] , scale_ ( radius ) ) ) ;
const auto ret = m_collision_cache . insert ( { key , std : : move ( collision_areas ) } ) ;
return ret . first - > second ;
}
const ExPolygons & TreeSupportData : : calculate_avoidance ( const RadiusLayerPair & key ) const
{
const auto & radius = key . first ;
2022-12-07 12:32:33 +00:00
const auto & layer_nr = key . second ;
std : : pair < tbb : : concurrent_unordered_map < RadiusLayerPair , ExPolygons , RadiusLayerPairHash > : : iterator , bool > ret ;
if ( is_slim ) {
if ( layer_nr = = 0 ) {
m_avoidance_cache [ key ] = get_collision ( radius , 0 ) ;
return m_avoidance_cache [ key ] ;
}
// Avoidance for a given layer depends on all layers beneath it so could have very deep recursion depths if
// called at high layer heights. We can limit the reqursion depth to N by checking if the layer N
// below the current one exists and if not, forcing the calculation of that layer. This may cause another recursion
// if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers
// below our current one.
constexpr auto max_recursion_depth = 100 ;
// Check if we would exceed the recursion limit by trying to process this layer
if ( layer_nr > = max_recursion_depth & & m_avoidance_cache . find ( { radius , layer_nr - max_recursion_depth } ) = = m_avoidance_cache . end ( ) ) {
// Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result.
get_avoidance ( radius , layer_nr - max_recursion_depth ) ;
}
ExPolygons avoidance_areas = std : : move ( offset_ex ( get_avoidance ( radius , layer_nr - 1 ) , scale_ ( - m_max_move ) ) ) ;
const ExPolygons & collision = get_collision ( radius , layer_nr ) ;
avoidance_areas . insert ( avoidance_areas . end ( ) , collision . begin ( ) , collision . end ( ) ) ;
avoidance_areas = std : : move ( union_ex ( avoidance_areas ) ) ;
ret = m_avoidance_cache . insert ( { key , std : : move ( avoidance_areas ) } ) ;
assert ( ret . second ) ;
} else {
ExPolygons avoidance_areas = std : : move ( offset_ex ( m_layer_outlines_below [ layer_nr ] , scale_ ( m_xy_distance + radius ) ) ) ;
ret = m_avoidance_cache . insert ( { key , std : : move ( avoidance_areas ) } ) ;
assert ( ret . second ) ;
2022-07-15 15:37:19 +00:00
}
return ret . first - > second ;
}
} //namespace Slic3r