2022-07-15 15:37:19 +00:00
# include <math.h>
2023-08-19 02:57:07 +00:00
# include <chrono>
2022-07-15 15:37:19 +00:00
# include "MinimumSpanningTree.hpp"
# include "TreeSupport.hpp"
# include "Print.hpp"
# include "Layer.hpp"
# include "Fill/FillBase.hpp"
2022-12-21 11:05:14 +00:00
# include "Fill/FillConcentric.hpp"
2022-07-15 15:37:19 +00:00
# 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>
2024-03-26 03:39:30 +00:00
# include <libnest2d/placers/nfpplacer.hpp>
2023-08-19 02:57:07 +00:00
# include "TreeModelVolumes.hpp"
# include "TreeSupport3D.hpp"
# include "SupportMaterial.hpp"
# include "Fill/FillBase.hpp"
# include "BuildVolume.hpp"
# include "ClipperUtils.hpp"
2022-07-15 15:37:19 +00:00
2024-01-06 13:27:46 +00:00
# include <tbb/concurrent_vector.h>
2024-03-15 03:53:06 +00:00
# include <tbb/concurrent_unordered_set.h>
2023-12-21 09:09:19 +00:00
# include <tbb/blocked_range.h>
# include <tbb/parallel_for.h>
2024-03-15 03:53:06 +00:00
# include <tbb/parallel_for_each.h>
2023-12-21 09:09:19 +00:00
2024-03-27 10:24:58 +00:00
# include <boost/log/trivial.hpp>
2022-07-15 15:37:19 +00:00
# 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())
2023-10-23 07:29:16 +00:00
# define USE_SUPPORT_3D 0
2022-07-15 15:37:19 +00:00
2023-10-23 07:29:16 +00:00
//#define SUPPORT_TREE_DEBUG_TO_SVG
2022-11-30 03:51:25 +00:00
2023-04-14 07:37:43 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2023-04-13 12:38:41 +00:00
# include "nlohmann/json.hpp"
# endif
2022-07-15 15:37:19 +00:00
namespace Slic3r
{
# define unscale_(val) ((val) * SCALING_FACTOR)
2023-08-19 02:57:07 +00:00
# define FIRST_LAYER_EXPANSION 1.2
2022-07-15 15:37:19 +00:00
2023-08-19 02:57:07 +00:00
extern void generate_tree_support_3D ( PrintObject & print_object , TreeSupport * tree_support , std : : function < void ( ) > throw_on_cancel ) ;
2022-07-15 15:37:19 +00:00
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 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 :
2024-01-06 13:27:46 +00:00
uint32_t stage_durations [ NUM_STAGES ] = { 0 } ;
2022-07-15 15:37:19 +00:00
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 ( ) ; }
2024-01-06 13:27:46 +00:00
uint32_t toc ( ) {
toc_time = boost : : posix_time : : microsec_clock : : local_time ( ) ;
return ( toc_time - tic_time ) . total_milliseconds ( ) ;
}
void stage_add ( TreeSupportStage stage )
2022-07-15 15:37:19 +00:00
{
if ( stage > NUM_STAGES )
return ;
2024-01-06 13:27:46 +00:00
stage_durations [ stage ] + = toc ( ) ;
2022-07-15 15:37:19 +00:00
}
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 void draw_contours_and_nodes_to_svg
(
2024-01-06 13:27:46 +00:00
std : : string fname ,
2022-07-15 15:37:19 +00:00
const ExPolygons & overhangs ,
const ExPolygons & overhangs_after_offset ,
const ExPolygons & outlines_below ,
2024-01-06 13:27:46 +00:00
const std : : vector < SupportNode * > & layer_nodes ,
const std : : vector < SupportNode * > & lower_layer_nodes ,
std : : vector < std : : string > legends = { " overhang " , " avoid " , " outlines " } ,
std : : vector < std : : string > colors = { " blue " , " red " , " yellow " }
2022-07-15 15:37:19 +00:00
)
{
BoundingBox bbox = get_extents ( overhangs ) ;
bbox . merge ( get_extents ( overhangs_after_offset ) ) ;
bbox . merge ( get_extents ( outlines_below ) ) ;
Points layer_pts ;
2024-01-06 13:27:46 +00:00
for ( SupportNode * node : layer_nodes ) {
2022-07-15 15:37:19 +00:00
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 ;
2024-01-06 13:27:46 +00:00
svg . open ( fname , 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
2023-08-19 02:57:07 +00:00
svg . draw_outline ( overhangs , colors [ 0 ] ) ;
svg . draw_outline ( overhangs_after_offset , colors [ 1 ] ) ;
2022-07-15 15:37:19 +00:00
svg . draw_outline ( outlines_below , colors [ 2 ] ) ;
// draw legend
2024-01-06 13:27:46 +00:00
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 0 ) ) , format ( " nPoints: %1%->%2% " , layer_nodes . size ( ) , lower_layer_nodes . size ( ) ) . c_str ( ) , " green " , 2 ) ;
2023-03-06 10:23:15 +00:00
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 2 ) ) , legends [ 0 ] . c_str ( ) , colors [ 0 ] . c_str ( ) , 2 ) ;
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 4 ) ) , legends [ 1 ] . c_str ( ) , colors [ 1 ] . c_str ( ) , 2 ) ;
svg . draw_text ( bbox . min + Point ( scale_ ( 0 ) , scale_ ( 6 ) ) , legends [ 2 ] . c_str ( ) , colors [ 2 ] . c_str ( ) , 2 ) ;
2022-07-15 15:37:19 +00:00
// draw layer nodes
svg . draw ( layer_pts , " green " , coord_t ( scale_ ( 0.1 ) ) ) ;
2024-01-06 13:27:46 +00:00
2022-07-15 15:37:19 +00:00
// lower layer points
layer_pts . clear ( ) ;
2024-01-06 13:27:46 +00:00
for ( SupportNode * node : lower_layer_nodes ) {
2022-07-15 15:37:19 +00:00
layer_pts . push_back ( node - > position ) ;
}
svg . draw ( layer_pts , " black " , coord_t ( scale_ ( 0.1 ) ) ) ;
// higher layer points
layer_pts . clear ( ) ;
2024-01-06 13:27:46 +00:00
for ( SupportNode * node : layer_nodes ) {
2022-07-15 15:37:19 +00:00
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
2024-01-06 13:27:46 +00:00
( const std : : string & fname ,
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 ) ;
}
2024-01-06 13:27:46 +00:00
SVG svg ( fname , 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
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.
2023-09-23 08:31:49 +00:00
// @return success(true) or not(false)
static bool move_inside_expoly ( const ExPolygon & polygon , Point & from , double distance = 0 , double max_move_distance = std : : numeric_limits < double > : : max ( ) )
2022-07-15 15:37:19 +00:00
{
//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 )
{
2023-09-23 08:31:49 +00:00
return false ;
2022-07-15 15:37:19 +00:00
}
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 ;
}
2023-09-23 08:31:49 +00:00
return true ;
2022-07-15 15:37:19 +00:00
}
else if ( bestDist2 < max_move_distance * max_move_distance )
{
from = ret ;
2023-09-23 08:31:49 +00:00
return true ;
2022-07-15 15:37:19 +00:00
}
2023-09-23 08:31:49 +00:00
return false ;
2022-07-15 15:37:19 +00:00
}
/*
* 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 ;
}
2023-09-23 08:31:49 +00:00
// use project_onto which is more accurate but more expensive
2022-07-15 15:37:19 +00:00
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 ) ) ) ;
2023-04-11 08:49:02 +00:00
Point pt = projection_onto ( polys_dilated , from ) ; // find_closest_ex(from, polys_dilated);
2022-07-15 15:37:19 +00:00
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 )
2024-01-06 13:27:46 +00:00
: m_object ( & object ) , m_slicing_params ( slicing_params ) , m_support_params ( object ) , m_object_config ( & object . config ( ) )
2022-07-15 15:37:19 +00:00
{
2023-08-19 02:57:07 +00:00
m_print_config = & m_object - > print ( ) - > config ( ) ;
2022-07-15 15:37:19 +00:00
m_raft_layers = slicing_params . base_raft_layers + slicing_params . interface_raft_layers ;
2023-03-18 07:53:13 +00:00
support_type = m_object_config - > support_type ;
support_style = m_object_config - > support_style ;
2024-04-29 07:57:16 +00:00
if ( support_style = = smsDefault ) {
2024-06-26 07:19:05 +00:00
// organic support doesn't work with variable layer heights (including adaptive layer height and height range modifier, see #4313)
if ( ! m_object - > has_variable_layer_heights ) {
2024-04-29 07:57:16 +00:00
BOOST_LOG_TRIVIAL ( warning ) < < " tree support default to organic support " ;
support_style = smsTreeOrganic ;
}
else {
2024-06-26 07:19:05 +00:00
BOOST_LOG_TRIVIAL ( warning ) < < " tree support default to hybrid tree due to adaptive layer height " ;
2024-04-29 07:57:16 +00:00
support_style = smsTreeHybrid ;
}
}
2022-07-15 15:37:19 +00:00
SupportMaterialPattern support_pattern = m_object_config - > support_base_pattern ;
2023-09-23 08:31:49 +00:00
if ( support_style = = smsTreeHybrid & & support_pattern = = smpDefault )
support_pattern = smpRectilinear ;
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 :
2023-09-23 08:31:49 +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 ) ;
2023-03-20 10:36:56 +00:00
if ( m_object_config - > support_interface_pattern = = smipGrid )
m_support_params . contact_fill_pattern = ipGrid ;
else if ( m_object_config - > support_interface_pattern = = smipRectilinearInterlaced )
m_support_params . contact_fill_pattern = ipRectilinear ;
else
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 ;
2023-06-25 05:38:29 +00:00
// Check if set to zero, use default if so.
if ( m_support_params . support_extrusion_width < = 0.0 ) {
const auto nozzle_diameter = object . print ( ) - > config ( ) . nozzle_diameter . get_at ( object . config ( ) . support_interface_filament - 1 ) ;
m_support_params . support_extrusion_width = Flow : : auto_extrusion_width ( FlowRole : : frSupportMaterial , ( float ) nozzle_diameter ) ;
}
2023-03-18 07:53:13 +00:00
is_slim = is_tree_slim ( support_type , support_style ) ;
2023-04-12 12:23:00 +00:00
is_strong = is_tree ( support_type ) & & support_style = = smsTreeStrong ;
2023-02-15 06:20:31 +00:00
MAX_BRANCH_RADIUS = 10.0 ;
2022-12-07 12:32:33 +00:00
tree_support_branch_diameter_angle = 5.0 ; //is_slim ? 10.0 : 5.0;
2022-12-23 07:19:57 +00:00
// by default tree support needs no infill, unless it's tree hybrid which contains normal nodes.
with_infill = support_pattern ! = smpNone & & support_pattern ! = smpDefault ;
2023-09-23 08:31:49 +00:00
m_machine_border . contour = get_bed_shape_with_excluded_area ( * m_print_config ) ;
2023-02-01 09:10:42 +00:00
Vec3d plate_offset = m_object - > print ( ) - > get_plate_origin ( ) ;
// align with the centered object in current plate (may not be the 1st plate, so need to add the plate offset)
m_machine_border . translate ( Point ( scale_ ( plate_offset ( 0 ) ) , scale_ ( plate_offset ( 1 ) ) ) - m_object - > instances ( ) . front ( ) . shift ) ;
2023-09-23 08:31:49 +00:00
m_support_params . independent_layer_height = m_print_config - > independent_support_layer_height ;
# if USE_TREESUPPRT3D
m_support_params . independent_layer_height = false ; // do not use independent layer height for 3D tree support
# endif
2022-12-15 05:03:53 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2024-01-06 13:27:46 +00:00
SVG svg ( debug_out_path ( " machine_boarder.svg " ) , m_object - > bounding_box ( ) ) ;
2022-12-15 05:03:53 +00:00
if ( svg . is_opened ( ) ) svg . draw ( m_machine_border , " yellow " ) ;
# endif
2022-07-15 15:37:19 +00:00
}
# define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
2024-02-01 09:39:37 +00:00
void TreeSupport : : detect_overhangs ( bool check_support_necessity /* = false*/ )
2022-07-15 15:37:19 +00:00
{
2024-01-26 08:19:59 +00:00
bool tree_support_enable = m_object_config - > enable_support . value & & is_tree ( m_object_config - > support_type . value ) ;
2024-03-26 03:39:30 +00:00
if ( ! tree_support_enable & & ! check_support_necessity ) {
BOOST_LOG_TRIVIAL ( info ) < < " Tree support is disabled. " ;
return ;
}
2024-01-26 08:19:59 +00:00
2022-12-14 13:12:36 +00:00
// Clear and create Tree Support Layers
2022-12-09 06:08:30 +00:00
m_object - > clear_support_layers ( ) ;
2022-07-22 09:46:10 +00:00
m_object - > clear_tree_support_preview_cache ( ) ;
2023-01-04 09:58:18 +00:00
2022-07-15 15:37:19 +00:00
const PrintObjectConfig & config = m_object - > config ( ) ;
2023-02-01 09:10:42 +00:00
SupportType stype = support_type ;
2023-01-04 09:58:18 +00:00
const coordf_t radius_sample_resolution = g_config_tree_support_collision_resolution ;
2022-07-15 15:37:19 +00:00
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 ;
2024-03-15 03:53:06 +00:00
bool config_remove_small_overhangs = config . support_remove_small_overhang . value ;
bool config_detect_sharp_tails = g_config_support_sharp_tails ;
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-12-26 08:59:30 +00:00
static const double sharp_tail_max_support_height = 16.f ;
2022-07-15 15:37:19 +00:00
// 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 ( ) ;
2023-04-06 12:48:58 +00:00
// +1 makes the threshold inclusive
double thresh_angle = config . support_threshold_angle . value > EPSILON ? config . support_threshold_angle . value + 1 : 30 ;
thresh_angle = std : : min ( thresh_angle , 89. ) ; // should be smaller than 90
const double threshold_rad = Geometry : : deg2rad ( thresh_angle ) ;
2022-07-15 15:37:19 +00:00
// for small overhang removal
struct OverhangCluster {
std : : map < int , const ExPolygon * > layer_overhangs ;
ExPolygons merged_poly ;
2023-03-14 09:04:42 +00:00
BoundingBox merged_bbox ;
2022-07-15 15:37:19 +00:00
int min_layer = 1e7 ;
int max_layer = 0 ;
coordf_t offset = 0 ;
2022-09-22 03:09:38 +00:00
bool is_cantilever = false ;
2023-03-27 08:45:12 +00:00
bool is_sharp_tail = false ;
bool is_small_overhang = 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 ) ;
2023-03-14 09:04:42 +00:00
merged_bbox . merge ( get_extents ( dilate1 ) ) ;
2022-07-15 15:37:19 +00:00
}
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 ) ;
2023-03-14 09:04:42 +00:00
BoundingBox bbox = get_extents ( dilate1 ) ;
if ( ! merged_bbox . overlap ( bbox ) )
return false ;
return overlaps ( { * overhang } , dilate1 ) ;
}
// it's basically the combination of push_back and intersects, but saves an offset_ex
bool push_back_if_intersects ( const ExPolygon & region , int layer_nr , coordf_t offset ) {
bool is_intersect = false ;
ExPolygons dilate1 ;
BoundingBox bbox ;
do {
if ( layer_nr < 1 ) break ;
auto it = layer_overhangs . find ( layer_nr - 1 ) ;
if ( it = = layer_overhangs . end ( ) ) break ;
const ExPolygon * overhang = it - > second ;
this - > offset = offset ;
dilate1 = offset_ex ( region , offset ) ;
if ( dilate1 . empty ( ) ) break ;
bbox = get_extents ( dilate1 ) ;
if ( ! merged_bbox . overlap ( bbox ) )
break ;
is_intersect = overlaps ( { * overhang } , dilate1 ) ;
} while ( 0 ) ;
if ( is_intersect ) {
layer_overhangs . emplace ( layer_nr , & region ) ;
merged_poly = union_ex ( merged_poly , dilate1 ) ;
min_layer = std : : min ( min_layer , layer_nr ) ;
max_layer = std : : max ( max_layer , layer_nr ) ;
merged_bbox . merge ( bbox ) ;
}
return is_intersect ;
2022-07-15 15:37:19 +00:00
}
} ;
std : : vector < OverhangCluster > overhangClusters ;
2023-04-03 12:49:25 +00:00
auto find_and_insert_cluster = [ ] ( auto & regionClusters , const ExPolygon & region , int layer_nr , coordf_t offset ) {
OverhangCluster * cluster = nullptr ;
for ( int i = 0 ; i < regionClusters . size ( ) ; i + + ) {
auto cluster_i = & regionClusters [ i ] ;
if ( cluster_i - > push_back_if_intersects ( region , layer_nr , offset ) ) {
cluster = cluster_i ;
2022-07-15 15:37:19 +00:00
break ;
}
}
2023-04-03 12:49:25 +00:00
if ( ! cluster ) {
cluster = & regionClusters . emplace_back ( & region , layer_nr ) ;
2022-07-15 15:37:19 +00:00
}
2023-04-03 12:49:25 +00:00
return cluster ;
2022-07-15 15:37:19 +00:00
} ;
2023-04-03 12:49:25 +00:00
if ( ! is_tree ( stype ) ) return ;
max_cantilever_dist = 0 ;
2023-09-23 08:31:49 +00:00
m_highest_overhang_layer = 0 ;
2023-03-14 09:04:42 +00:00
2024-03-07 08:14:46 +00:00
// calc the extrudable expolygons of each layer
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 ( m_object - > print ( ) - > canceled ( ) )
break ;
Layer * layer = m_object - > get_layer ( layer_nr ) ;
// Filter out areas whose diameter that is smaller than extrusion_width, but we don't want to lose any details.
layer - > lslices_extrudable = intersection_ex ( layer - > lslices , offset2_ex ( layer - > lslices , - extrusion_width_scaled / 2 , extrusion_width_scaled ) ) ;
2024-03-26 03:39:30 +00:00
layer - > loverhangs . clear ( ) ;
2024-03-07 08:14:46 +00:00
}
} ) ;
2024-03-15 03:53:06 +00:00
typedef std : : chrono : : high_resolution_clock clock_ ;
typedef std : : chrono : : duration < double , std : : ratio < 1 > > second_ ;
std : : chrono : : time_point < clock_ > t0 { clock_ : : now ( ) } ;
2023-03-14 09:04:42 +00:00
// main part of overhang detection can be parallel
2024-03-26 03:39:30 +00:00
tbb : : concurrent_vector < ExPolygons > overhangs_all_layers ( m_object - > layer_count ( ) ) ;
2023-03-14 09:04:42 +00:00
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 ( m_object - > print ( ) - > canceled ( ) )
break ;
if ( ! is_auto ( stype ) & & layer_nr > enforce_support_layers )
continue ;
Layer * layer = m_object - > get_layer ( layer_nr ) ;
if ( layer - > lower_layer = = nullptr ) {
2024-04-01 02:03:30 +00:00
for ( auto & slice : layer - > lslices_extrudable ) {
2023-03-14 09:04:42 +00:00
auto bbox_size = get_extents ( slice ) . size ( ) ;
if ( ! ( ( bbox_size . x ( ) > length_thresh_well_supported & & bbox_size . y ( ) > length_thresh_well_supported ) )
& & g_config_support_sharp_tails ) {
layer - > sharp_tails . push_back ( slice ) ;
2024-03-07 08:14:46 +00:00
layer - > sharp_tails_height . push_back ( layer - > height ) ;
2023-03-14 09:04:42 +00:00
}
2022-12-08 07:11:39 +00:00
}
2023-03-14 09:04:42 +00:00
continue ;
2022-07-15 15:37:19 +00:00
}
2023-03-14 09:04:42 +00:00
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 ) ;
2024-03-07 01:26:48 +00:00
//lower_layer_offset = std::min(lower_layer_offset, extrusion_width);
2023-03-14 09:04:42 +00:00
coordf_t support_offset_scaled = scale_ ( lower_layer_offset ) ;
2024-03-07 08:14:46 +00:00
ExPolygons & curr_polys = layer - > lslices_extrudable ;
ExPolygons & lower_polys = lower_layer - > lslices_extrudable ;
2022-07-15 15:37:19 +00:00
2023-03-14 09:04:42 +00:00
// normal overhang
ExPolygons lower_layer_offseted = offset_ex ( lower_polys , support_offset_scaled , SUPPORT_SURFACES_OFFSET_PARAMETERS ) ;
2024-03-26 03:39:30 +00:00
overhangs_all_layers [ layer_nr ] = std : : move ( diff_ex ( curr_polys , lower_layer_offseted ) ) ;
2022-12-14 13:12:36 +00:00
2024-03-15 03:53:06 +00:00
double duration { std : : chrono : : duration_cast < second_ > ( clock_ : : now ( ) - t0 ) . count ( ) } ;
2024-03-26 03:39:30 +00:00
if ( duration > 30 | | overhangs_all_layers [ layer_nr ] . size ( ) > 100 ) {
2024-03-15 03:53:06 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " detect_overhangs takes more than 30 secs, skip cantilever and sharp tails detection: layer_nr= " < < layer_nr < < " duration= " < < duration ;
config_detect_sharp_tails = false ;
config_remove_small_overhangs = false ;
continue ;
}
if ( is_auto ( stype ) & & config_detect_sharp_tails )
2023-03-14 09:04:42 +00:00
{
// BBS detect sharp tail
2024-03-07 08:14:46 +00:00
for ( const ExPolygon & expoly : curr_polys ) {
2023-03-14 09:04:42 +00:00
bool is_sharp_tail = false ;
2022-07-15 15:37:19 +00:00
// 1. nothing below
2023-09-14 06:11:12 +00:00
// this is a sharp tail region if it's floating and non-ignorable
2024-03-07 08:14:46 +00:00
if ( ! overlaps ( offset_ex ( expoly , 0.5 * extrusion_width_scaled ) , lower_polys ) ) {
is_sharp_tail = ! offset_ex ( expoly , - 0.1 * extrusion_width_scaled ) . empty ( ) ;
2022-07-15 15:37:19 +00:00
}
2023-03-14 09:04:42 +00:00
if ( is_sharp_tail ) {
2024-03-07 08:14:46 +00:00
layer - > sharp_tails . push_back ( expoly ) ;
2024-03-26 03:39:30 +00:00
layer - > sharp_tails_height . push_back ( 0 ) ;
2022-07-15 15:37:19 +00:00
2024-03-26 03:39:30 +00:00
has_sharp_tails = true ;
2023-05-11 09:45:33 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2024-03-26 03:39:30 +00:00
SVG : : export_expolygons ( debug_out_path ( " sharp_tail_orig_%.02f.svg " , layer - > print_z ) , { expoly } ) ;
2023-05-11 09:45:33 +00:00
# endif
2024-03-26 03:39:30 +00:00
}
2023-03-14 09:04:42 +00:00
}
}
2022-07-15 15:37:19 +00:00
2023-04-03 12:49:25 +00:00
2024-03-15 03:53:06 +00:00
// check cantilever
// lower_layer_offset may be very small, so we need to do max and then add 0.1
lower_layer_offseted = offset_ex ( lower_layer_offseted , scale_ ( std : : max ( extrusion_width - lower_layer_offset , 0. ) + 0.1 ) ) ;
2024-03-26 03:39:30 +00:00
for ( ExPolygon & poly : overhangs_all_layers [ layer_nr ] ) {
2024-03-15 03:53:06 +00:00
auto cluster_boundary_ex = intersection_ex ( poly , lower_layer_offseted ) ;
Polygons cluster_boundary = to_polygons ( cluster_boundary_ex ) ;
if ( cluster_boundary . empty ( ) ) continue ;
double dist_max = 0 ;
for ( auto & pt : poly . contour . points ) {
double dist_pt = std : : numeric_limits < double > : : max ( ) ;
for ( auto & ply : cluster_boundary ) {
double d = ply . distance_to ( pt ) ;
dist_pt = std : : min ( dist_pt , d ) ;
2023-04-03 12:49:25 +00:00
}
2024-03-15 03:53:06 +00:00
dist_max = std : : max ( dist_max , dist_pt ) ;
}
if ( dist_max > scale_ ( 3 ) ) { // is cantilever if the farmost point is larger than 3mm away from base
max_cantilever_dist = std : : max ( max_cantilever_dist , dist_max ) ;
layer - > cantilevers . emplace_back ( poly ) ;
BOOST_LOG_TRIVIAL ( debug ) < < " found a cantilever cluster. layer_nr= " < < layer_nr < < dist_max ;
has_cantilever = true ;
2023-04-03 12:49:25 +00:00
}
2022-07-15 15:37:19 +00:00
}
}
2023-03-14 09:04:42 +00:00
}
) ; // end tbb::parallel_for
2022-07-15 15:37:19 +00:00
2023-04-03 12:49:25 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " max_cantilever_dist= " < < max_cantilever_dist ;
2024-02-01 09:39:37 +00:00
if ( check_support_necessity )
return ;
2023-04-03 12:49:25 +00:00
2023-03-14 09:04:42 +00:00
// check if the sharp tails should be extended higher
2024-03-15 03:53:06 +00:00
if ( is_auto ( stype ) & & config_detect_sharp_tails ) {
2023-03-14 09:04:42 +00:00
for ( size_t layer_nr = 0 ; layer_nr < m_object - > layer_count ( ) ; layer_nr + + ) {
if ( m_object - > print ( ) - > canceled ( ) )
break ;
2022-07-15 15:37:19 +00:00
2023-03-14 09:04:42 +00:00
Layer * layer = m_object - > get_layer ( layer_nr ) ;
Layer * lower_layer = layer - > lower_layer ;
2023-05-11 09:45:33 +00:00
if ( ! lower_layer )
2023-03-14 09:04:42 +00:00
continue ;
2022-07-15 15:37:19 +00:00
2023-03-14 09:04:42 +00:00
// BBS detect sharp tail
const ExPolygons & lower_layer_sharptails = lower_layer - > sharp_tails ;
2023-05-11 09:45:33 +00:00
const auto & lower_layer_sharptails_height = lower_layer - > sharp_tails_height ;
2024-03-07 08:14:46 +00:00
for ( ExPolygon & expoly : layer - > lslices_extrudable ) {
2023-03-14 09:04:42 +00:00
bool is_sharp_tail = false ;
float accum_height = layer - > height ;
do {
// 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.
#if 0
// judge by area isn't reliable, failure cases include 45 degree rotated cube
float supported_area = area ( supported_by_lower ) ;
if ( supported_area > area_thresh_well_supported ) {
is_sharp_tail = false ;
break ;
}
# endif
BoundingBox bbox = get_extents ( supported_by_lower ) ;
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
2024-03-07 08:14:46 +00:00
for ( size_t i = 0 ; i < lower_layer_sharptails . size ( ) ; i + + ) {
if ( lower_layer_sharptails [ i ] . overlaps ( expoly ) ) {
accum_height + = lower_layer_sharptails_height [ i ] ;
2023-03-14 09:04:42 +00:00
break ;
2022-07-15 15:37:19 +00:00
}
}
2023-05-11 09:45:33 +00:00
if ( accum_height > sharp_tail_max_support_height ) {
2023-03-14 09:04:42 +00:00
is_sharp_tail = false ;
break ;
}
2022-07-15 15:37:19 +00:00
2023-03-14 09:04:42 +00:00
// 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.
ExPolygons new_overhang_expolys = diff_ex ( { expoly } , lower_layer_sharptails ) ;
if ( ( get_extents ( new_overhang_expolys ) . size ( ) - get_extents ( lower_layer_sharptails ) . size ( ) ) . both_comp ( Point ( scale_ ( 5 ) , scale_ ( 5 ) ) , " > " ) | | ! 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 ) {
layer - > sharp_tails . push_back ( expoly ) ;
2024-03-07 08:14:46 +00:00
layer - > sharp_tails_height . push_back ( accum_height ) ;
2023-03-14 09:04:42 +00:00
}
}
}
}
2024-03-26 03:39:30 +00:00
// group overhang clusters
for ( size_t layer_nr = 0 ; layer_nr < m_object - > layer_count ( ) ; layer_nr + + ) {
if ( m_object - > print ( ) - > canceled ( ) )
break ;
Layer * layer = m_object - > get_layer ( layer_nr ) ;
for ( auto & overhang : overhangs_all_layers [ layer_nr ] ) {
OverhangCluster * cluster = find_and_insert_cluster ( overhangClusters , overhang , layer_nr , extrusion_width_scaled ) ;
if ( overlaps ( { overhang } , layer - > cantilevers ) )
cluster - > is_cantilever = true ;
}
}
2022-07-15 15:37:19 +00:00
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 ) ;
2024-03-26 03:39:30 +00:00
2023-05-31 07:05:28 +00:00
if ( is_auto ( stype ) & & config_remove_small_overhangs ) {
2024-03-15 03:53:06 +00:00
// remove small overhangs
2022-07-15 15:37:19 +00:00
for ( auto & cluster : overhangClusters ) {
2023-04-03 12:49:25 +00:00
// 3. check whether the small overhang is sharp tail
cluster . 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 ( overlaps ( layer - > sharp_tails , cluster . merged_poly ) ) {
cluster . is_sharp_tail = true ;
break ;
}
}
2022-07-15 15:37:19 +00:00
2023-03-27 08:45:12 +00:00
if ( ! cluster . is_sharp_tail & & ! cluster . is_cantilever ) {
// 2. check overhang cluster size is smaller than 3.0 * fw_scaled
2023-04-27 02:27:38 +00:00
auto erode1 = offset_ex ( cluster . merged_poly , - 1 * extrusion_width_scaled ) ;
Point bbox_sz = get_extents ( erode1 ) . size ( ) ;
if ( bbox_sz . x ( ) < 2 * extrusion_width_scaled | | bbox_sz . y ( ) < 2 * extrusion_width_scaled ) {
cluster . is_small_overhang = true ;
}
2023-03-27 08:45:12 +00:00
}
2022-07-15 15:37:19 +00:00
2023-03-27 08:45:12 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
const Layer * layer1 = m_object - > get_layer ( cluster . min_layer ) ;
2024-01-06 13:27:46 +00:00
std : : string fname = debug_out_path ( " overhangCluster_%d-%d_%.2f-%.2f_tail=%d_cantilever=%d_small=%d.svg " ,
2023-05-11 09:45:33 +00:00
cluster . min_layer , cluster . max_layer , layer1 - > print_z , m_object - > get_layer ( cluster . max_layer ) - > print_z ,
2024-01-06 13:27:46 +00:00
cluster . is_sharp_tail , cluster . is_cantilever , cluster . is_small_overhang ) ;
SVG : : export_expolygons ( fname , {
{ layer1 - > lslices , { " min_layer_lslices " , " red " , 0.5 } } ,
{ m_object - > get_layer ( cluster . max_layer ) - > lslices , { " max_layer_lslices " , " yellow " , 0.5 } } ,
{ cluster . merged_poly , { " overhang " , " blue " , 0.5 } } ,
{ cluster . is_cantilever ? layer1 - > cantilevers : offset_ex ( cluster . merged_poly , - 1 * extrusion_width_scaled ) , { cluster . is_cantilever ? " cantilever " : " erode1 " , " green " , 0.5 } } } ) ;
2023-03-27 08:45:12 +00:00
# endif
2024-03-26 03:39:30 +00:00
}
}
2022-07-15 15:37:19 +00:00
2024-03-26 03:39:30 +00:00
for ( auto & cluster : overhangClusters ) {
if ( cluster . is_small_overhang ) continue ;
// collect overhangs that's not small overhangs
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 ) - > loverhangs . emplace_back ( * p_overhang ) ;
2022-07-15 15:37:19 +00:00
}
}
2024-03-26 03:39:30 +00:00
int layers_with_overhangs = 0 ;
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 ;
2023-01-04 09:58:18 +00:00
auto layer = m_object - > get_layer ( layer_nr ) ;
2023-03-28 07:41:19 +00:00
auto lower_layer = layer - > lower_layer ;
2022-11-24 03:45:17 +00:00
2024-03-26 03:39:30 +00:00
// add support for every 1mm height for sharp tails
ExPolygons sharp_tail_overhangs ;
if ( lower_layer = = nullptr )
sharp_tail_overhangs = layer - > sharp_tails ;
else {
ExPolygons lower_layer_expanded = offset_ex ( lower_layer - > lslices_extrudable , SCALED_RESOLUTION ) ;
for ( size_t i = 0 ; i < layer - > sharp_tails_height . size ( ) ; i + + ) {
ExPolygons areas = diff_clipped ( { layer - > sharp_tails [ i ] } , lower_layer_expanded ) ;
float accum_height = layer - > sharp_tails_height [ i ] ;
if ( ! areas . empty ( ) & & int ( accum_height * 10 ) % 5 = = 0 ) {
append ( sharp_tail_overhangs , areas ) ;
has_sharp_tails = true ;
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
SVG : : export_expolygons ( debug_out_path ( " sharp_tail_%.02f.svg " , layer - > print_z ) , areas ) ;
# endif
}
}
}
2022-07-15 15:37:19 +00:00
if ( layer_nr < blockers . size ( ) ) {
2023-05-06 06:52:49 +00:00
// Arthur: union_ is a must because after mirroring, the blocker polygons are in left-hand coordinates, ie clockwise,
// which are not valid polygons, and will be removed by offset_ex. union_ can make these polygons right.
2024-04-12 11:46:04 +00:00
ExPolygons blocker = offset_ex ( union_ ( blockers [ layer_nr ] ) , scale_ ( radius_sample_resolution ) ) ;
layer - > loverhangs = diff_ex ( layer - > loverhangs , blocker ) ;
layer - > cantilevers = diff_ex ( layer - > cantilevers , blocker ) ;
sharp_tail_overhangs = diff_ex ( sharp_tail_overhangs , blocker ) ;
}
if ( support_critical_regions_only & & is_auto ( stype ) ) {
layer - > loverhangs . clear ( ) ; // remove oridinary overhangs, only keep cantilevers and sharp tails (added later)
append ( layer - > loverhangs , layer - > cantilevers ) ;
2022-07-15 15:37:19 +00:00
}
2024-03-26 03:39:30 +00:00
if ( max_bridge_length > 0 & & layer - > loverhangs . size ( ) > 0 & & lower_layer ) {
2024-06-25 01:30:12 +00:00
// do not break bridge as the interface will be poor, see #4318
bool break_bridge = false ;
2024-03-26 03:39:30 +00:00
m_object - > remove_bridges_from_contacts ( lower_layer , layer , extrusion_width_scaled , & layer - > loverhangs , max_bridge_length , break_bridge ) ;
2023-03-28 07:41:19 +00:00
}
2024-03-26 03:39:30 +00:00
int nDetected = layer - > loverhangs . size ( ) ;
2023-07-20 02:33:29 +00:00
// enforcers now follow same logic as normal support. See STUDIO-3692
if ( layer_nr < enforcers . size ( ) & & lower_layer ) {
float no_interface_offset = std : : accumulate ( layer - > regions ( ) . begin ( ) , layer - > regions ( ) . end ( ) , FLT_MAX ,
[ ] ( float acc , const LayerRegion * layerm ) { return std : : min ( acc , float ( layerm - > flow ( frExternalPerimeter ) . scaled_width ( ) ) ) ; } ) ;
2024-04-01 02:03:30 +00:00
Polygons lower_layer_polygons = ( layer_nr = = 0 ) ? Polygons ( ) : to_polygons ( lower_layer - > lslices_extrudable ) ;
2022-07-15 15:37:19 +00:00
Polygons & enforcer = enforcers [ layer_nr ] ;
2023-07-20 02:33:29 +00:00
if ( ! enforcer . empty ( ) ) {
2024-04-01 02:03:30 +00:00
ExPolygons enforcer_polygons = diff_ex ( intersection_ex ( layer - > lslices_extrudable , enforcer ) ,
2023-07-20 02:33:29 +00:00
// Inflate just a tiny bit to avoid intersection of the overhang areas with the object.
expand ( lower_layer_polygons , 0.05f * no_interface_offset , SUPPORT_SURFACES_OFFSET_PARAMETERS ) ) ;
2024-03-26 03:39:30 +00:00
append ( layer - > loverhangs , enforcer_polygons ) ;
2022-07-15 15:37:19 +00:00
}
}
2024-03-26 03:39:30 +00:00
int nEnforced = layer - > loverhangs . size ( ) ;
2024-01-06 13:27:46 +00:00
2024-03-26 03:39:30 +00:00
// add sharp tail overhangs
append ( layer - > loverhangs , sharp_tail_overhangs ) ;
2024-01-06 13:27:46 +00:00
// fill overhang_types
2024-03-26 03:39:30 +00:00
for ( size_t i = 0 ; i < layer - > loverhangs . size ( ) ; i + + )
overhang_types . emplace ( & layer - > loverhangs [ i ] , i < nDetected ? OverhangType : : Detected :
i < nEnforced ? OverhangType : : Enforced : OverhangType : : SharpTail ) ;
2022-11-30 04:15:40 +00:00
2024-03-26 03:39:30 +00:00
if ( ! layer - > loverhangs . empty ( ) ) {
layers_with_overhangs + + ;
2023-09-23 08:31:49 +00:00
m_highest_overhang_layer = std : : max ( m_highest_overhang_layer , size_t ( layer_nr ) ) ;
}
2023-01-04 09:58:18 +00:00
if ( ! layer - > cantilevers . empty ( ) ) has_cantilever = true ;
2022-07-15 15:37:19 +00:00
}
2024-03-26 03:39:30 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Tree support overhang detection done. " < < layers_with_overhangs < < " layers with overhangs. " ;
2022-07-15 15:37:19 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2024-03-26 03:39:30 +00:00
for ( const Layer * layer : m_object - > layers ( ) ) {
if ( layer - > loverhangs . empty ( ) & & ( blockers . size ( ) < = layer - > id ( ) | | blockers [ layer - > id ( ) ] . empty ( ) ) )
2022-07-15 15:37:19 +00:00
continue ;
2024-03-26 03:39:30 +00:00
SVG : : export_expolygons ( debug_out_path ( " overhang_areas_%d_%.2f.svg " , layer - > id ( ) , layer - > print_z ) , {
2024-04-01 02:03:30 +00:00
{ m_object - > get_layer ( layer - > id ( ) ) - > lslices_extrudable , { " lslices_extrudable " , " yellow " , 0.5 } } ,
2024-03-26 03:39:30 +00:00
{ layer - > loverhangs , { " overhang " , " red " , 0.5 } }
2024-01-06 13:27:46 +00:00
} ) ;
2023-08-07 03:27:09 +00:00
if ( enforcers . size ( ) > layer - > id ( ) ) {
SVG svg ( format ( " SVG/enforcer_%s.svg " , layer - > print_z ) , m_object - > bounding_box ( ) ) ;
if ( svg . is_opened ( ) ) {
2024-04-01 02:03:30 +00:00
svg . draw_outline ( m_object - > get_layer ( layer - > id ( ) ) - > lslices_extrudable , " yellow " ) ;
2023-08-07 03:27:09 +00:00
svg . draw ( enforcers [ layer - > id ( ) ] , " red " ) ;
}
}
if ( blockers . size ( ) > layer - > id ( ) ) {
SVG svg ( format ( " SVG/blocker_%s.svg " , layer - > print_z ) , m_object - > bounding_box ( ) ) ;
if ( svg . is_opened ( ) ) {
2024-04-01 02:03:30 +00:00
svg . draw_outline ( m_object - > get_layer ( layer - > id ( ) ) - > lslices_extrudable , " yellow " ) ;
2023-08-07 03:27:09 +00:00
svg . draw ( blockers [ layer - > id ( ) ] , " red " ) ;
2022-07-15 15:37:19 +00:00
}
}
}
# endif
}
2024-01-26 08:19:59 +00:00
// create support layers for raft and tree support
// if support is disabled, only raft layers are created
2024-02-01 09:39:37 +00:00
void TreeSupport : : create_tree_support_layers ( )
2022-07-15 15:37:19 +00:00
{
2023-11-16 13:05:26 +00:00
int layer_id = 0 ;
2024-05-28 10:07:44 +00:00
if ( m_raft_layers > 0 ) { //create raft layers
2023-09-06 07:27:19 +00:00
coordf_t raft_print_z = 0.f ;
coordf_t raft_slice_z = 0.f ;
2023-11-16 13:05:26 +00:00
{
// Do not add the raft contact layer, 1st layer should use first_print_layer_height
coordf_t height = m_slicing_params . first_print_layer_height ;
raft_print_z + = height ;
raft_slice_z = raft_print_z - height / 2 ;
m_object - > add_tree_support_layer ( layer_id + + , height , raft_print_z , raft_slice_z ) ;
}
2023-09-06 07:27:19 +00:00
// Insert the base layers.
2023-11-16 13:05:26 +00:00
for ( size_t i = 1 ; i < m_slicing_params . base_raft_layers ; i + + ) {
coordf_t height = m_slicing_params . base_raft_layer_height ;
2023-09-06 07:27:19 +00:00
raft_print_z + = height ;
raft_slice_z = raft_print_z - height / 2 ;
2023-11-16 13:05:26 +00:00
m_object - > add_tree_support_layer ( layer_id + + , height , raft_print_z , raft_slice_z ) ;
2023-09-06 07:27:19 +00:00
}
// Insert the interface layers.
2023-11-16 13:05:26 +00:00
for ( size_t i = 0 ; i < m_slicing_params . interface_raft_layers ; i + + ) {
2023-09-06 07:27:19 +00:00
coordf_t height = m_slicing_params . interface_raft_layer_height ;
raft_print_z + = height ;
raft_slice_z = raft_print_z - height / 2 ;
2023-11-16 13:05:26 +00:00
m_object - > add_tree_support_layer ( layer_id + + , height , raft_print_z , raft_slice_z ) ;
2023-09-06 07:27:19 +00:00
}
2022-07-15 15:37:19 +00:00
}
for ( Layer * layer : m_object - > layers ( ) ) {
2023-11-16 13:05:26 +00:00
SupportLayer * ts_layer = m_object - > add_tree_support_layer ( layer_id + + , layer - > height , layer - > print_z , layer - > slice_z ) ;
2022-07-15 15:37:19 +00:00
if ( ts_layer - > id ( ) > m_raft_layers ) {
2022-12-09 06:08:30 +00:00
SupportLayer * lower_layer = m_object - > get_support_layer ( ts_layer - > id ( ) - 1 ) ;
2023-11-16 13:05:26 +00:00
if ( lower_layer ) {
lower_layer - > upper_layer = ts_layer ;
ts_layer - > lower_layer = lower_layer ;
}
2022-07-15 15:37:19 +00:00
}
}
}
static inline BoundingBox fill_expolygon_generate_paths (
ExtrusionEntitiesPtr & dst ,
2023-11-16 13:05:26 +00:00
ExPolygon & expolygon ,
2022-07-15 15:37:19 +00:00
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 ,
2023-11-16 13:05:26 +00:00
ExPolygons & expolygons ,
2022-07-15 15:37:19 +00:00
Fill * filler ,
const FillParams & fill_params ,
ExtrusionRole role ,
const Flow & flow )
{
std : : vector < BoundingBox > fill_boxes ;
for ( ExPolygon & expoly : expolygons ) {
2023-11-17 03:38:55 +00:00
auto box = fill_expolygon_generate_paths ( dst , expoly , filler , fill_params , role , flow ) ;
2022-07-15 15:37:19 +00:00
fill_boxes . emplace_back ( box ) ;
}
return fill_boxes ;
}
2022-12-21 11:05:14 +00:00
static void _make_loops ( ExtrusionEntitiesPtr & loops_entities , ExPolygons & support_area , ExtrusionRole role , size_t wall_count , const Flow & flow )
2022-07-15 15:37:19 +00:00
{
2022-12-21 11:05:14 +00:00
Polygons loops ;
2022-07-15 15:37:19 +00:00
std : : map < ExPolygon * , int > depth_per_expoly ;
std : : list < ExPolygon > expoly_list ;
2022-12-21 11:05:14 +00:00
for ( ExPolygon & expoly : support_area ) {
2022-07-15 15:37:19 +00:00
expoly_list . emplace_back ( std : : move ( expoly ) ) ;
depth_per_expoly . insert ( { & expoly_list . back ( ) , 0 } ) ;
}
2022-12-21 11:05:14 +00:00
if ( expoly_list . empty ( ) ) return ;
2022-07-15 15:37:19 +00:00
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 ) {
2022-12-21 11:05:14 +00:00
//ExPolygons expolys_new = offset_ex(expoly_list.front(), -float(flow.scaled_spacing()), jtSquare);
2022-07-15 15:37:19 +00:00
// shrink and then dilate to prevent overlapping and overflow
2022-12-21 11:05:14 +00:00
ExPolygons expolys_new = offset2_ex ( { expoly_list . front ( ) } , - 1.4 * float ( flow . scaled_spacing ( ) ) , .4 * float ( flow . scaled_spacing ( ) ) ) ;
2022-07-15 15:37:19 +00:00
for ( ExPolygon & expoly : expolys_new ) {
auto new_iter = expoly_list . insert ( expoly_list . begin ( ) , expoly ) ;
2022-12-21 11:05:14 +00:00
depth_per_expoly . insert ( { & * new_iter , depth_iter - > second + 1 } ) ;
2022-07-15 15:37:19 +00:00
}
}
depth_per_expoly . erase ( depth_iter ) ;
expoly_list . erase ( first_iter ) ;
}
2023-10-23 07:29:16 +00:00
extrusion_entities_append_loops ( loops_entities , std : : move ( loops ) , role , float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
}
2022-12-21 11:05:14 +00:00
static void make_perimeter_and_inner_brim ( ExtrusionEntitiesPtr & dst , const ExPolygon & support_area , size_t wall_count , const Flow & flow , ExtrusionRole role )
{
Polygons loops ;
ExPolygons support_area_new = offset_ex ( support_area , - 0.5f * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
_make_loops ( dst , support_area_new , role , wall_count , flow ) ;
2022-07-15 15:37:19 +00:00
}
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 ;
2023-12-20 04:09:27 +00:00
ExPolygons to_infill = offset_ex ( support_area , - float ( wall_count ) * float ( flow . scaled_spacing ( ) ) , jtSquare ) ;
2023-11-17 03:38:55 +00:00
std : : vector < BoundingBox > fill_boxes = fill_expolygons_generate_paths ( dst , to_infill , filler_support , fill_params , role , flow ) ;
2022-07-15 15:37:19 +00:00
// allow wall_count to be zero, which means only draw infill
if ( wall_count = = 0 ) {
for ( auto fill_bbox : fill_boxes )
{
2023-03-28 07:41:19 +00:00
// extend bounding box on x-axis
if ( cos ( filler_support - > angle ) > = sin ( filler_support - > angle ) ) {
2022-07-15 15:37:19 +00:00
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 + + ;
}
}
2022-12-21 11:05:14 +00:00
{ // draw loops
ExtrusionEntitiesPtr loops_entities ;
_make_loops ( loops_entities , support_area_new , role , wall_count , flow ) ;
2022-07-15 15:37:19 +00:00
2022-11-24 06:37:19 +00:00
if ( infill_first )
2022-12-21 11:05:14 +00:00
dst . insert ( dst . end ( ) , loops_entities . begin ( ) , loops_entities . end ( ) ) ;
else { // loops first
2022-11-24 06:37:19 +00:00
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 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 ;
2023-09-23 08:31:49 +00:00
coordf_t nozzle_diameter = m_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 ;
// 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 ) ;
2022-12-09 06:08:30 +00:00
if ( m_object - > support_layers ( ) . empty ( ) )
2022-07-15 15:37:19 +00:00
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 ) ;
}
}
2022-12-09 06:08:30 +00:00
if ( m_object - > support_layer_count ( ) > m_raft_layers ) {
const SupportLayer * ts_layer = m_object - > get_support_layer ( m_raft_layers ) ;
2022-07-15 15:37:19 +00:00
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. ) ) ) ;
for ( size_t layer_nr = 0 ; layer_nr < m_slicing_params . base_raft_layers ; layer_nr + + ) {
2022-12-09 06:08:30 +00:00
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_nr ) ;
2024-01-26 08:19:59 +00:00
coordf_t expand_offset = ( layer_nr = = 0 ? m_object_config - > raft_first_layer_expansion . value : 0. ) ;
auto raft_areas1 = offset_ex ( raft_areas , scale_ ( expand_offset ) ) ;
2022-07-15 15:37:19 +00:00
2023-09-23 08:31:49 +00:00
Flow support_flow = Flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
2024-03-06 01:50:15 +00:00
Fill * filler_raft = Fill : : new_from_type ( ipRectilinear ) ;
filler_raft - > angle = layer_nr = = 0 ? PI / 2 : 0 ;
filler_raft - > spacing = support_flow . spacing ( ) ;
2022-07-15 15:37:19 +00:00
FillParams fill_params ;
2024-03-06 01:50:15 +00:00
coordf_t raft_density = std : : min ( 1. , support_flow . spacing ( ) / ( object_config . support_base_pattern_spacing . value + support_flow . spacing ( ) ) ) ;
fill_params . density = layer_nr = = 0 ? object_config . raft_first_layer_density * 0.01 : raft_density ;
2022-07-15 15:37:19 +00:00
fill_params . dont_adjust = true ;
2024-01-26 08:19:59 +00:00
// wall of first layer raft
if ( layer_nr = = 0 ) {
Flow flow = Flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
extrusion_entities_append_loops ( ts_layer - > support_fills . entities , to_polygons ( raft_areas1 ) , erSupportMaterial ,
float ( flow . mm3_per_mm ( ) ) , float ( flow . width ( ) ) , float ( flow . height ( ) ) ) ;
raft_areas1 = offset_ex ( raft_areas1 , - flow . scaled_spacing ( ) / 2. ) ;
}
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , raft_areas1 ,
2024-03-06 01:50:15 +00:00
filler_raft , fill_params , erSupportMaterial , support_flow ) ;
2022-07-15 15:37:19 +00:00
}
2023-11-16 13:05:26 +00:00
// subtract the non-raft support bases, otherwise we'll get support base on top of raft interfaces which is not stable
ExPolygons first_non_raft_base ;
2024-01-26 08:19:59 +00:00
SupportLayer * first_non_raft_layer = m_object - > get_support_layer ( m_raft_layers ) ;
if ( first_non_raft_layer ) {
for ( auto & area_group : first_non_raft_layer - > area_groups ) {
if ( area_group . type = = SupportLayer : : BaseType )
first_non_raft_base . emplace_back ( * area_group . area ) ;
}
2023-11-16 13:05:26 +00:00
}
ExPolygons raft_base_areas = intersection_ex ( raft_areas , first_non_raft_base ) ;
ExPolygons raft_interface_areas = diff_ex ( raft_areas , raft_base_areas ) ;
2022-07-15 15:37:19 +00:00
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 + + )
{
2022-12-09 06:08:30 +00:00
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_nr ) ;
2022-07-15 15:37:19 +00:00
Flow support_flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
Fill * filler_interface = Fill : : new_from_type ( ipRectilinear ) ;
2024-03-06 01:50:15 +00:00
filler_interface - > angle = PI / 2 ; // interface should be perpendicular to base
filler_interface - > spacing = support_flow . spacing ( ) ;
2022-07-15 15:37:19 +00:00
FillParams fill_params ;
fill_params . density = interface_density ;
fill_params . dont_adjust = true ;
2023-11-16 13:05:26 +00:00
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , raft_interface_areas ,
2022-07-15 15:37:19 +00:00
filler_interface , fill_params , erSupportMaterialInterface , support_flow ) ;
2023-11-16 13:05:26 +00:00
fill_params . density = object_config . raft_first_layer_density * 0.01 ;
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , raft_base_areas ,
filler_interface , fill_params , erSupportMaterial , support_flow ) ;
2022-07-15 15:37:19 +00:00
}
2024-01-26 08:19:59 +00:00
if ( m_object - > support_layer_count ( ) < = m_raft_layers )
return ;
2022-07-15 15:37:19 +00:00
BoundingBox bbox_object ( Point ( - scale_ ( 1. ) , - scale_ ( 1.0 ) ) , Point ( scale_ ( 1. ) , scale_ ( 1. ) ) ) ;
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 ) ) ;
filler_interface - > set_bounding_box ( bbox_object ) ;
filler_Roof1stLayer - > set_bounding_box ( bbox_object ) ;
2023-03-18 07:53:13 +00:00
filler_interface - > angle = Geometry : : deg2rad ( object_config . support_angle . value + 90. ) ;
2022-08-11 06:27:59 +00:00
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 (
2022-12-09 06:08:30 +00:00
tbb : : blocked_range < size_t > ( m_raft_layers , m_object - > support_layer_count ( ) ) ,
2022-07-15 15:37:19 +00:00
[ & ] ( 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 ;
2024-03-15 03:53:06 +00:00
//m_object->print()->set_status(70, (boost::format(_u8L("Support: generate toolpath at layer %d")) % layer_id).str());
2022-07-15 15:37:19 +00:00
2022-12-09 06:08:30 +00:00
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_id ) ;
2022-07-15 15:37:19 +00:00
Flow support_flow ( support_extrusion_width , ts_layer - > height , nozzle_diameter ) ;
2023-08-04 14:29:56 +00:00
m_support_material_interface_flow = support_material_interface_flow ( m_object , ts_layer - > height ) ; // update flow using real support layer height
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-09 06:08:30 +00:00
if ( area_group . type ! = SupportLayer : : 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 ;
2022-12-21 11:05:14 +00:00
make_perimeter_and_inner_brim ( ts_layer - > support_fills . entities , poly , wall_count , flow ,
2022-12-09 06:08:30 +00:00
area_group . type = = SupportLayer : : RoofType ? erSupportMaterialInterface : erSupportMaterial ) ;
2022-07-15 15:37:19 +00:00
polys = std : : move ( offset_ex ( poly , - flow . scaled_spacing ( ) ) ) ;
2022-12-09 06:08:30 +00:00
} else if ( area_group . type = = SupportLayer : : 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-09 06:08:30 +00:00
if ( area_group . type = = SupportLayer : : 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-09 06:08:30 +00:00
} else if ( area_group . type = = SupportLayer : : 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 ( ) ;
2023-11-17 03:38:55 +00:00
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , polys ,
2022-07-15 15:37:19 +00:00
filler_interface . get ( ) , fill_params , erSupportMaterialInterface , m_support_material_interface_flow ) ;
2022-12-09 06:08:30 +00:00
} else if ( area_group . type = = SupportLayer : : 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 ( ) ;
2023-03-20 10:36:56 +00:00
if ( m_object_config - > support_interface_pattern = = smipGrid ) {
filler_interface - > angle = Geometry : : deg2rad ( object_config . support_angle . value ) ;
fill_params . dont_sort = true ;
}
if ( m_object_config - > support_interface_pattern = = smipRectilinearInterlaced )
2023-12-20 04:09:27 +00:00
filler_interface - > layer_id = area_group . interface_id ;
2023-11-17 03:38:55 +00:00
fill_expolygons_generate_paths ( ts_layer - > support_fills . entities , polys , filler_interface . get ( ) , fill_params , erSupportMaterialInterface ,
2022-12-01 13:15:13 +00:00
m_support_material_interface_flow ) ;
2022-07-15 15:37:19 +00:00
}
else {
// base_areas
2022-11-30 03:51:25 +00:00
Flow flow = ( layer_id = = 0 & & m_raft_layers = = 0 ) ? m_object - > print ( ) - > brim_flow ( ) : support_flow ;
2023-03-28 07:41:19 +00:00
bool need_infill = with_infill ;
if ( m_object_config - > support_base_pattern = = smpDefault )
need_infill & = area_group . need_infill ;
2023-09-23 08:31:49 +00:00
std : : shared_ptr < Fill > filler_support = std : : shared_ptr < Fill > ( Fill : : new_from_type ( layer_id = = 0 ? ipConcentric : m_support_params . base_fill_pattern ) ) ;
filler_support - > set_bounding_box ( bbox_object ) ;
filler_support - > spacing = support_flow . spacing ( ) ;
filler_support - > angle = Geometry : : deg2rad ( object_config . support_angle . value ) ;
2023-10-23 07:29:16 +00:00
Polygons loops = to_polygons ( poly ) ;
if ( layer_id = = 0 ) {
float density = float ( m_object_config - > raft_first_layer_density . value * 0.01 ) ;
fill_expolygons_with_sheath_generate_paths ( ts_layer - > support_fills . entities , loops , filler_support . get ( ) , density , erSupportMaterial , flow , true , false ) ;
2022-07-15 15:37:19 +00:00
}
2023-01-12 13:54:43 +00:00
else {
2023-10-23 07:29:16 +00:00
if ( need_infill & & m_support_params . base_fill_pattern ! = ipLightning ) {
// allow infill-only mode if support is thick enough (so min_wall_count is 0);
// otherwise must draw 1 wall
2023-11-16 13:05:26 +00:00
// Don't need extra walls if we have infill. Extra walls may overlap with the infills.
2023-10-23 07:29:16 +00:00
size_t min_wall_count = offset ( poly , - scale_ ( support_spacing * 1.5 ) ) . empty ( ) ? 1 : 0 ;
2023-11-16 13:05:26 +00:00
make_perimeter_and_infill ( ts_layer - > support_fills . entities , * m_object - > print ( ) , poly , std : : max ( min_wall_count , wall_count ) , flow ,
2023-10-23 07:29:16 +00:00
erSupportMaterial , filler_support . get ( ) , support_density ) ;
2023-09-23 08:31:49 +00:00
}
2023-11-16 13:05:26 +00:00
else {
size_t walls = wall_count ;
if ( area_group . need_extra_wall & & walls < 2 ) walls + = 1 ;
2023-09-23 08:31:49 +00:00
for ( size_t i = 1 ; i < walls ; i + + ) {
2023-10-23 07:29:16 +00:00
Polygons contour_new = offset ( poly . contour , - ( i - 0.5f ) * flow . scaled_spacing ( ) , jtSquare ) ;
2023-09-23 08:31:49 +00:00
loops . insert ( loops . end ( ) , contour_new . begin ( ) , contour_new . end ( ) ) ;
}
fill_expolygons_with_sheath_generate_paths ( ts_layer - > support_fills . entities , loops , nullptr , 0 , erSupportMaterial , flow , true , false ) ;
}
2023-01-12 13:54:43 +00:00
}
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
2024-01-06 13:27:46 +00:00
std : : string name = debug_out_path ( " trees_polyline_%.2f.svg " , ts_layer - > print_z ) ;
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
}
}
) ;
}
2024-01-06 13:27:46 +00:00
void deleteDirectoryContents ( const std : : filesystem : : path & dir )
2023-08-19 02:57:07 +00:00
{
2024-01-06 13:27:46 +00:00
for ( const auto & entry : std : : filesystem : : directory_iterator ( dir ) )
std : : filesystem : : remove_all ( entry . path ( ) ) ;
2023-08-19 02:57:07 +00:00
}
2023-01-04 09:58:18 +00:00
void TreeSupport : : generate ( )
2022-07-15 15:37:19 +00:00
{
2023-08-19 02:57:07 +00:00
auto t_start = std : : chrono : : high_resolution_clock : : now ( ) ;
if ( support_style = = smsTreeOrganic ) {
generate_tree_support_3D ( * m_object , this , this - > throw_on_cancel ) ;
return ;
}
2022-07-15 15:37:19 +00:00
profiler . stage_start ( STAGE_total ) ;
// Generate overhang areas
profiler . stage_start ( STAGE_DETECT_OVERHANGS ) ;
2024-03-26 03:39:30 +00:00
m_object - > print ( ) - > set_status ( 55 , _u8L ( " Generating support " ) ) ;
2023-01-04 09:58:18 +00:00
detect_overhangs ( ) ;
2022-07-15 15:37:19 +00:00
profiler . stage_finish ( STAGE_DETECT_OVERHANGS ) ;
2024-03-26 03:39:30 +00:00
create_tree_support_layers ( ) ;
2023-01-04 09:58:18 +00:00
m_ts_data = m_object - > alloc_tree_support_preview_cache ( ) ;
m_ts_data - > is_slim = is_slim ;
2022-07-15 15:37:19 +00:00
// Generate contact points of tree support
2024-01-06 13:27:46 +00:00
std : : vector < std : : vector < SupportNode * > > contact_nodes ( m_object - > layers ( ) . size ( ) ) ;
2023-09-23 08:31:49 +00:00
2024-03-26 03:39:30 +00:00
# if USE_SUPPORT_3D
2023-10-23 07:29:16 +00:00
std : : vector < TreeSupport3D : : SupportElements > move_bounds ( m_highest_overhang_layer + 1 ) ;
profiler . stage_start ( STAGE_GENERATE_CONTACT_NODES ) ;
2024-03-15 03:53:06 +00:00
//m_object->print()->set_status(56, _u8L("Support: precalculate avoidance"));
2023-09-23 08:31:49 +00:00
Points bedpts = m_machine_border . contour . points ;
BuildVolume build_volume { Pointfs { unscaled ( bedpts [ 0 ] ) , unscaled ( bedpts [ 1 ] ) , unscaled ( bedpts [ 2 ] ) , unscaled ( bedpts [ 3 ] ) } , m_print_config - > printable_height } ;
TreeSupport3D : : TreeSupportSettings tree_support_3d_config { TreeSupport3D : : TreeSupportMeshGroupSettings { * m_object } } ;
2023-10-23 07:29:16 +00:00
m_model_volumes = std : : make_unique < TreeSupport3D : : TreeModelVolumes > ( * m_object , build_volume , tree_support_3d_config . maximum_move_distance , tree_support_3d_config . maximum_move_distance_slow , 1 ) ;
2023-09-23 08:31:49 +00:00
// ### Precalculate avoidances, collision etc.
if ( m_highest_overhang_layer < = tree_support_3d_config . z_distance_top_layers )
return ;
size_t highest_node_layer = m_highest_overhang_layer - tree_support_3d_config . z_distance_top_layers + 1 ;
m_model_volumes - > precalculate ( highest_node_layer , throw_on_cancel ) ;
auto t_precalc = std : : chrono : : high_resolution_clock : : now ( ) ;
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
// ### Place tips of the support tree
SupportGeneratorLayersPtr bottom_contacts ( m_highest_overhang_layer + 1 , nullptr ) ;
SupportGeneratorLayersPtr top_contacts ( m_highest_overhang_layer + 1 , nullptr ) ;
SupportGeneratorLayersPtr intermediate_layers ( m_highest_overhang_layer + 1 , nullptr ) ;
SupportGeneratorLayerStorage layer_storage ;
std : : vector < Polygons > overhangs ;
2024-03-15 03:53:06 +00:00
//m_object->print()->set_status(57, _u8L("Support: generate contact points"));
2023-09-23 08:31:49 +00:00
if ( support_style ! = smsTreeHybrid ) {
overhangs . resize ( m_object - > support_layer_count ( ) ) ;
for ( size_t i = 0 ; i < overhangs . size ( ) ; i + + ) {
2024-03-26 03:39:30 +00:00
overhangs [ i ] = to_polygons ( m_object - > get_layer ( i ) - > loverhangs ) ;
2023-09-23 08:31:49 +00:00
}
//m_object->clear_support_layers();
TreeSupport3D : : generate_initial_areas ( * m_object , * m_model_volumes . get ( ) , tree_support_3d_config , overhangs , move_bounds , top_contacts , layer_storage , throw_on_cancel ) ;
}
2023-10-23 07:29:16 +00:00
# endif
2024-03-26 03:39:30 +00:00
generate_contact_points ( contact_nodes ) ;
2022-07-15 15:37:19 +00:00
profiler . stage_finish ( STAGE_GENERATE_CONTACT_NODES ) ;
2023-10-23 07:29:16 +00:00
2022-07-15 15:37:19 +00:00
//Drop nodes to lower layers.
profiler . stage_start ( STAGE_DROP_DOWN_NODES ) ;
2024-03-26 03:39:30 +00:00
m_object - > print ( ) - > set_status ( 60 , _u8L ( " Generating support " ) ) ;
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 ) ;
2023-09-23 08:31:49 +00:00
smooth_nodes ( contact_nodes ) ; // , tree_support_3d_config);
2022-07-15 15:37:19 +00:00
//Generate support areas.
profiler . stage_start ( STAGE_DRAW_CIRCLES ) ;
2024-03-26 03:39:30 +00:00
m_object - > print ( ) - > set_status ( 65 , _u8L ( " Generating support " ) ) ;
2022-07-15 15:37:19 +00:00
draw_circles ( contact_nodes ) ;
profiler . stage_finish ( STAGE_DRAW_CIRCLES ) ;
2024-01-06 13:27:46 +00:00
2023-09-23 08:31:49 +00:00
2022-07-15 15:37:19 +00:00
profiler . stage_start ( STAGE_GENERATE_TOOLPATHS ) ;
2024-03-26 03:39:30 +00:00
m_object - > print ( ) - > set_status ( 70 , _u8L ( " Generating support " ) ) ;
2022-07-15 15:37:19 +00:00
generate_toolpaths ( ) ;
profiler . stage_finish ( STAGE_GENERATE_TOOLPATHS ) ;
profiler . stage_finish ( STAGE_total ) ;
2023-03-28 07:41:19 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " tree support time " < < profiler . report ( ) ;
2022-07-15 15:37:19 +00:00
}
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 ;
}
2023-09-23 08:31:49 +00:00
coordf_t TreeSupport : : calc_branch_radius ( coordf_t base_radius , coordf_t mm_to_top , double diameter_angle_scale_factor , bool use_min_distance )
2022-12-07 12:32:33 +00:00
{
double radius ;
2022-12-07 12:32:33 +00:00
coordf_t tip_height = base_radius ; // this is a 45 degree tip
if ( mm_to_top > tip_height )
2022-07-15 15:37:19 +00:00
{
2022-12-07 12:32:33 +00:00
radius = base_radius + ( mm_to_top - tip_height ) * 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 ; // this is a 45 degree tip
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 ) ;
2022-12-07 12:32:33 +00:00
// if have interface layers, radius should be larger
if ( m_object_config - > support_interface_top_layers . value > 0 )
radius = std : : max ( radius , base_radius ) ;
2023-09-23 08:31:49 +00:00
# if USE_SUPPORT_3D
2023-10-23 07:29:16 +00:00
if ( m_model_volumes )
2023-09-23 08:31:49 +00:00
radius = unscale_ ( m_model_volumes - > ceilRadius ( scale_ ( radius ) , use_min_distance ) ) ;
# endif
2022-07-15 15:37:19 +00:00
return radius ;
}
2024-03-15 03:53:06 +00:00
coordf_t TreeSupport : : get_radius ( const SupportNode * node , coordf_t base_radius )
{
if ( node - > radius = = 0 )
node - > radius = calc_branch_radius ( base_radius , node - > dist_mm_to_top , node - > diameter_angle_scale_factor ) ;
return node - > radius ;
}
2023-09-23 08:31:49 +00:00
ExPolygons TreeSupport : : get_avoidance ( coordf_t radius , size_t obj_layer_nr )
{
# if USE_SUPPORT_3D
if ( m_model_volumes ) {
bool on_build_plate = m_object_config - > support_on_build_plate_only . value ;
2023-10-23 07:29:16 +00:00
const Polygons & avoid_polys = m_model_volumes - > getAvoidance ( radius , obj_layer_nr , TreeSupport3D : : TreeModelVolumes : : AvoidanceType : : FastSafe , on_build_plate , true ) ;
2023-09-23 08:31:49 +00:00
ExPolygons expolys ;
for ( auto & poly : avoid_polys )
expolys . emplace_back ( std : : move ( poly ) ) ;
return expolys ;
}
return ExPolygons ( ) ;
# else
return m_ts_data - > get_avoidance ( radius , obj_layer_nr ) ;
# endif
}
ExPolygons TreeSupport : : get_collision ( coordf_t radius , size_t layer_nr )
{
# if USE_SUPPORT_3D
if ( m_model_volumes ) {
bool on_build_plate = m_object_config - > support_on_build_plate_only . value ;
const Polygons & collision_polys = m_model_volumes - > getCollision ( radius , layer_nr , true ) ;
ExPolygons expolys ;
for ( auto & poly : collision_polys )
expolys . emplace_back ( std : : move ( poly ) ) ;
return expolys ;
}
# else
return m_ts_data - > get_collision ( radius , layer_nr ) ;
# endif
return ExPolygons ( ) ;
}
Polygons TreeSupport : : get_collision_polys ( coordf_t radius , size_t layer_nr )
{
# if USE_SUPPORT_3D
if ( m_model_volumes ) {
bool on_build_plate = m_object_config - > support_on_build_plate_only . value ;
const Polygons & collision_polys = m_model_volumes - > getCollision ( radius , layer_nr , true ) ;
return collision_polys ;
}
# else
ExPolygons expolys = m_ts_data - > get_collision ( radius , layer_nr ) ;
Polygons polys ;
for ( auto & expoly : expolys )
2023-10-23 07:29:16 +00:00
for ( int i = 0 ; i < expoly . num_contours ( ) ; i + + )
polys . emplace_back ( std : : move ( expoly . contour_or_hole ( i ) ) ) ;
2023-09-23 08:31:49 +00:00
return polys ;
# endif
return Polygons ( ) ;
}
2024-03-15 02:11:54 +00:00
ExPolygons avoid_object_remove_extra_small_parts ( const ExPolygon & expoly , const ExPolygons & avoid_region ) {
2022-09-23 12:55:29 +00:00
ExPolygons expolys_out ;
2024-03-15 02:11:54 +00:00
if ( expoly . empty ( ) ) return expolys_out ;
auto clipped_avoid_region = ClipperUtils : : clip_clipper_polygons_with_subject_bbox ( avoid_region , get_extents ( expoly ) ) ;
auto expolys_avoid = diff_ex ( expoly , clipped_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 ;
2022-09-23 12:55:29 +00:00
}
}
2024-03-15 02:11:54 +00:00
if ( idx_max_area > = 0 ) expolys_out . emplace_back ( std : : move ( expolys_avoid [ idx_max_area ] ) ) ;
2022-09-23 12:55:29 +00:00
return expolys_out ;
}
2023-06-30 10:11:31 +00:00
Polygons TreeSupport : : get_trim_support_regions (
const PrintObject & object ,
SupportLayer * support_layer_ptr ,
const coordf_t gap_extra_above ,
const coordf_t gap_extra_below ,
const coordf_t gap_xy )
{
static const double sharp_tail_xy_gap = 0.2f ;
static const double no_overlap_xy_gap = 0.2f ;
double gap_xy_scaled = scale_ ( gap_xy ) ;
SupportLayer & support_layer = * support_layer_ptr ;
auto m_print_config = object . print ( ) - > config ( ) ;
size_t idx_object_layer_overlapping = size_t ( - 1 ) ;
auto is_layers_overlap = [ ] ( const SupportLayer & support_layer , const Layer & object_layer , coordf_t bridging_height = 0.f ) - > bool {
if ( std : : abs ( support_layer . print_z - object_layer . print_z ) < EPSILON )
return true ;
coordf_t object_lh = bridging_height > EPSILON ? bridging_height : object_layer . height ;
if ( support_layer . print_z < object_layer . print_z & & support_layer . print_z > object_layer . print_z - object_lh )
return true ;
if ( support_layer . print_z > object_layer . print_z & & support_layer . bottom_z ( ) < object_layer . print_z - EPSILON )
return true ;
return false ;
} ;
// Find the overlapping object layers including the extra above / below gap.
coordf_t z_threshold = support_layer . bottom_z ( ) - gap_extra_below + EPSILON ;
idx_object_layer_overlapping = Layer : : idx_higher_or_equal (
object . layers ( ) . begin ( ) , object . layers ( ) . end ( ) , idx_object_layer_overlapping ,
[ z_threshold ] ( const Layer * layer ) { return layer - > print_z > = z_threshold ; } ) ;
// Collect all the object layers intersecting with this layer.
Polygons polygons_trimming ;
size_t i = idx_object_layer_overlapping ;
for ( ; i < object . layers ( ) . size ( ) ; + + i ) {
const Layer & object_layer = * object . layers ( ) [ i ] ;
if ( object_layer . bottom_z ( ) > support_layer . print_z + gap_extra_above - EPSILON )
break ;
bool is_overlap = is_layers_overlap ( support_layer , object_layer ) ;
for ( const ExPolygon & expoly : object_layer . lslices ) {
// BBS
bool is_sharptail = ! intersection_ex ( { expoly } , object_layer . sharp_tails ) . empty ( ) ;
coordf_t trimming_offset = is_sharptail ? scale_ ( sharp_tail_xy_gap ) :
is_overlap ? gap_xy_scaled :
scale_ ( no_overlap_xy_gap ) ;
polygons_append ( polygons_trimming , offset ( { expoly } , trimming_offset , SUPPORT_SURFACES_OFFSET_PARAMETERS ) ) ;
}
}
if ( ! m_slicing_params . soluble_interface & & m_object_config - > thick_bridges ) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for ( ; i < object . layers ( ) . size ( ) ; + + i ) {
const Layer & object_layer = * object . layers ( ) [ i ] ;
bool some_region_overlaps = false ;
for ( LayerRegion * region : object_layer . regions ( ) ) {
coordf_t bridging_height = region - > region ( ) . bridging_height_avg ( m_print_config ) ;
if ( object_layer . print_z - bridging_height > support_layer . print_z + gap_extra_above - EPSILON )
break ;
some_region_overlaps = true ;
bool is_overlap = is_layers_overlap ( support_layer , object_layer , bridging_height ) ;
coordf_t trimming_offset = is_overlap ? gap_xy_scaled : scale_ ( no_overlap_xy_gap ) ;
polygons_append ( polygons_trimming ,
offset ( region - > fill_surfaces . filter_by_type ( stBottomBridge ) , trimming_offset , SUPPORT_SURFACES_OFFSET_PARAMETERS ) ) ;
}
if ( ! some_region_overlaps )
break ;
}
}
return polygons_trimming ;
}
2024-01-06 13:27:46 +00:00
void TreeSupport : : draw_circles ( const std : : vector < std : : vector < SupportNode * > > & contact_nodes )
2022-07-15 15:37:19 +00:00
{
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-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 ) ;
2023-09-23 08:31:49 +00:00
bool on_buildplate_only = m_object_config - > support_on_build_plate_only . value ;
2022-07-15 15:37:19 +00:00
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 ;
2023-08-19 02:57:07 +00:00
const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 25 ; // The number of vertices in each circle.
2022-07-22 09:46:10 +00:00
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 ;
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
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 ;
2023-06-25 05:38:29 +00:00
const coordf_t line_width_scaled = scale_ ( support_extrusion_width ) ;
2023-05-16 06:55:50 +00:00
const float tree_brim_width = config . tree_support_brim_width . value ;
2022-07-06 04:16:37 +00:00
2024-01-26 08:19:59 +00:00
if ( m_object - > support_layer_count ( ) < = m_raft_layers )
return ;
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
2023-10-24 12:59:56 +00:00
tbb : : parallel_for ( tbb : : blocked_range < size_t > ( 0 , m_object - > layer_count ( ) ) ,
2022-07-15 15:37:19 +00:00
[ & ] ( 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
2024-01-06 13:27:46 +00:00
const std : : vector < SupportNode * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
2022-12-09 06:08:30 +00:00
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_nr + m_raft_layers ) ;
2022-07-15 15:37:19 +00:00
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
2024-05-23 14:01:01 +00:00
ts_layer - > print_z = m_ts_data - > layer_heights [ layer_nr ] . print_z ;
ts_layer - > height = m_ts_data - > layer_heights [ layer_nr ] . 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-21 11:05:14 +00:00
coordf_t max_layers_above_base = 0 ;
coordf_t max_layers_above_roof = 0 ;
coordf_t max_layers_above_roof1 = 0 ;
2023-12-20 04:09:27 +00:00
int interface_id = 0 ;
2023-03-28 07:41:19 +00:00
bool has_circle_node = false ;
2023-09-14 09:20:58 +00:00
bool need_extra_wall = false ;
2024-01-06 13:27:46 +00:00
ExPolygons collision_sharp_tails ;
ExPolygons collision_base ;
auto get_collision = [ & ] ( bool sharp_tail ) - > ExPolygons & {
if ( sharp_tail ) {
if ( collision_sharp_tails . empty ( ) )
collision_sharp_tails = m_ts_data - > get_collision ( m_object_config - > support_top_z_distance . value , layer_nr ) ;
return collision_sharp_tails ;
}
else {
if ( collision_base . empty ( ) )
2024-03-06 00:44:32 +00:00
collision_base = m_ts_data - > get_collision ( ( layer_nr = = 0 ) ? config . support_object_first_layer_gap : m_ts_data - > m_xy_distance , layer_nr ) ;
2024-01-06 13:27:46 +00:00
return collision_base ;
}
} ;
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 ( ) ) ;
2024-03-26 03:39:30 +00:00
ExPolygons area_poly ; // the polygon node area which will be printed as normal support
2024-01-06 13:27:46 +00:00
for ( const SupportNode * p_node : contact_nodes [ layer_nr ] )
2022-07-15 15:37:19 +00:00
{
if ( print - > canceled ( ) )
break ;
2024-01-06 13:27:46 +00:00
const SupportNode & 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
2024-01-06 13:27:46 +00:00
if ( node . type = = ePolygon | | ( node . distance_to_top < 0 & & ! node . is_sharp_tail ) ) {
2023-09-23 08:31:49 +00:00
if ( node . overhang . contour . size ( ) > 100 | | node . overhang . holes . size ( ) > 1 )
area . emplace_back ( node . overhang ) ;
2022-12-01 13:15:13 +00:00
else {
2023-09-23 08:31:49 +00:00
area = offset_ex ( { node . overhang } , scale_ ( m_ts_data - > m_xy_distance ) ) ;
2022-12-01 13:15:13 +00:00
}
2024-03-14 09:57:32 +00:00
area = diff_clipped ( area , get_collision ( node . is_sharp_tail & & node . distance_to_top < = 0 ) ) ;
2024-03-26 03:39:30 +00:00
if ( node . type = = ePolygon )
area_poly = area ;
2022-07-15 15:37:19 +00:00
}
else {
2023-08-04 14:29:56 +00:00
Polygon circle ( branch_circle ) ;
2024-01-06 13:27:46 +00:00
double scale = node . radius / branch_radius ;
2023-08-04 14:29:56 +00:00
double moveX = node . movement . x ( ) / ( scale * branch_radius_scaled ) ;
double moveY = node . movement . y ( ) / ( scale * branch_radius_scaled ) ;
//BOOST_LOG_TRIVIAL(debug) << format("scale,moveX,moveY: %.3f,%.3f,%.3f", scale, moveX, moveY);
2022-12-07 12:32:33 +00:00
2023-08-04 14:29:56 +00:00
if ( ! SQUARE_SUPPORT & & std : : abs ( moveX ) > 0.001 & & std : : abs ( moveY ) > 0.001 ) { // draw ellipse along movement direction
2022-12-07 12:32:33 +00:00
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 ) ,
} ;
2023-08-04 14:29:56 +00:00
int i = 0 ;
2022-12-07 12:32:33 +00:00
for ( auto vertex : branch_circle . points ) {
vertex = Point ( matrix [ 0 ] * vertex . x ( ) + matrix [ 1 ] * vertex . y ( ) , matrix [ 2 ] * vertex . x ( ) + matrix [ 3 ] * vertex . y ( ) ) ;
2023-08-04 14:29:56 +00:00
circle . points [ i + + ] = node . position + vertex ;
2022-12-07 12:32:33 +00:00
}
} else {
2023-08-04 14:29:56 +00:00
for ( int i = 0 ; i < circle . points . size ( ) ; i + + ) {
circle . points [ i ] = circle . points [ i ] * scale + node . position ;
2022-12-07 12:32:33 +00:00
}
2022-07-15 15:37:19 +00:00
}
if ( layer_nr = = 0 & & m_raft_layers = = 0 ) {
2023-10-31 02:33:42 +00:00
double brim_width = tree_brim_width > 0 ? tree_brim_width : std : : max ( MIN_BRANCH_RADIUS_FIRST_LAYER , std : : min ( node . radius + node . dist_mm_to_top / ( scale * branch_radius ) * 0.5 , MAX_BRANCH_RADIUS_FIRST_LAYER ) - node . radius ) ;
2023-09-23 08:31:49 +00:00
auto tmp = offset ( circle , scale_ ( brim_width ) ) ;
if ( ! tmp . empty ( ) )
circle = tmp [ 0 ] ;
2022-07-15 15:37:19 +00:00
}
2022-12-07 12:32:33 +00:00
// merge overhang to get a smoother interface surface
2023-05-29 08:06:15 +00:00
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
2024-01-06 13:27:46 +00:00
if ( top_interface_layers > 0 & & node . support_roof_layers_below > 0 & & ! on_buildplate_only & & ! node . is_sharp_tail ) {
2022-12-07 12:32:33 +00:00
ExPolygons overhang_expanded ;
2023-09-23 08:31:49 +00:00
if ( node . overhang . contour . size ( ) > 100 | | node . overhang . holes . size ( ) > 1 )
overhang_expanded . emplace_back ( node . overhang ) ;
2022-12-07 12:32:33 +00:00
else {
// 对于有缺陷的模型, overhang膨胀以后可能是空的!
2023-09-23 08:31:49 +00:00
overhang_expanded = offset_ex ( { node . overhang } , scale_ ( m_ts_data - > m_xy_distance ) ) ;
2022-12-07 12:32:33 +00:00
}
append ( area , overhang_expanded ) ;
}
2024-04-01 02:03:30 +00:00
area = avoid_object_remove_extra_small_parts ( ExPolygon ( circle ) , get_collision ( node . is_sharp_tail & & node . distance_to_top < = 0 ) ) ;
//area = diff_clipped({ ExPolygon(circle) }, get_collision(node.is_sharp_tail && node.distance_to_top <= 0));
if ( ! area . empty ( ) )
has_circle_node = true ;
2023-09-23 08:31:49 +00:00
if ( node . need_extra_wall )
2023-09-14 09:20:58 +00:00
need_extra_wall = true ;
2022-07-15 15:37:19 +00:00
}
2023-10-31 02:33:42 +00:00
if ( layer_nr > 0 & & node . distance_to_top < 0 )
2022-12-07 12:32:33 +00:00
append ( roof_gap_areas , area ) ;
2024-01-06 13:27:46 +00:00
else if ( layer_nr > 0 & & node . support_roof_layers_below = = 1 & & node . is_sharp_tail = = false )
2022-07-15 15:37:19 +00:00
{
2022-12-07 12:32:33 +00:00
append ( roof_1st_layer , area ) ;
2022-12-21 11:05:14 +00:00
max_layers_above_roof1 = std : : max ( max_layers_above_roof1 , node . dist_mm_to_top ) ;
2022-07-15 15:37:19 +00:00
}
2024-01-06 13:27:46 +00:00
else if ( layer_nr > 0 & & node . support_roof_layers_below > 0 & & node . is_sharp_tail = = false )
2022-07-15 15:37:19 +00:00
{
2022-12-07 12:32:33 +00:00
append ( roof_areas , area ) ;
2022-12-21 11:05:14 +00:00
max_layers_above_roof = std : : max ( max_layers_above_roof , node . dist_mm_to_top ) ;
2023-12-20 04:09:27 +00:00
interface_id = node . obj_layer_nr % top_interface_layers ;
2022-07-15 15:37:19 +00:00
}
else
{
2024-03-14 09:57:32 +00:00
//area = diff_clipped(area, get_collision(node.is_sharp_tail && node.distance_to_top<=0));
2022-12-07 12:32:33 +00:00
append ( base_areas , area ) ;
2022-12-21 11:05:14 +00:00
max_layers_above_base = std : : max ( max_layers_above_base , node . dist_mm_to_top ) ;
2022-07-15 15:37:19 +00:00
}
}
2024-03-15 03:53:06 +00:00
//m_object->print()->set_status(65, (boost::format( _u8L("Support: generate polygons at layer %d")) % layer_nr).str());
2022-07-15 15:37:19 +00:00
// 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 ) ) ;
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
2023-09-23 08:31:49 +00:00
roof_1st_layer = diff_ex ( roof_1st_layer , ClipperUtils : : clip_clipper_polygons_with_subject_bbox ( roof_areas , get_extents ( roof_1st_layer ) ) ) ;
2022-12-15 05:03:53 +00:00
roof_1st_layer = intersection_ex ( roof_1st_layer , m_machine_border ) ;
2022-07-15 15:37:19 +00:00
2023-09-23 08:31:49 +00:00
ExPolygons roofs ; append ( roofs , roof_1st_layer ) ; append ( roofs , roof_areas ) ; append ( roofs , roof_gap_areas ) ;
base_areas = diff_ex ( base_areas , ClipperUtils : : clip_clipper_polygons_with_subject_bbox ( roofs , get_extents ( base_areas ) ) ) ;
2022-12-15 05:03:53 +00:00
base_areas = intersection_ex ( base_areas , m_machine_border ) ;
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 ;
2023-06-25 05:38:29 +00:00
for ( auto & area : base_areas ) { area . simplify ( scale_ ( support_extrusion_width / 2 ) , & base_areas_simplified ) ; }
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 )
{
2023-08-04 14:29:56 +00:00
// find the lowest interface layer
// TODO the gap may not be exact when "independent support layer height" is enabled
size_t layer_nr_next = layer_nr ;
for ( size_t i = 0 ; i < bottom_interface_layers & & layer_nr_next > 0 ; i + + ) {
layer_nr_next = m_ts_data - > layer_heights [ layer_nr_next ] . next_layer_nr ;
}
for ( size_t i = 0 ; i < = bottom_gap_layers & & i < = layer_nr_next ; i + + )
2022-11-20 14:45:29 +00:00
{
2023-08-04 14:29:56 +00:00
const Layer * below_layer = m_object - > get_layer ( layer_nr_next - i ) ;
2022-11-20 14:45:29 +00:00
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 ) {
2023-08-04 14:29:56 +00:00
//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));
2022-07-15 15:37:19 +00:00
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 ;
2023-03-28 07:41:19 +00:00
for ( auto & area : ts_layer - > base_areas ) {
area_groups . emplace_back ( & area , SupportLayer : : BaseType , max_layers_above_base ) ;
2024-03-26 03:39:30 +00:00
area_groups . back ( ) . need_infill = overlaps ( { area } , area_poly ) ;
area_groups . back ( ) . need_extra_wall = need_extra_wall & & ! area_groups . back ( ) . need_infill ;
2023-03-28 07:41:19 +00:00
}
2023-12-20 04:09:27 +00:00
for ( auto & area : ts_layer - > roof_areas ) {
area_groups . emplace_back ( & area , SupportLayer : : RoofType , max_layers_above_roof ) ;
area_groups . back ( ) . interface_id = interface_id ;
}
2022-12-09 06:08:30 +00:00
for ( auto & area : ts_layer - > floor_areas ) area_groups . emplace_back ( & area , SupportLayer : : FloorType , 10000 ) ;
for ( auto & area : ts_layer - > roof_1st_layer ) area_groups . emplace_back ( & area , SupportLayer : : 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 ( ) ) ;
2023-09-14 09:20:58 +00:00
if ( layer_nr < brim_skirt_layers )
ts_layer - > lslices . emplace_back ( * expoly ) ;
2022-07-15 15:37:19 +00:00
}
2022-07-22 09:46:10 +00:00
2023-09-14 09:20:58 +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 ( ) ;
2022-07-15 15:37:19 +00:00
}
} ) ;
2023-06-30 10:11:31 +00:00
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 ;
2024-01-06 13:27:46 +00:00
const std : : vector < SupportNode * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
2022-12-09 06:08:30 +00:00
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_nr + m_raft_layers ) ;
2022-07-06 04:16:37 +00:00
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 - - ) {
2022-12-09 06:08:30 +00:00
if ( ! m_object - > get_support_layer ( layer_nr_lower + m_raft_layers ) - > area_groups . empty ( ) ) break ;
2022-07-15 15:37:19 +00:00
}
2023-03-28 07:41:19 +00:00
if ( layer_nr_lower < = 0 ) continue ;
2022-12-09 06:08:30 +00:00
SupportLayer * lower_layer = m_object - > get_support_layer ( layer_nr_lower + m_raft_layers ) ;
2023-03-28 07:41:19 +00:00
ExPolygons & base_areas_lower = lower_layer - > base_areas ;
2022-07-06 04:16:37 +00:00
ExPolygons overhang ;
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 ( ) ;
2023-03-13 09:33:02 +00:00
ExPolygons ex_hole ;
ex_hole . emplace_back ( std : : move ( ExPolygon ( rev_hole ) ) ) ;
2022-07-06 04:16:37 +00:00
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 ) ) ;
2023-03-28 07:41:19 +00:00
contours . emplace_back ( to_polygons ( base_areas_lower ) ) ;
2022-07-06 04:16:37 +00:00
printZ_to_lightninglayer [ lower_layer - > print_z ] = overhangs . size ( ) - 1 ;
2022-07-15 15:37:19 +00:00
}
2022-07-06 04:16:37 +00:00
2023-03-28 07:41:19 +00:00
auto m_support_material_flow = support_material_flow ( m_object , m_slicing_params . layer_height ) ;
2024-01-26 08:19:59 +00:00
coordf_t support_spacing = m_object_config - > support_base_pattern_spacing . value + m_support_material_flow . spacing ( ) ;
2023-03-28 07:41:19 +00:00
coordf_t support_density = std : : min ( 1. , m_support_material_flow . spacing ( ) / support_spacing * 2 ) ; // for lightning infill the density is defined differently, so need to double it
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
}
2022-12-23 07:19:57 +00:00
else if ( ! with_infill ) {
2022-07-06 04:16:37 +00:00
// move the holes to contour so they can be well supported
2023-06-30 10:11:31 +00:00
// check if poly's contour intersects with expoly's contour
2022-07-06 04:16:37 +00:00
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 ) {
2023-08-04 14:29:56 +00:00
Polylines pl_out = intersection_pl ( to_polylines ( expoly ) , ExPolygon ( poly ) ) ;
if ( pl_out . empty ( ) ) return false ;
2022-07-06 04:16:37 +00:00
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 ;
2024-03-15 03:53:06 +00:00
//m_object->print()->set_status(66, (boost::format(_u8L("Support: fix holes at layer %d")) % layer_nr).str());
2022-07-06 04:16:37 +00:00
2024-01-06 13:27:46 +00:00
const std : : vector < SupportNode * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
2022-12-09 06:08:30 +00:00
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_nr + m_raft_layers ) ;
2022-07-06 04:16:37 +00:00
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 - - ) {
2022-12-09 06:08:30 +00:00
if ( ! m_object - > get_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-12-09 06:08:30 +00:00
auto & area_groups_lower = m_object - > get_support_layer ( layer_nr_lower + m_raft_layers ) - > area_groups ;
2022-07-06 04:16:37 +00:00
for ( const auto & area_group : ts_layer - > area_groups ) {
2022-12-09 06:08:30 +00:00
if ( area_group . type ! = SupportLayer : : BaseType ) continue ;
2022-12-01 13:15:13 +00:00
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-09 06:08:30 +00:00
if ( area_group . type ! = SupportLayer : : BaseType ) continue ;
2022-12-01 13:15:13 +00:00
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 ] ) ) ) {
2023-08-19 02:57:07 +00:00
// after the hole connects to contour, shrink it gradually until it vanishes while moving it outwards. The moving direction is defined in the previous step
2022-07-06 04:16:37 +00:00
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 ( ) ;
2023-08-19 02:57:07 +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.
2024-03-06 00:44:32 +00:00
auto tmp1 = diff_ex ( roof1 , get_collision ( ( layer_nr = = 0 ) ? config . support_object_xy_distance : m_ts_data - > m_xy_distance , layer_nr ) ) ;
2022-07-22 09:46:10 +00:00
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
}
}
2023-06-30 10:11:31 +00:00
2022-07-22 09:46:10 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2022-12-09 06:08:30 +00:00
for ( int layer_nr = m_object - > layer_count ( ) - 1 ; layer_nr > = 0 ; layer_nr - - ) {
SupportLayer * ts_layer = m_object - > get_support_layer ( layer_nr + m_raft_layers ) ;
2022-07-06 04:16:37 +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 ;
2023-08-19 02:57:07 +00:00
ExPolygons roofs = roof_areas ; append ( roofs , roof_1st_layer ) ;
2022-07-06 04:16:37 +00:00
if ( base_areas . empty ( ) & & roof_areas . empty ( ) & & roof_1st_layer . empty ( ) ) continue ;
2024-01-06 13:27:46 +00:00
draw_contours_and_nodes_to_svg ( debug_out_path ( " circles_%d_%.2f.svg " , layer_nr , ts_layer - > print_z ) , base_areas , roofs , ts_layer - > lslices , { } , { } , { " base " , " roof " , " lslices " } , { " blue " , " red " , " black " } ) ;
2022-07-06 04:16:37 +00:00
}
2022-11-30 03:51:25 +00:00
# endif // SUPPORT_TREE_DEBUG_TO_SVG
2022-07-15 15:37:19 +00:00
2022-12-09 06:08:30 +00:00
SupportLayerPtrs & ts_layers = m_object - > support_layers ( ) ;
auto iter = std : : remove_if ( ts_layers . begin ( ) , ts_layers . end ( ) , [ ] ( SupportLayer * ts_layer ) { return ts_layer - > height < EPSILON ; } ) ;
2022-07-06 04:16:37 +00:00
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
}
2024-01-06 13:27:46 +00:00
double SupportNode : : diameter_angle_scale_factor ;
void TreeSupport : : drop_nodes ( std : : vector < std : : vector < SupportNode * > > & contact_nodes )
2022-07-15 15:37:19 +00:00
{
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 ;
2024-01-06 13:27:46 +00:00
SupportNode : : diameter_angle_scale_factor = diameter_angle_scale_factor ;
2022-12-07 12:32:33 +00:00
2024-01-06 13:27:46 +00:00
auto get_max_move_dist = [ this , & config , branch_radius , tip_layers , diameter_angle_scale_factor , wall_count , support_extrusion_width ] ( const SupportNode * node , int power = 1 ) {
2022-12-07 12:32:33 +00:00
double move_dist = node - > max_move_dist ;
if ( node - > max_move_dist = = 0 ) {
2024-03-15 03:53:06 +00:00
node - > radius = get_radius ( node , branch_radius ) ;
2022-12-07 12:32:33 +00:00
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 ) ;
2023-09-23 08:31:49 +00:00
node - > max_move_dist = ( angle < 90 ) ? ( coordf_t ) ( tan_angle * node - > height ) : std : : numeric_limits < coordf_t > : : max ( ) ;
2022-12-07 12:32:33 +00:00
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 ;
} ;
2023-01-05 12:25:55 +00:00
m_ts_data - > layer_heights = plan_layer_heights ( contact_nodes ) ;
std : : vector < LayerHeightData > & layer_heights = m_ts_data - > layer_heights ;
2022-12-07 12:32:33 +00:00
if ( layer_heights . empty ( ) ) return ;
2022-07-15 15:37:19 +00:00
2024-03-15 03:53:06 +00:00
// precalculate avoidance of all possible radii.
// This will cause computing more (radius, layer_nr) pairs, but it's worth to do so since we are doning this in parallel.
if ( 1 ) {
typedef std : : chrono : : high_resolution_clock clock_ ;
typedef std : : chrono : : duration < double , std : : ratio < 1 > > second_ ;
std : : chrono : : time_point < clock_ > t0 { clock_ : : now ( ) } ;
// get all the possible radiis
std : : vector < std : : set < coordf_t > > all_layer_radius ( contact_nodes . size ( ) ) ;
std : : vector < std : : set < coordf_t > > all_layer_node_dist ( contact_nodes . size ( ) ) ;
for ( size_t layer_nr = contact_nodes . size ( ) - 1 ; layer_nr > 0 ; layer_nr - - ) {
if ( layer_heights [ layer_nr ] . height < EPSILON ) continue ;
auto & layer_radius = all_layer_radius [ layer_nr ] ;
auto & layer_node_dist = all_layer_node_dist [ layer_nr ] ;
for ( auto * p_node : contact_nodes [ layer_nr ] ) {
layer_node_dist . emplace ( p_node - > dist_mm_to_top ) ;
}
size_t layer_nr_next = layer_heights [ layer_nr ] . next_layer_nr ;
if ( layer_nr_next < = contact_nodes . size ( ) - 1 & & layer_nr_next > 0 ) {
for ( auto node_dist : layer_node_dist )
all_layer_node_dist [ layer_nr_next ] . emplace ( node_dist + layer_heights [ layer_nr ] . height ) ;
}
for ( auto node_dist : layer_node_dist ) {
layer_radius . emplace ( calc_branch_radius ( branch_radius , node_dist , diameter_angle_scale_factor ) ) ;
}
}
// parallel pre-compute avoidance
tbb : : parallel_for ( tbb : : blocked_range < size_t > ( 0 , contact_nodes . size ( ) - 1 ) , [ & ] ( const tbb : : blocked_range < size_t > & range ) {
for ( size_t layer_nr = range . begin ( ) ; layer_nr < range . end ( ) ; layer_nr + + ) {
for ( auto node_radius : all_layer_radius [ layer_nr ] ) {
m_ts_data - > get_avoidance ( node_radius , layer_nr ) ;
get_collision ( m_ts_data - > m_xy_distance , layer_nr ) ;
}
}
} ) ;
double duration { std : : chrono : : duration_cast < second_ > ( clock_ : : now ( ) - t0 ) . count ( ) } ;
BOOST_LOG_TRIVIAL ( debug ) < < " finish pre calculate_avoidance. before m_avoidance_cache.size()= " < < m_ts_data - > m_avoidance_cache . size ( )
< < " , takes " < < duration < < " secs. " ;
}
2022-07-15 15:37:19 +00:00
m_spanning_trees . resize ( contact_nodes . size ( ) ) ;
//m_mst_line_x_layer_contour_caches.resize(contact_nodes.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 ;
2023-03-28 07:41:19 +00:00
2023-01-05 12:25:55 +00:00
int layer_nr_next = layer_heights [ layer_nr ] . next_layer_nr ;
2024-03-26 03:39:30 +00:00
coordf_t print_z = layer_heights [ layer_nr ] . print_z ;
2023-01-05 12:25:55 +00:00
coordf_t print_z_next = layer_heights [ layer_nr_next ] . print_z ;
coordf_t height_next = layer_heights [ layer_nr_next ] . height ;
2022-11-30 03:51:25 +00:00
2024-01-06 13:27:46 +00:00
std : : deque < std : : pair < size_t , SupportNode * > > unsupported_branch_leaves ; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches.
2022-12-09 06:08:30 +00:00
const Layer * ts_layer = m_object - > get_support_layer ( layer_nr ) ;
2022-11-30 03:51:25 +00:00
2024-03-26 03:39:30 +00:00
m_object - > print ( ) - > set_status ( 60 + int ( 10 * ( 1 - float ( layer_nr ) / contact_nodes . size ( ) ) ) , _u8L ( " Generating support " ) ) ; // (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str());
2022-07-15 15:37:19 +00:00
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];
2024-03-15 03:53:06 +00:00
tbb : : concurrent_unordered_map < Line , bool , LineHash > mst_line_x_layer_contour_cache ;
2022-07-15 15:37:19 +00:00
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 ( ) } ) ;
2024-01-06 13:27:46 +00:00
profiler . stage_add ( STAGE_intersection_ln ) ;
2022-07-15 15:37:19 +00:00
if ( ! pls_intersect . empty ( ) )
return true ;
}
return false ;
} ;
//Group together all nodes for each part.
2024-03-15 03:53:06 +00:00
const ExPolygons & parts = m_ts_data - > m_layer_outlines_below [ layer_nr ] ;
2024-01-06 13:27:46 +00:00
std : : vector < std : : unordered_map < Point , SupportNode * , PointHash > > nodes_per_part ( 1 + parts . size ( ) ) ; //All nodes that aren't inside a part get grouped together in the 0th part.
for ( SupportNode * p_node : layer_contact_nodes )
2022-07-15 15:37:19 +00:00
{
2024-01-06 13:27:46 +00:00
const SupportNode & node = * p_node ;
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-12-23 07:19:57 +00:00
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 ;
2024-01-06 13:27:46 +00:00
for ( const std : : unordered_map < Point , SupportNode * , PointHash > & group : nodes_per_part )
2022-07-15 15:37:19 +00:00
{
std : : vector < Point > points_to_buildplate ;
2024-01-06 13:27:46 +00:00
for ( const std : : pair < const Point , SupportNode * > & entry : group )
2022-07-15 15:37:19 +00:00
{
points_to_buildplate . emplace_back ( entry . first ) ; //Just the position of the node.
}
spanning_trees . emplace_back ( points_to_buildplate ) ;
}
2024-01-06 13:27:46 +00:00
profiler . stage_add ( STAGE_MinimumSpanningTree ) ;
2022-07-15 15:37:19 +00:00
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
coordf_t branch_radius_temp = 0 ;
coordf_t max_y = std : : numeric_limits < coordf_t > : : min ( ) ;
2024-01-06 13:27:46 +00:00
draw_layer_mst ( debug_out_path ( " mtree_%.2f.svg " , ts_layer - > print_z ) , 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 + + )
{
2024-03-15 03:53:06 +00:00
auto & nodes_this_part = nodes_per_part [ group_index ] ;
2022-07-15 15:37:19 +00:00
const MinimumSpanningTree & mst = spanning_trees [ group_index ] ;
//In the first pass, merge all nodes that are close together.
2024-03-15 03:53:06 +00:00
tbb : : concurrent_unordered_set < SupportNode * > to_delete ;
std : : vector < std : : pair < const Point , SupportNode * > > nodes_vec ( nodes_this_part . begin ( ) , nodes_this_part . end ( ) ) ;
tbb : : parallel_for_each ( nodes_vec . begin ( ) , nodes_vec . end ( ) , [ & ] ( const std : : pair < const Point , SupportNode * > & entry ) {
2024-01-06 13:27:46 +00:00
SupportNode * p_node = entry . second ;
SupportNode & node = * p_node ;
2022-07-15 15:37:19 +00:00
if ( to_delete . find ( p_node ) ! = to_delete . end ( ) )
{
2024-03-15 03:53:06 +00:00
return ; //Delete this node (don't create a new node for it on the next layer).
2022-07-15 15:37:19 +00:00
}
const std : : vector < Point > & neighbours = mst . adjacent_nodes ( node . position ) ;
2022-12-23 07:19:57 +00:00
if ( node . type = = ePolygon ) {
2023-10-23 07:29:16 +00:00
// Remove all circle neighbours that are completely inside the polygon and merge them into this node.
2022-12-23 07:19:57 +00:00
for ( const Point & neighbour : neighbours ) {
2024-03-15 03:53:06 +00:00
SupportNode * neighbour_node = nodes_this_part [ neighbour ] ;
2024-03-29 12:31:14 +00:00
if ( neighbour_node - > type = = ePolygon ) continue ;
2024-01-06 13:27:46 +00:00
coord_t neighbour_radius = scale_ ( neighbour_node - > radius ) ;
2022-12-23 07:19:57 +00:00
Point pt_north = neighbour + Point ( 0 , neighbour_radius ) , pt_south = neighbour - Point ( 0 , neighbour_radius ) ,
pt_west = neighbour - Point ( neighbour_radius , 0 ) , pt_east = neighbour + Point ( neighbour_radius , 0 ) ;
2023-09-23 08:31:49 +00:00
if ( is_inside_ex ( node . overhang , neighbour ) & & is_inside_ex ( node . overhang , pt_north ) & & is_inside_ex ( node . overhang , pt_south )
& & is_inside_ex ( node . overhang , pt_west ) & & is_inside_ex ( node . overhang , pt_east ) ) {
2022-12-23 07:19:57 +00:00
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 ) ;
node . dist_mm_to_top = std : : max ( node . dist_mm_to_top , neighbour_node - > dist_mm_to_top ) ;
node . merged_neighbours . push_front ( neighbour_node ) ;
node . merged_neighbours . insert ( node . merged_neighbours . end ( ) , neighbour_node - > merged_neighbours . begin ( ) , neighbour_node - > merged_neighbours . end ( ) ) ;
to_delete . insert ( neighbour_node ) ;
}
}
}
else if ( neighbours . size ( ) = = 1 & & vsize2_with_unscale ( neighbours [ 0 ] - node . position ) < max_move_distance2 & & mst . adjacent_nodes ( neighbours [ 0 ] ) . size ( ) = = 1 & &
2024-03-15 03:53:06 +00:00
nodes_this_part [ neighbours [ 0 ] ] - > type ! = ePolygon ) // We have just two nodes left, and they're very close, and the only neighbor is not ePolygon
2022-07-15 15:37:19 +00:00
{
//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.
2024-03-15 03:53:06 +00:00
coordf_t next_radius = calc_branch_radius ( branch_radius , node . dist_mm_to_top + height_next , diameter_angle_scale_factor ) ;
auto avoid_layer = get_avoidance ( next_radius , 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 ) ;
}
2024-03-15 03:53:06 +00:00
SupportNode * neighbour = nodes_this_part [ neighbours [ 0 ] ] ;
SupportNode * node_parent ;
2022-12-07 12:32:33 +00:00
if ( p_node - > parent & & neighbour - > parent )
2024-03-15 03:53:06 +00:00
node_parent = ( node . dist_mm_to_top > = neighbour - > dist_mm_to_top ) ? p_node : neighbour ;
2022-12-07 12:32:33 +00:00
else
2024-03-15 03:53:06 +00:00
node_parent = p_node - > parent ? p_node : neighbour ;
2023-05-05 04:05:24 +00:00
// Make sure the next pass doesn't drop down either of these (since that already happened).
2024-03-15 03:53:06 +00:00
node_parent - > merged_neighbours . push_front ( node_parent = = p_node ? neighbour : p_node ) ;
2023-10-24 12:59:56 +00:00
const bool to_buildplate = ! is_inside_ex ( get_collision ( 0 , layer_nr_next ) , next_position ) ;
2024-03-15 03:53:06 +00:00
SupportNode * next_node = m_ts_data - > create_node ( next_position , node_parent - > distance_to_top + 1 , layer_nr_next , node_parent - > support_roof_layers_below - 1 , to_buildplate , node_parent ,
print_z_next , height_next ) ;
2022-12-07 12:32:33 +00:00
get_max_move_dist ( next_node ) ;
2024-03-15 03:53:06 +00:00
m_ts_data - > m_mutex . lock ( ) ;
2022-12-07 12:32:33 +00:00
contact_nodes [ layer_nr_next ] . push_back ( next_node ) ;
2022-07-15 15:37:19 +00:00
to_delete . insert ( neighbour ) ;
to_delete . insert ( p_node ) ;
2024-03-15 03:53:06 +00:00
m_ts_data - > m_mutex . unlock ( ) ;
2022-07-15 15:37:19 +00:00
}
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
{
2024-03-15 03:53:06 +00:00
SupportNode * neighbour_node = nodes_this_part [ neighbour ] ;
2022-12-23 07:19:57 +00:00
if ( neighbour_node - > type = = ePolygon ) continue ;
2024-03-14 09:57:32 +00:00
// only allow bigger node to merge smaller nodes. See STUDIO-6326
if ( node . dist_mm_to_top < neighbour_node - > dist_mm_to_top ) continue ;
2022-12-23 07:19:57 +00:00
2024-03-29 12:31:14 +00:00
m_ts_data - > m_mutex . lock ( ) ;
if ( to_delete . find ( p_node ) = = to_delete . end ( ) )
{ // since we are processing all nodes in parallel, p_node may have been deleted by another thread. In this case, we should not delete neighbour_node.
node . merged_neighbours . push_front ( neighbour_node ) ;
node . merged_neighbours . insert ( node . merged_neighbours . end ( ) , neighbour_node - > merged_neighbours . begin ( ) , neighbour_node - > merged_neighbours . end ( ) ) ;
to_delete . insert ( neighbour_node ) ;
neighbour_node - > valid = false ;
}
m_ts_data - > m_mutex . unlock ( ) ;
2022-07-15 15:37:19 +00:00
}
}
}
}
2024-03-15 03:53:06 +00:00
) ;
2022-07-15 15:37:19 +00:00
//In the second pass, move all middle nodes.
2024-03-15 03:53:06 +00:00
tbb : : parallel_for_each ( nodes_vec . begin ( ) , nodes_vec . end ( ) , [ & ] ( const std : : pair < const Point , SupportNode * > & entry ) {
2024-01-06 13:27:46 +00:00
SupportNode * p_node = entry . second ;
const SupportNode & node = * p_node ;
2022-07-15 15:37:19 +00:00
if ( to_delete . find ( p_node ) ! = to_delete . end ( ) )
{
2024-03-15 03:53:06 +00:00
return ;
2022-07-15 15:37:19 +00:00
}
2022-12-23 07:19:57 +00:00
if ( node . type = = ePolygon ) {
// polygon node do not merge or move
2023-10-24 12:59:56 +00:00
const bool to_buildplate = true ;
// keep only the part that won't be removed by the next layer
ExPolygons overhangs_next = diff_clipped ( { node . overhang } , get_collision_polys ( 0 , layer_nr_next ) ) ;
// find the biggest overhang if there are many
float area_biggest = - 1 ;
int index_biggest = - 1 ;
for ( int i = 0 ; i < overhangs_next . size ( ) ; i + + ) {
float a = area ( overhangs_next [ i ] ) ;
if ( a > area_biggest ) {
area_biggest = a ;
index_biggest = i ;
}
}
if ( index_biggest > = 0 ) {
2024-01-06 13:27:46 +00:00
SupportNode * next_node = m_ts_data - > create_node ( p_node - > position , p_node - > distance_to_top + 1 , layer_nr_next , p_node - > support_roof_layers_below - 1 , to_buildplate ,
2023-10-24 12:59:56 +00:00
p_node , print_z_next , height_next ) ;
next_node - > max_move_dist = 0 ;
next_node - > overhang = std : : move ( overhangs_next [ index_biggest ] ) ;
2024-03-15 03:53:06 +00:00
m_ts_data - > m_mutex . lock ( ) ;
2023-10-24 12:59:56 +00:00
contact_nodes [ layer_nr_next ] . emplace_back ( next_node ) ;
2024-03-15 03:53:06 +00:00
m_ts_data - > m_mutex . unlock ( ) ;
2023-10-24 12:59:56 +00:00
}
2024-03-15 03:53:06 +00:00
return ;
2022-12-23 07:19:57 +00:00
}
2022-07-15 15:37:19 +00:00
//If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it.
2023-10-23 07:29:16 +00:00
if ( group_index > 0 & & is_inside_ex ( get_collision ( m_ts_data - > m_xy_distance , layer_nr ) , node . position ) )
2022-07-15 15:37:19 +00:00
{
2024-03-15 03:53:06 +00:00
std : : scoped_lock lock ( m_ts_data - > m_mutex ) ;
const coordf_t branch_radius_node = get_radius ( p_node , branch_radius ) ;
2023-10-23 07:29:16 +00:00
Point to_outside = projection_onto ( get_collision ( m_ts_data - > m_xy_distance , layer_nr ) , node . position ) ;
2022-07-15 15:37:19 +00:00
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
to_delete . insert ( p_node ) ;
2024-01-06 13:27:46 +00:00
p_node - > valid = false ;
2022-10-18 02:46:54 +00:00
}
2024-03-15 03:53:06 +00:00
return ;
2022-07-15 15:37:19 +00:00
}
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 )
{
to_delete . insert ( p_node ) ;
2024-01-06 13:27:46 +00:00
p_node - > valid = false ;
2024-03-15 03:53:06 +00:00
return ;
2022-07-15 15:37:19 +00:00
}
}
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 ;
2024-03-15 03:53:06 +00:00
const std : : vector < Point > & neighbours = mst . adjacent_nodes ( node . position ) ;
2022-07-15 15:37:19 +00:00
// 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
2024-03-15 03:53:06 +00:00
SupportNode * neighbour_node = nodes_this_part [ neighbour ] ;
2022-12-07 12:32:33 +00:00
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 ;
2023-04-12 12:23:00 +00:00
if ( ! is_strong )
2022-12-07 12:32:33 +00:00
sum_direction + = direction * ( 1 / dist2_to_neighbor ) ;
else
2023-03-28 07:41:19 +00:00
sum_direction + = direction ;
2022-07-15 15:37:19 +00:00
}
2023-04-12 12:23:00 +00:00
if ( ! is_strong )
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
}
}
# 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 ) ;
2024-03-15 03:53:06 +00:00
branch_radius_temp = get_radius ( p_node , branch_radius ) ;
2022-07-15 15:37:19 +00:00
}
# endif
2024-03-15 03:53:06 +00:00
coordf_t next_radius = calc_branch_radius ( branch_radius , node . dist_mm_to_top + height_next , diameter_angle_scale_factor ) ;
auto avoidance_next = get_avoidance ( next_radius , layer_nr_next ) ;
2022-07-15 15:37:19 +00:00
2023-10-23 07:29:16 +00:00
Point to_outside = projection_onto ( avoidance_next , node . position ) ;
2022-12-07 12:32:33 +00:00
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 ) | |
2023-10-23 07:29:16 +00:00
! is_inside_ex ( avoidance_next , node . position ) ) {
2022-12-07 12:32:33 +00:00
// 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.
2023-10-23 07:29:16 +00:00
// use get_collision instead of get_avoidance here (See STUDIO-4252)
2024-03-15 03:53:06 +00:00
bool is_outside = move_out_expolys ( get_collision ( next_radius , layer_nr_next ) , candidate_vertex , max_move_between_samples , max_move_between_samples ) ;
2022-12-07 12:32:33 +00:00
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 ;
2023-08-19 02:57:07 +00:00
if ( support_on_buildplate_only )
movement = move_to_neighbor_center + direction_to_outer * 2 ;
else if ( ! is_strong )
2022-12-07 12:32:33 +00:00
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
next_layer_vertex + = movement ;
2022-07-15 15:37:19 +00:00
2022-12-07 12:32:33 +00:00
if ( group_index = = 0 ) {
// Avoid collisions.
const coordf_t max_move_between_samples = get_max_move_dist ( & node , 1 ) + radius_sample_resolution + EPSILON ; // 100 micron extra for rounding errors.
2023-10-23 07:29:16 +00:00
bool is_outside = move_out_expolys ( avoidance_next , next_layer_vertex , radius_sample_resolution + EPSILON , max_move_between_samples ) ;
2022-12-07 12:32:33 +00:00
if ( ! is_outside ) {
Point candidate_vertex = node . position ;
2023-10-23 07:29:16 +00:00
is_outside = move_out_expolys ( avoidance_next , candidate_vertex , radius_sample_resolution + EPSILON , max_move_between_samples ) ;
2022-12-07 12:32:33 +00:00
if ( is_outside ) { next_layer_vertex = candidate_vertex ; }
}
}
2023-10-24 12:59:56 +00:00
const bool to_buildplate = ! is_inside_ex ( get_collision ( 0 , layer_nr_next ) , next_layer_vertex ) ;
2024-01-06 13:27:46 +00:00
SupportNode * next_node = m_ts_data - > create_node ( next_layer_vertex , node . distance_to_top + 1 , layer_nr_next , node . support_roof_layers_below - 1 , to_buildplate , p_node ,
2022-12-07 12:32:33 +00:00
print_z_next , height_next ) ;
get_max_move_dist ( next_node ) ;
2024-03-15 03:53:06 +00:00
m_ts_data - > m_mutex . lock ( ) ;
2022-12-07 12:32:33 +00:00
contact_nodes [ layer_nr_next ] . push_back ( next_node ) ;
2024-03-15 03:53:06 +00:00
m_ts_data - > m_mutex . unlock ( ) ;
2022-07-15 15:37:19 +00:00
}
2024-03-15 03:53:06 +00:00
) ;
2022-07-15 15:37:19 +00:00
}
# ifdef SUPPORT_TREE_DEBUG_TO_SVG
2022-12-23 07:19:57 +00:00
if ( contact_nodes [ layer_nr ] . empty ( ) = = false ) {
2024-01-06 13:27:46 +00:00
draw_contours_and_nodes_to_svg ( debug_out_path ( " contact_points_%.2f.svg " , contact_nodes [ layer_nr ] [ 0 ] - > print_z ) , get_collision ( 0 , layer_nr_next ) ,
2023-09-23 08:31:49 +00:00
get_avoidance ( branch_radius_temp , layer_nr ) ,
2023-10-23 07:29:16 +00:00
m_ts_data - > m_layer_outlines [ layer_nr ] ,
2024-01-06 13:27:46 +00:00
contact_nodes [ layer_nr ] , contact_nodes [ layer_nr_next ] , { " overhang " , " avoid " , " outline " } , { " blue " , " red " , " yellow " } ) ;
2022-12-07 12:32:33 +00:00
2024-01-06 13:27:46 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " drop_nodes layer->next " < < layer_nr < < " -> " < < layer_nr_next < < " , print_z= " < < ts_layer - > print_z
< < " , num points: " < < contact_nodes [ layer_nr ] . size ( ) < < " -> " < < contact_nodes [ layer_nr_next ] . size ( ) ;
2022-12-07 12:32:33 +00:00
for ( size_t i = 0 ; i < std : : min ( size_t ( 5 ) , contact_nodes [ layer_nr ] . size ( ) ) ; i + + ) {
auto & node = contact_nodes [ layer_nr ] [ i ] ;
2023-09-23 08:31:49 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " \t node " < < i < < " , pos= " < < node - > position < < " , move = " < < node - > movement ;
2022-12-07 12:32:33 +00:00
}
}
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 ( ) ;
2024-01-06 13:27:46 +00:00
SupportNode * i_node = entry . second ;
2023-02-15 06:20:31 +00:00
for ( ; i_node ! = nullptr ; i_node = i_node - > parent )
2022-07-15 15:37:19 +00:00
{
2023-02-15 06:20:31 +00:00
size_t i_layer = i_node - > obj_layer_nr ;
2024-01-06 13:27:46 +00:00
std : : vector < SupportNode * > : : iterator to_erase = std : : find ( contact_nodes [ i_layer ] . begin ( ) , contact_nodes [ i_layer ] . end ( ) , i_node ) ;
2022-07-15 15:37:19 +00:00
if ( to_erase ! = contact_nodes [ i_layer ] . end ( ) )
{
2023-02-15 04:03:55 +00:00
// update the parent-child chain
2024-01-06 13:27:46 +00:00
if ( i_node - > parent ) {
i_node - > parent - > child = i_node - > child ;
for ( SupportNode * parent : i_node - > parents ) {
2023-09-23 08:31:49 +00:00
if ( parent - > child = = i_node )
parent - > child = i_node - > child ;
2024-01-06 13:27:46 +00:00
}
2023-09-23 08:31:49 +00:00
}
if ( i_node - > child ) {
2024-01-06 13:27:46 +00:00
i_node - > child - > parent = i_node - > parent ;
i_node - > child - > parents . erase ( std : : find ( i_node - > child - > parents . begin ( ) , i_node - > child - > parents . end ( ) , i_node ) ) ;
append ( i_node - > child - > parents , i_node - > parents ) ;
2023-09-23 08:31:49 +00:00
}
2022-07-15 15:37:19 +00:00
contact_nodes [ i_layer ] . erase ( to_erase ) ;
2024-01-06 13:27:46 +00:00
i_node - > valid = false ;
2022-07-15 15:37:19 +00:00
2024-01-06 13:27:46 +00:00
for ( SupportNode * neighbour : i_node - > merged_neighbours )
2022-07-15 15:37:19 +00:00
{
unsupported_branch_leaves . push_front ( { i_layer , neighbour } ) ;
}
}
}
}
}
2023-08-09 08:58:45 +00:00
2022-07-15 15:37:19 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " after m_avoidance_cache.size()= " < < m_ts_data - > m_avoidance_cache . size ( ) ;
}
2024-01-06 13:27:46 +00:00
void TreeSupport : : smooth_nodes ( std : : vector < std : : vector < SupportNode * > > & contact_nodes )
2022-12-07 12:32:33 +00:00
{
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
2024-01-06 13:27:46 +00:00
std : : vector < SupportNode * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
2022-12-07 12:32:33 +00:00
if ( curr_layer_nodes . empty ( ) ) continue ;
2024-01-06 13:27:46 +00:00
for ( SupportNode * node : curr_layer_nodes ) {
2023-04-13 12:38:41 +00:00
node - > is_processed = false ;
2022-12-07 12:32:33 +00:00
}
}
2023-09-23 08:31:49 +00:00
float max_move = scale_ ( m_object_config - > support_line_width / 2 ) ;
2023-04-13 12:38:41 +00:00
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
2024-01-06 13:27:46 +00:00
std : : vector < SupportNode * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
2022-12-07 12:32:33 +00:00
if ( curr_layer_nodes . empty ( ) ) continue ;
2024-01-06 13:27:46 +00:00
for ( SupportNode * node : curr_layer_nodes ) {
2023-04-13 12:38:41 +00:00
if ( ! node - > is_processed ) {
2022-12-07 12:32:33 +00:00
std : : vector < Point > pts ;
2023-09-23 08:31:49 +00:00
std : : vector < double > radii ;
2024-01-06 13:27:46 +00:00
std : : vector < SupportNode * > branch ;
SupportNode * p_node = node ;
2023-09-23 08:31:49 +00:00
// add a fixed head if it's not a polygon node, see STUDIO-4403
// Polygon node can't be added because the move distance might be huge, making the nodes in between jump and dangling
if ( node - > child & & node - > child - > type ! = ePolygon ) {
2023-04-13 12:38:41 +00:00
pts . push_back ( p_node - > child - > position ) ;
2023-09-23 08:31:49 +00:00
radii . push_back ( p_node - > child - > radius ) ;
2023-04-13 12:38:41 +00:00
branch . push_back ( p_node - > child ) ;
2022-12-07 12:32:33 +00:00
}
do {
pts . push_back ( p_node - > position ) ;
2023-09-23 08:31:49 +00:00
radii . push_back ( p_node - > radius ) ;
2022-12-07 12:32:33 +00:00
branch . push_back ( p_node ) ;
2023-04-13 12:38:41 +00:00
p_node = p_node - > parent ;
} while ( p_node & & ! p_node - > is_processed ) ;
2022-12-07 12:32:33 +00:00
if ( pts . size ( ) < 3 ) continue ;
std : : vector < Point > pts1 = pts ;
2023-09-23 08:31:49 +00:00
std : : vector < double > radii1 = radii ;
2022-12-07 12:32:33 +00:00
// TODO here we assume layer height gap is constant. If not true, need to consider height jump
2023-04-13 12:38:41 +00:00
const int iterations = 100 ;
for ( size_t k = 0 ; k < iterations ; k + + ) {
2022-12-07 12:32:33 +00:00
for ( size_t i = 1 ; i < pts . size ( ) - 1 ; i + + ) {
2023-09-23 08:31:49 +00:00
Point pt = ( pts [ i - 1 ] + pts [ i ] + pts [ i + 1 ] ) / 3 ;
2022-12-07 12:32:33 +00:00
pts1 [ i ] = pt ;
2023-09-23 08:31:49 +00:00
radii1 [ i ] = ( radii [ i - 1 ] + radii [ i ] + radii [ i + 1 ] ) / 3 ;
2023-04-13 12:38:41 +00:00
if ( k = = iterations - 1 ) {
branch [ i ] - > position = pt ;
2023-09-23 08:31:49 +00:00
branch [ i ] - > radius = radii1 [ i ] ;
2023-04-13 12:38:41 +00:00
branch [ i ] - > movement = ( pts [ i + 1 ] - pts [ i - 1 ] ) / 2 ;
branch [ i ] - > is_processed = true ;
2023-09-23 08:31:49 +00:00
if ( branch [ i ] - > parents . size ( ) > 1 | | ( branch [ i ] - > movement . x ( ) > max_move | | branch [ i ] - > movement . y ( ) > max_move ) )
branch [ i ] - > need_extra_wall = true ;
2024-05-10 12:56:42 +00:00
BOOST_LOG_TRIVIAL ( trace ) < < " smooth_nodes: layer_nr= " < < layer_nr < < " , i= " < < i < < " , pt= " < < pt < < " , movement= " < < branch [ i ] - > movement < < " , radius= " < < branch [ i ] - > radius ;
2023-04-13 12:38:41 +00:00
}
2022-12-07 12:32:33 +00:00
}
2023-09-23 08:31:49 +00:00
if ( k < iterations - 1 ) {
2023-04-13 12:38:41 +00:00
std : : swap ( pts , pts1 ) ;
2023-09-23 08:31:49 +00:00
std : : swap ( radii , radii1 ) ;
}
else {
// interpolate need_extra_wall in the end
for ( size_t i = 1 ; i < branch . size ( ) - 1 ; i + + ) {
if ( branch [ i - 1 ] - > need_extra_wall & & branch [ i + 1 ] - > need_extra_wall )
branch [ i ] - > need_extra_wall = true ;
}
}
2022-12-07 12:32:33 +00:00
}
}
}
}
}
2023-09-23 08:31:49 +00:00
# if USE_SUPPORT_3D
2024-01-06 13:27:46 +00:00
void TreeSupport : : smooth_nodes ( std : : vector < std : : vector < SupportNode * > > & contact_nodes , const TreeSupport3D : : TreeSupportSettings & config )
2022-07-15 15:37:19 +00:00
{
2023-09-23 08:31:49 +00:00
const coordf_t branch_radius = m_object_config - > tree_support_branch_diameter . value / 2 ;
const coordf_t branch_radius_scaled = scale_ ( branch_radius ) ;
const double diameter_angle_scale_factor = tan ( tree_support_branch_diameter_angle * M_PI / 180. ) ;
// contact nodes to support elements
TreeSupport3D : : SupportElements move_bounds ;
2024-01-06 13:27:46 +00:00
std : : map < SupportNode * , int > node2elemIdx ;
std : : map < int , SupportNode * > elemIdx2node ;
2023-09-23 08:31:49 +00:00
int n = 0 ;
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
2024-01-06 13:27:46 +00:00
std : : vector < SupportNode * > & curr_layer_nodes = contact_nodes [ layer_nr ] ;
for ( SupportNode * node : curr_layer_nodes ) {
2023-09-23 08:31:49 +00:00
if ( node - > distance_to_top < 0 ) continue ;
TreeSupport3D : : SupportElementState state ;
state . type = node - > type ;
state . radius = node - > radius ;
state . print_z = node - > print_z ;
state . target_height = node - > obj_layer_nr ;
state . target_position = node - > position ;
state . next_position = node - > position ;
state . layer_idx = node - > obj_layer_nr ;
state . effective_radius_height = node - > distance_to_top ;
state . to_buildplate = node - > to_buildplate ;
state . distance_to_top = node - > distance_to_top ;
state . result_on_layer = node - > position ;
state . increased_to_model_radius = 0 ;
state . to_model_gracious = true ; //TODO
state . elephant_foot_increases = 0 ;
state . use_min_xy_dist = true ; //TODO
state . supports_roof = node - > distance_to_top < = m_object_config - > support_interface_top_layers . value ;
state . dont_move_until = DO_NOT_MOVER_UNDER_MM / m_object_config - > layer_height . value ;
state . can_use_safe_radius = true ; //TODO
state . missing_roof_layers = 0 ;
state . skip_ovalisation = false ;
Polygons circle ;
if ( node - > type = = eCircle ) {
const Polygon base_circle = make_circle ( scale_ ( std : : max ( 1.0 , node - > radius ) ) , TreeSupport3D : : SUPPORT_TREE_CIRCLE_RESOLUTION ) ;
circle . emplace_back ( base_circle ) ;
circle . front ( ) . translate ( node - > position ) ;
2022-07-15 15:37:19 +00:00
}
2023-09-23 08:31:49 +00:00
else if ( node - > type = = ePolygon ) {
circle . emplace_back ( node - > overhang . contour ) ;
}
move_bounds . emplace_back ( state , std : : move ( circle ) ) ;
node2elemIdx . emplace ( node , n ) ;
elemIdx2node . emplace ( n , node ) ;
n + + ;
2022-07-15 15:37:19 +00:00
}
}
2023-09-23 08:31:49 +00:00
// All SupportElements are put into a layer independent storage to improve parallelization.
std : : vector < std : : pair < TreeSupport3D : : SupportElement * , int > > elements_with_link_down ;
std : : vector < size_t > linear_data_layers ;
do
{
for ( size_t i = 0 ; i < move_bounds . size ( ) ; i + + ) {
TreeSupport3D : : SupportElement * elem = & move_bounds [ i ] ;
2024-01-06 13:27:46 +00:00
SupportNode * node = elemIdx2node [ i ] ;
2023-09-23 08:31:49 +00:00
size_t child = i ;
for ( auto parent : node - > parents ) {
size_t idx_parent = parent ? node2elemIdx [ parent ] : 0 ;
if ( ! parent | | idx_parent > = move_bounds . size ( ) ) child = - 1 ;
elem - > parents . push_back ( idx_parent ) ;
TreeSupport3D : : SupportElement * elem_parent = & move_bounds [ idx_parent ] ;
if ( elem_parent - > state . layer_idx = = 0 ) child = - 1 ;
elements_with_link_down . push_back ( { elem_parent , child } ) ;
2022-07-15 15:37:19 +00:00
}
}
2023-09-23 08:31:49 +00:00
for ( LayerIndex layer_idx = 0 ; layer_idx < LayerIndex ( contact_nodes . size ( ) ) ; + + layer_idx ) {
linear_data_layers . emplace_back ( 0 ) ;
}
break ;
std : : vector < std : : pair < Node * , int > > map_downwards_old ;
std : : vector < std : : pair < Node * , int > > map_downwards_new ;
linear_data_layers . emplace_back ( 0 ) ;
for ( LayerIndex layer_idx = 0 ; layer_idx < LayerIndex ( contact_nodes . size ( ) ) ; + + layer_idx ) {
std : : sort ( map_downwards_old . begin ( ) , map_downwards_old . end ( ) , [ ] ( auto & l , auto & r ) { return l . first < r . first ; } ) ;
auto & layer = contact_nodes [ layer_idx ] ;
for ( size_t elem_idx = 0 ; elem_idx < layer . size ( ) ; + + elem_idx ) {
Node * node = layer [ elem_idx ] ;
int child = - 1 ;
if ( layer_idx > 0 ) {
auto it = std : : lower_bound ( map_downwards_old . begin ( ) , map_downwards_old . end ( ) , node , [ ] ( auto & l , const Node * r ) { return l . first < r ; } ) ;
if ( it ! = map_downwards_old . end ( ) & & it - > first = = node ) {
child = it - > second ;
// Only one link points to a node above from below.
assert ( ! ( + + it ! = map_downwards_old . end ( ) & & it - > first = = node ) ) ;
}
const Node * pchild = child = = - 1 ? nullptr : contact_nodes [ layer_idx - 1 ] [ child ] ;
}
TreeSupport3D : : SupportElement * elem = & move_bounds [ node2elemIdx [ node ] ] ;
if ( node - > parent ) {
map_downwards_new . emplace_back ( node - > parent , elem_idx ) ;
elem - > parents . push_back ( node2elemIdx [ node - > parent ] ) ;
//for (auto parent_neighbor : node->parent->merged_neighbours) {
// assert(parent_neighbor);
// if (parent_neighbor) {
// map_downwards_new.emplace_back(parent_neighbor, elem_idx);
// elem->parents.push_back(node2elemIdx[parent_neighbor]);
// }
//}
}
elements_with_link_down . push_back ( { elem , int ( child ) } ) ;
2022-07-15 15:37:19 +00:00
}
2023-09-23 08:31:49 +00:00
std : : swap ( map_downwards_old , map_downwards_new ) ;
// index of parents are directly based on the beginning of move_bounds, so no need to store the size of the previous layer.
linear_data_layers . emplace_back ( 0 ) ;
2022-07-15 15:37:19 +00:00
}
2023-09-23 08:31:49 +00:00
} while ( 0 ) ;
2022-07-15 15:37:19 +00:00
2023-09-23 08:31:49 +00:00
throw_on_cancel ( ) ;
2022-07-15 15:37:19 +00:00
2023-09-23 08:31:49 +00:00
organic_smooth_branches_avoid_collisions ( * m_object , * this - > m_model_volumes , config , elements_with_link_down , linear_data_layers , throw_on_cancel ) ;
2022-07-15 15:37:19 +00:00
}
2023-09-23 08:31:49 +00:00
# endif
2022-07-15 15:37:19 +00:00
2024-01-06 13:27:46 +00:00
std : : vector < LayerHeightData > TreeSupport : : plan_layer_heights ( std : : vector < std : : vector < SupportNode * > > & contact_nodes )
2022-11-30 03:51:25 +00:00
{
const coordf_t max_layer_height = m_slicing_params . max_layer_height ;
2023-09-23 08:31:49 +00:00
const coordf_t layer_height = m_object_config - > layer_height . value ;
2022-11-30 03:51:25 +00:00
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 ) {
2023-09-23 08:31:49 +00:00
z_distance_top + = m_object - > layers ( ) [ 0 ] - > regions ( ) [ 0 ] - > region ( ) . bridging_height_avg ( * m_print_config ) - layer_height ;
2022-11-30 03:51:25 +00:00
}
2023-09-23 08:31:49 +00:00
const size_t support_roof_layers = m_object_config - > support_interface_top_layers . value ;
2022-11-30 03:51:25 +00:00
const int z_distance_top_layers = round_up_divide ( scale_ ( z_distance_top ) , scale_ ( layer_height ) ) + 1 ;
2023-01-05 12:25:55 +00:00
std : : vector < LayerHeightData > layer_heights ( contact_nodes . size ( ) ) ;
2022-11-30 03:51:25 +00:00
std : : vector < int > bounds ;
2023-11-16 13:05:26 +00:00
if ( layer_height = = max_layer_height | | ! m_support_params . independent_layer_height ) {
2022-12-07 12:32:33 +00:00
for ( int layer_nr = 0 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
2023-01-05 12:25:55 +00:00
layer_heights [ layer_nr ] = { m_object - > get_layer ( layer_nr ) - > print_z , m_object - > get_layer ( layer_nr ) - > height , layer_nr > 0 ? size_t ( layer_nr - 1 ) : 0 } ;
2022-12-07 12:32:33 +00:00
}
}
2024-03-14 09:57:32 +00:00
else {
bounds . push_back ( 0 ) ;
// Keep first layer still
layer_heights [ 0 ] = { m_object - > get_layer ( 0 ) - > print_z , m_object - > get_layer ( 0 ) - > height , 0 } ;
// Collect top contact layers
for ( int layer_nr = 1 ; layer_nr < contact_nodes . size ( ) ; layer_nr + + ) {
if ( ! contact_nodes [ layer_nr ] . empty ( ) ) {
bounds . push_back ( layer_nr ) ;
layer_heights [ layer_nr ] . print_z = contact_nodes [ layer_nr ] . front ( ) - > print_z ;
layer_heights [ layer_nr ] . height = contact_nodes [ layer_nr ] . front ( ) - > height ;
BOOST_LOG_TRIVIAL ( trace ) < < " plan_layer_heights0 print_z, height, layer_nr: " < < layer_heights [ layer_nr ] . print_z < < " " < < layer_heights [ layer_nr ] . height < < " "
< < layer_nr ;
2022-11-30 03:51:25 +00:00
}
2024-03-14 09:57:32 +00:00
}
std : : set < int > s ( bounds . begin ( ) , bounds . end ( ) ) ;
bounds . assign ( s . begin ( ) , s . end ( ) ) ;
for ( size_t idx_extreme = 1 ; idx_extreme < bounds . size ( ) ; idx_extreme + + ) {
int extr2_layer_nr = bounds [ idx_extreme ] ;
coordf_t extr2z = layer_heights [ extr2_layer_nr ] . bottom_z ( ) ;
int extr1_layer_nr = bounds [ idx_extreme - 1 ] ;
coordf_t extr1z = layer_heights [ 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 + 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 ;
if ( n_layers_extra < 1 )
continue ;
2022-11-30 03:51:25 +00:00
2024-03-14 09:57:32 +00:00
coordf_t step = dist / coordf_t ( n_layers_extra ) ;
coordf_t print_z = extr1z + step ;
//assert(step >= layer_height - EPSILON);
coordf_t extr2z_large_steps = extr2z ;
for ( int layer_nr = extr1_layer_nr + 1 ; layer_nr < extr2_layer_nr ; layer_nr + + ) {
// if (curr_layer_nodes.empty()) continue;
if ( std : : abs ( print_z - m_object - > get_layer ( layer_nr ) - > print_z ) < step / 2 + EPSILON | | extr_layers_left < 1 ) {
layer_heights [ layer_nr ] . print_z = print_z ;
layer_heights [ layer_nr ] . height = step ;
print_z + = step ;
}
else {
// can't clear curr_layer_nodes, or the model will have empty layers
layer_heights [ layer_nr ] . print_z = 0.0 ;
layer_heights [ layer_nr ] . height = 0.0 ;
extr_layers_left - - ;
}
2022-11-30 03:51:25 +00:00
}
}
2024-03-14 09:57:32 +00:00
// fill in next_layer_nr
int i = layer_heights . size ( ) - 1 , j = i ;
2024-05-23 14:01:01 +00:00
for ( ; i > = 0 ; i - - ) {
2024-03-14 09:57:32 +00:00
if ( layer_heights [ i ] . height < EPSILON ) {
continue ;
}
for ( j = i - 1 ; j > = 0 ; j - - ) {
2024-05-23 14:01:01 +00:00
if ( layer_heights [ j ] . height > EPSILON & & layer_heights [ j ] . print_z < layer_heights [ i ] . bottom_z ( ) + EPSILON ) {
2024-03-14 09:57:32 +00:00
layer_heights [ i ] . next_layer_nr = j ;
2024-05-23 14:01:01 +00:00
if ( layer_heights [ i ] . bottom_z ( ) - layer_heights [ j ] . print_z > 0.01 ) // there is a gap more than 0.01mm, increase the top z distance to fill the gap
layer_heights [ i ] . height = layer_heights [ i ] . print_z - layer_heights [ j ] . print_z ;
2024-03-14 09:57:32 +00:00
break ;
}
}
2023-03-14 09:04:42 +00:00
}
2024-01-06 13:27:46 +00:00
}
2024-05-23 14:01:01 +00:00
// log layer_heights
2024-03-14 09:57:32 +00:00
for ( size_t i = 0 ; i < layer_heights . size ( ) ; i + + ) {
2024-01-06 13:27:46 +00:00
if ( layer_heights [ i ] . height > EPSILON )
BOOST_LOG_TRIVIAL ( trace ) < < " plan_layer_heights print_z, height, lower_layer_nr->layer_nr: " < < layer_heights [ i ] . print_z < < " " < < layer_heights [ i ] . height < < " "
< < layer_heights [ i ] . next_layer_nr < < " -> " < < i ;
2023-01-05 12:25:55 +00:00
}
2022-11-30 03:51:25 +00:00
return layer_heights ;
}
2024-03-26 03:39:30 +00:00
void TreeSupport : : generate_contact_points ( std : : vector < std : : vector < SupportNode * > > & contact_nodes )
2022-07-15 15:37:19 +00:00
{
const PrintObjectConfig & config = m_object - > config ( ) ;
const coordf_t point_spread = scale_ ( config . tree_support_branch_distance . value ) ;
2024-01-06 13:27:46 +00:00
const coordf_t max_bridge_length = scale_ ( config . max_bridge_length . value ) ;
coord_t radius = scale_ ( config . tree_support_branch_diameter . value / 2 ) ;
radius = std : : max ( m_min_radius , radius ) ;
2022-07-15 15:37:19 +00:00
//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 ;
const auto center = bounding_box_middle ( bounding_box ) ;
const auto sin_angle = std : : sin ( rotate_angle ) ;
const auto cos_angle = std : : cos ( rotate_angle ) ;
2023-01-22 06:52:18 +00:00
const Point rotated_dims = Point (
2022-07-15 15:37:19 +00:00
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 ;
2024-01-06 13:27:46 +00:00
coordf_t sample_step = std : : max ( point_spread , max_bridge_length / 2 ) ;
for ( auto x = - rotated_dims ( 0 ) ; x < rotated_dims ( 0 ) ; x + = sample_step ) {
for ( auto y = - rotated_dims ( 1 ) ; y < rotated_dims ( 1 ) ; y + = sample_step ) {
2022-07-15 15:37:19 +00:00
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 ;
2024-01-06 13:27:46 +00:00
if ( ! m_support_params . independent_layer_height ) {
z_distance_top = round ( z_distance_top / layer_height ) * layer_height ;
2022-07-15 15:37:19 +00:00
// 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 ;
2024-01-06 13:27:46 +00:00
}
2022-07-15 15:37:19 +00:00
}
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.
2024-01-31 06:05:09 +00:00
int gap_layers = z_distance_top = = 0 ? 0 : 1 ;
2024-01-06 13:27:46 +00:00
// virtual layer with 0 height will be deleted
if ( z_distance_top = = 0 )
z_distance_top = 0.001 ;
2022-07-15 15:37:19 +00:00
2022-12-24 11:16:53 +00:00
size_t support_roof_layers = config . support_interface_top_layers . value ;
if ( support_roof_layers > 0 )
support_roof_layers + = 1 ; // BBS: add a normal support layer below interface (if we have interface)
2023-04-06 12:48:58 +00:00
coordf_t thresh_angle = std : : min ( 89.f , config . support_threshold_angle . value < EPSILON ? 30.f : config . support_threshold_angle . value ) ;
2022-07-15 15:37:19 +00:00
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 ;
2024-03-26 03:39:30 +00:00
//if (m_object->support_layer_count() <= m_raft_layers)
// return;
2022-07-15 15:37:19 +00:00
2022-07-22 09:46:10 +00:00
int nonempty_layers = 0 ;
2024-01-06 13:27:46 +00:00
tbb : : concurrent_vector < Slic3r : : Vec3f > all_nodes ;
tbb : : parallel_for ( tbb : : blocked_range < size_t > ( 1 , m_object - > layers ( ) . size ( ) ) , [ & ] ( const tbb : : blocked_range < size_t > & range ) {
for ( size_t layer_nr = range . begin ( ) ; layer_nr < range . end ( ) ; layer_nr + + ) {
if ( m_object - > print ( ) - > canceled ( ) )
break ;
Layer * layer = m_object - > get_layer ( layer_nr ) ;
auto & curr_nodes = contact_nodes [ layer_nr - 1 ] ;
2024-03-26 03:39:30 +00:00
if ( layer - > loverhangs . empty ( ) ) continue ;
2024-01-06 13:27:46 +00:00
std : : unordered_set < Point , PointHash > already_inserted ;
auto print_z = m_object - > get_layer ( layer_nr ) - > print_z ;
auto bottom_z = m_object - > get_layer ( layer_nr ) - > bottom_z ( ) ;
auto height = m_object - > get_layer ( layer_nr ) - > height ;
bool added = false ; // Did we add a point this way?
bool is_sharp_tail = false ;
auto insert_point = [ & ] ( Point pt , const ExPolygon & overhang_part , bool force_add = false ) {
Point hash_pos = pt / ( ( radius + 1 ) / 1 ) ;
SupportNode * contact_node = nullptr ;
if ( force_add | | ! already_inserted . count ( hash_pos ) ) {
already_inserted . emplace ( hash_pos ) ;
bool to_buildplate = true ;
// add a new node as a virtual node which acts as the invisible gap between support and object
// distance_to_top=-1: it's virtual
// print_z=object_layer->bottom_z: it directly contacts the bottom
// height=z_distance_top: it's height is exactly the gap distance
// dist_mm_to_top=0: it directly contacts the bottom
2024-01-31 06:05:09 +00:00
contact_node = m_ts_data - > create_node ( pt , - gap_layers , layer_nr - 1 , support_roof_layers + 1 , to_buildplate , SupportNode : : NO_PARENT , bottom_z , z_distance_top , 0 ,
2024-01-06 13:27:46 +00:00
unscale_ ( radius ) ) ;
2023-10-24 12:59:56 +00:00
contact_node - > overhang = overhang_part ;
2024-01-06 13:27:46 +00:00
contact_node - > is_sharp_tail = is_sharp_tail ;
if ( is_sharp_tail ) {
int ind = overhang_part . contour . closest_point_index ( pt ) ;
auto n1 = ( overhang_part . contour [ ind ] - overhang_part . contour [ ind - 1 ] ) . cast < double > ( ) . normalized ( ) ;
auto n2 = ( overhang_part . contour [ ind ] - overhang_part . contour [ ind + 1 ] ) . cast < double > ( ) . normalized ( ) ;
contact_node - > skin_direction = scaled ( ( n1 + n2 ) . normalized ( ) ) ;
}
2023-05-31 08:27:01 +00:00
curr_nodes . emplace_back ( contact_node ) ;
2024-01-06 13:27:46 +00:00
added = true ;
} ;
return contact_node ;
} ;
2022-07-15 15:37:19 +00:00
2024-03-26 03:39:30 +00:00
for ( const auto & overhang_part : layer - > loverhangs ) {
const auto & overhang_type = this - > overhang_types [ & overhang_part ] ;
is_sharp_tail = overhang_type = = OverhangType : : SharpTail ;
2024-01-06 13:27:46 +00:00
BoundingBox overhang_bounds = get_extents ( overhang_part ) ;
if ( support_style = = smsTreeHybrid & & overhang_part . area ( ) > m_support_params . thresh_big_overhang & & ! is_sharp_tail ) {
if ( ! overlaps ( { overhang_part } , m_ts_data - > m_layer_outlines_below [ layer_nr - 1 ] ) ) {
Point candidate = overhang_bounds . center ( ) ;
SupportNode * contact_node = insert_point ( candidate , overhang_part , true ) ;
contact_node - > type = ePolygon ;
contact_node - > radius = unscale_ ( overhang_bounds . radius ( ) ) ;
2022-07-22 09:46:10 +00:00
curr_nodes . emplace_back ( contact_node ) ;
2024-01-06 13:27:46 +00:00
continue ;
2023-09-23 08:31:49 +00:00
}
}
2024-01-06 13:27:46 +00:00
// add supports at corners for both auto and manual overhangs, github #2008
{
auto & points = overhang_part . contour . points ;
int nSize = points . size ( ) ;
for ( int i = 0 ; i < nSize ; i + + ) {
auto pt = points [ i ] ;
auto v1 = ( pt - points [ ( i - 1 + nSize ) % nSize ] ) . cast < double > ( ) . normalized ( ) ;
auto v2 = ( pt - points [ ( i + 1 ) % nSize ] ) . cast < double > ( ) . normalized ( ) ;
if ( v1 . dot ( v2 ) > - 0.7 ) { // angle smaller than 135 degrees
SupportNode * contact_node = insert_point ( pt , overhang_part ) ;
if ( contact_node ) {
contact_node - > is_corner = true ;
2023-09-23 08:31:49 +00:00
}
}
}
}
2024-03-26 03:39:30 +00:00
// add supports along contours
libnest2d : : placers : : EdgeCache < ExPolygon > edge_cache ( overhang_part ) ;
for ( size_t i = 0 ; i < edge_cache . holeCount ( ) + 1 ; i + + ) {
double step = point_spread / ( i = = 0 ? edge_cache . circumference ( ) : edge_cache . circumference ( i - 1 ) ) ;
double distance = 0 ;
while ( distance < 1 ) {
auto pt = i = = 0 ? edge_cache . coords ( distance ) : edge_cache . coords ( i - 1 , distance ) ;
SupportNode * contact_node = insert_point ( pt , overhang_part ) ;
distance + = step ;
}
}
// don't add inner supports for sharp tails
if ( is_sharp_tail )
continue ;
// add inner supports
overhang_bounds . inflated ( - radius ) ;
ExPolygons overhang_inner = offset_ex ( overhang_part , - radius ) ;
2024-01-06 13:27:46 +00:00
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!
2024-03-26 03:39:30 +00:00
bool is_inside = is_inside_ex ( overhang_inner , candidate ) ;
2024-01-06 13:27:46 +00:00
if ( is_inside ) {
SupportNode * contact_node = insert_point ( candidate , overhang_part ) ;
2022-07-22 09:46:10 +00:00
}
}
2024-01-06 13:27:46 +00:00
}
2024-03-26 03:39:30 +00:00
2023-09-23 08:31:49 +00:00
}
2024-01-06 13:27:46 +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
2024-03-26 03:39:30 +00:00
draw_contours_and_nodes_to_svg ( debug_out_path ( " init_contact_points_%.2f.svg " , print_z ) , layer - > loverhangs , layer - > lslices , m_ts_data - > m_layer_outlines_below [ layer_nr ] ,
contact_nodes [ layer_nr ] , contact_nodes [ layer_nr - 1 ] , { " overhang " , " lslices " , " outlines_below " } ) ;
2022-07-15 15:37:19 +00:00
# endif
2024-01-06 13:27:46 +00:00
} }
) ; // end tbb::parallel_for
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-07-15 15:37:19 +00:00
}
2024-01-06 13:27:46 +00:00
void TreeSupport : : insert_dropped_node ( std : : vector < SupportNode * > & nodes_layer , SupportNode * p_node )
2022-07-15 15:37:19 +00:00
{
2024-01-06 13:27:46 +00:00
std : : vector < SupportNode * > : : iterator conflicting_node_it = std : : find ( nodes_layer . begin ( ) , nodes_layer . end ( ) , p_node ) ;
2022-07-15 15:37:19 +00:00
if ( conflicting_node_it = = nodes_layer . end ( ) ) //No conflict.
{
nodes_layer . emplace_back ( p_node ) ;
return ;
}
2024-01-06 13:27:46 +00:00
SupportNode * conflicting_node = * conflicting_node_it ;
2022-07-15 15:37:19 +00:00
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 )
{
2024-01-06 13:27:46 +00:00
clear_nodes ( ) ;
2022-07-15 15:37:19 +00:00
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 ) ;
2024-01-06 13:27:46 +00:00
profiler . stage_add ( STAGE_get_collision ) ;
2022-07-15 15:37:19 +00:00
return collision ;
}
2023-03-14 09:04:42 +00:00
const ExPolygons & TreeSupportData : : get_avoidance ( coordf_t radius , size_t layer_nr , int recursions ) const
2022-07-15 15:37:19 +00:00
{
profiler . tic ( ) ;
radius = ceil_radius ( radius ) ;
2023-03-14 09:04:42 +00:00
RadiusLayerPair key { radius , layer_nr , recursions } ;
2022-07-15 15:37:19 +00:00
const auto it = m_avoidance_cache . find ( key ) ;
const ExPolygons & avoidance = it ! = m_avoidance_cache . end ( ) ? it - > second : calculate_avoidance ( key ) ;
2024-01-06 13:27:46 +00:00
profiler . stage_add ( STAGE_GET_AVOIDANCE ) ;
2022-07-15 15:37:19 +00:00
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 ;
}
2024-01-06 13:27:46 +00:00
SupportNode * TreeSupportData : : create_node ( const Point position , const int distance_to_top , const int obj_layer_nr , const int support_roof_layers_below , const bool to_buildplate , SupportNode * parent , coordf_t print_z_ , coordf_t height_ , coordf_t dist_mm_to_top_ , coordf_t radius_ )
{
// this function may be called from multiple threads, need to lock
m_mutex . lock ( ) ;
SupportNode * node = new SupportNode ( position , distance_to_top , obj_layer_nr , support_roof_layers_below , to_buildplate , parent , print_z_ , height_ , dist_mm_to_top_ , radius_ ) ;
contact_nodes . emplace_back ( node ) ;
m_mutex . unlock ( ) ;
2024-03-14 09:57:32 +00:00
if ( parent )
node - > movement = position - parent - > position ;
2024-01-06 13:27:46 +00:00
return node ;
}
void TreeSupportData : : clear_nodes ( )
{
for ( auto node : contact_nodes ) {
delete node ;
}
contact_nodes . clear ( ) ;
}
void TreeSupportData : : remove_invalid_nodes ( )
{
for ( auto it = contact_nodes . begin ( ) ; it ! = contact_nodes . end ( ) ; ) {
if ( ( * it ) - > valid = = false ) {
delete ( * it ) ;
it = contact_nodes . erase ( it ) ;
}
else {
it + + ;
}
}
}
2022-07-15 15:37:19 +00:00
coordf_t TreeSupportData : : ceil_radius ( coordf_t radius ) const
{
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 ;
}
}
const ExPolygons & TreeSupportData : : calculate_collision ( const RadiusLayerPair & key ) const
{
2023-03-14 09:04:42 +00:00
assert ( key . layer_nr < m_layer_outlines . size ( ) ) ;
2022-07-15 15:37:19 +00:00
2023-03-14 09:04:42 +00:00
ExPolygons collision_areas = std : : move ( offset_ex ( m_layer_outlines [ key . layer_nr ] , scale_ ( key . radius ) ) ) ;
2023-09-23 08:31:49 +00:00
collision_areas = expolygons_simplify ( collision_areas , scale_ ( m_radius_sample_resolution ) ) ;
2022-07-15 15:37:19 +00:00
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
{
2023-03-14 09:04:42 +00:00
const auto & radius = key . radius ;
const auto & layer_nr = key . layer_nr ;
2024-03-15 03:53:06 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " calculate_avoidance on (radius,layer)= ( " < < radius < < " , " < < layer_nr < < " ), recursion= " < < key . recursions ;
2023-03-14 09:04:42 +00:00
constexpr auto max_recursion_depth = 100 ;
if ( key . recursions < = max_recursion_depth * 2 ) {
2022-12-07 12:32:33 +00:00
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.
2023-01-05 12:25:55 +00:00
size_t layer_nr_next = layer_nr ;
2023-03-14 09:04:42 +00:00
int layers_below ;
for ( layers_below = 0 ; layers_below < max_recursion_depth & & layer_nr_next > 0 ; layers_below + + ) { layer_nr_next = layer_heights [ layer_nr_next ] . next_layer_nr ; }
2022-12-07 12:32:33 +00:00
// Check if we would exceed the recursion limit by trying to process this layer
2023-03-14 09:04:42 +00:00
if ( layers_below > = max_recursion_depth & & m_avoidance_cache . find ( { radius , layer_nr_next } ) = = m_avoidance_cache . end ( ) ) {
2022-12-07 12:32:33 +00:00
// Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result.
2023-03-14 09:04:42 +00:00
get_avoidance ( radius , layer_nr_next , key . recursions + 1 ) ;
2022-12-07 12:32:33 +00:00
}
2023-01-05 12:25:55 +00:00
layer_nr_next = layer_heights [ layer_nr ] . next_layer_nr ;
2023-03-14 09:04:42 +00:00
ExPolygons avoidance_areas = std : : move ( offset_ex ( get_avoidance ( radius , layer_nr_next , key . recursions + 1 ) , scale_ ( - m_max_move ) ) ) ;
2022-12-07 12:32:33 +00:00
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 ) ) ;
2023-08-19 02:57:07 +00:00
auto ret = m_avoidance_cache . insert ( { key , std : : move ( avoidance_areas ) } ) ;
2022-12-23 07:19:57 +00:00
//assert(ret.second);
2023-05-04 03:19:37 +00:00
return ret . first - > second ;
2022-12-07 12:32:33 +00:00
} else {
2023-10-23 07:29:16 +00:00
BOOST_LOG_TRIVIAL ( debug ) < < " calculate_avoidance exceeds max_recursion_depth*2 on radius= " < < radius < < " , layer= " < < layer_nr < < " , recursion= " < < key . recursions ;
2023-09-23 08:31:49 +00:00
ExPolygons avoidance_areas = get_collision ( radius , layer_nr ) ; // std::move(offset_ex(m_layer_outlines_below[layer_nr], scale_(m_xy_distance + radius)));
2023-05-04 03:19:37 +00:00
auto ret = m_avoidance_cache . insert ( { key , std : : move ( avoidance_areas ) } ) ;
2022-12-07 12:32:33 +00:00
assert ( ret . second ) ;
2023-05-04 03:19:37 +00:00
return ret . first - > second ;
2022-07-15 15:37:19 +00:00
}
}
} //namespace Slic3r