mirror of
https://git.mirrors.martin98.com/https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-21 04:21:09 +08:00
1. first formal version of macos 2. add the bambu networking plugin install logic 3. auto compute the wipe volume when filament change 4. add the logic of wiping into support 5. refine the GUI layout and icons, improve the gui apperance in lots of small places 6. serveral improve to support 7. support AMS auto-mapping 8. disable lots of unstable features: such as params table, media file download, HMS 9. fix serveral kinds of bugs 10. update the document of building 11. ...
3058 lines
144 KiB
C++
3058 lines
144 KiB
C++
#include <math.h>
|
|
|
|
#include "MinimumSpanningTree.hpp"
|
|
#include "TreeSupport.hpp"
|
|
#include "Print.hpp"
|
|
#include "Layer.hpp"
|
|
#include "Fill/FillBase.hpp"
|
|
#include "CurveAnalyzer.hpp"
|
|
#include "SVG.hpp"
|
|
#include "ShortestPath.hpp"
|
|
#include "I18N.hpp"
|
|
#include <libnest2d/backends/libslic3r/geometries.hpp>
|
|
|
|
#define _L(s) Slic3r::I18N::translate(s)
|
|
|
|
|
|
#define MAX_BRANCH_RADIUS 10.0
|
|
#define HEIGHT_TO_SWITCH_INFILL_DIRECTION 30 // change infill direction every 20mm
|
|
#define DO_NOT_MOVER_UNDER_MM 5 // do not move contact points under 5mm
|
|
|
|
#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())
|
|
|
|
#define SUPPORT_TREE_DEBUG_TO_SVG
|
|
|
|
namespace Slic3r
|
|
{
|
|
#define unscale_(val) ((val) * SCALING_FACTOR)
|
|
#define FIRST_LAYER_EXPANSION 1.2
|
|
|
|
static constexpr float tree_support_branch_diameter_angle = 5.0;
|
|
|
|
inline unsigned int round_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer
|
|
{
|
|
return (dividend + divisor / 2) / divisor;
|
|
}
|
|
inline unsigned int round_up_divide(unsigned int dividend, unsigned int divisor) //!< Return dividend divided by divisor rounded to the nearest integer
|
|
{
|
|
return (dividend + divisor - 1) / divisor;
|
|
}
|
|
|
|
inline double dot_with_unscale(const Point a, const Point b)
|
|
{
|
|
return unscale_(a(0)) * unscale_(b(0)) + unscale_(a(1)) * unscale_(b(1));
|
|
}
|
|
|
|
inline double vsize2_with_unscale(const Point pt)
|
|
{
|
|
return dot_with_unscale(pt, pt);
|
|
}
|
|
|
|
inline Point turn90_ccw(const Point pt)
|
|
{
|
|
Point ret;
|
|
|
|
ret(0) = -pt(1);
|
|
ret(1) = pt(0);
|
|
return ret;
|
|
}
|
|
|
|
inline Point normal(Point pt, double scale)
|
|
{
|
|
double length = scale_(sqrt(vsize2_with_unscale(pt)));
|
|
|
|
return pt * (scale / length);
|
|
}
|
|
|
|
enum TreeSupportStage {
|
|
STAGE_DETECT_OVERHANGS,
|
|
STAGE_GENERATE_CONTACT_NODES,
|
|
STAGE_DROP_DOWN_NODES,
|
|
STAGE_DRAW_CIRCLES,
|
|
STAGE_GENERATE_TOOLPATHS,
|
|
STAGE_MinimumSpanningTree,
|
|
STAGE_GET_AVOIDANCE,
|
|
STAGE_projection_onto_ex,
|
|
STAGE_get_collision,
|
|
STAGE_intersection_ln,
|
|
STAGE_total,
|
|
NUM_STAGES
|
|
};
|
|
|
|
class TreeSupportProfiler
|
|
{
|
|
public:
|
|
uint32_t stage_durations[NUM_STAGES];
|
|
uint32_t stage_index = 0;
|
|
boost::posix_time::ptime tic_time;
|
|
boost::posix_time::ptime toc_time;
|
|
|
|
TreeSupportProfiler()
|
|
{
|
|
for (uint32_t& item : stage_durations) {
|
|
item = 0;
|
|
}
|
|
}
|
|
|
|
void stage_start(TreeSupportStage stage)
|
|
{
|
|
if (stage > NUM_STAGES)
|
|
return;
|
|
|
|
m_stage_start_times[stage] = boost::posix_time::microsec_clock::local_time();
|
|
}
|
|
|
|
void stage_finish(TreeSupportStage stage)
|
|
{
|
|
if (stage > NUM_STAGES)
|
|
return;
|
|
|
|
boost::posix_time::ptime time = boost::posix_time::microsec_clock::local_time();
|
|
stage_durations[stage] = (time - m_stage_start_times[stage]).total_milliseconds();
|
|
}
|
|
|
|
void tic() { tic_time = boost::posix_time::microsec_clock::local_time(); }
|
|
void toc() { toc_time = boost::posix_time::microsec_clock::local_time(); }
|
|
void stage_add(TreeSupportStage stage, bool do_toc = false)
|
|
{
|
|
if (stage > NUM_STAGES)
|
|
return;
|
|
if(do_toc)
|
|
toc_time = boost::posix_time::microsec_clock::local_time();
|
|
stage_durations[stage] += (toc_time - tic_time).total_milliseconds();
|
|
}
|
|
|
|
std::string report()
|
|
{
|
|
std::stringstream ss;
|
|
ss << "total overhange cost: " << stage_durations[STAGE_total]
|
|
<< "; STAGE_DETECT_OVERHANGS: " << stage_durations[STAGE_DETECT_OVERHANGS]
|
|
<< "; STAGE_GENERATE_CONTACT_NODES: " << stage_durations[STAGE_GENERATE_CONTACT_NODES]
|
|
<< "; STAGE_DROP_DOWN_NODES: " << stage_durations[STAGE_DROP_DOWN_NODES]
|
|
<< "; STAGE_DRAW_CIRCLES: " << stage_durations[STAGE_DRAW_CIRCLES]
|
|
<< "; STAGE_GENERATE_TOOLPATHS: " << stage_durations[STAGE_GENERATE_TOOLPATHS]
|
|
<< "; STAGE_MinimumSpanningTree: " << stage_durations[STAGE_MinimumSpanningTree]
|
|
<< "; STAGE_GET_AVOIDANCE: " << stage_durations[STAGE_GET_AVOIDANCE]
|
|
<< "; STAGE_projection_onto_ex: " << stage_durations[STAGE_projection_onto_ex]
|
|
<< "; STAGE_get_collision: " << stage_durations[STAGE_get_collision]
|
|
<< "; STAGE_intersection_ln: " << stage_durations[STAGE_intersection_ln];
|
|
|
|
return ss.str();
|
|
}
|
|
private:
|
|
boost::posix_time::ptime m_stage_start_times[NUM_STAGES];
|
|
};
|
|
TreeSupportProfiler profiler;
|
|
|
|
Lines spanning_tree_to_lines(const std::vector<MinimumSpanningTree>& spanning_trees)
|
|
{
|
|
Lines polylines;
|
|
for (const MinimumSpanningTree& mst : spanning_trees) {
|
|
std::vector<Point> points = mst.vertices();
|
|
std::unordered_set<Point, PointHash> to_ignore;
|
|
for (Point pt1 : points) {
|
|
if (to_ignore.find(pt1) != to_ignore.end())
|
|
continue;
|
|
|
|
const std::vector<Point>& neighbours = mst.adjacent_nodes(pt1);
|
|
if (neighbours.empty())
|
|
continue;
|
|
|
|
for (Point pt2 : neighbours) {
|
|
if (to_ignore.find(pt2) != to_ignore.end())
|
|
continue;
|
|
|
|
Line line(pt1, pt2);
|
|
polylines.push_back(line);
|
|
}
|
|
|
|
to_ignore.insert(pt1);
|
|
}
|
|
}
|
|
return polylines;
|
|
}
|
|
|
|
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
static std::string get_svg_filename(std::string layer_nr_or_z, std::string tag = "bbl_ts")
|
|
{
|
|
static bool rand_init = false;
|
|
|
|
if (!rand_init) {
|
|
srand(time(NULL));
|
|
rand_init = true;
|
|
}
|
|
|
|
int rand_num = rand() % 1000000;
|
|
//makedir("./SVG");
|
|
std::string prefix = "./SVG/";
|
|
std::string suffix = ".svg";
|
|
return prefix + tag + "_" + layer_nr_or_z /*+ "_" + std::to_string(rand_num)*/ + suffix;
|
|
}
|
|
|
|
static void draw_contours_and_nodes_to_svg
|
|
(
|
|
int layer_nr,
|
|
const ExPolygons &overhangs,
|
|
const ExPolygons &overhangs_after_offset,
|
|
const ExPolygons &outlines_below,
|
|
const std::vector<TreeSupport::Node*> &layer_nodes,
|
|
const std::vector<TreeSupport::Node*> &lower_layer_nodes,
|
|
std::string name_prefix,
|
|
std::vector<std::string> legends = { "overhang","avoid","outlines" }, std::vector<std::string> colors = { "blue","red","yellow" }
|
|
)
|
|
{
|
|
BoundingBox bbox = get_extents(overhangs);
|
|
bbox.merge(get_extents(overhangs_after_offset));
|
|
bbox.merge(get_extents(outlines_below));
|
|
Points layer_pts;
|
|
for (TreeSupport::Node* node : layer_nodes) {
|
|
layer_pts.push_back(node->position);
|
|
}
|
|
bbox.merge(get_extents(layer_pts));
|
|
bbox.inflated(scale_(1));
|
|
bbox.max.x() = std::max(bbox.max.x(), (coord_t)scale_(10));
|
|
bbox.max.y() = std::max(bbox.max.y(), (coord_t)scale_(10));
|
|
|
|
SVG svg;
|
|
if(layer_nr>=0)
|
|
svg.open(get_svg_filename(std::to_string(layer_nr), name_prefix), bbox);
|
|
else
|
|
svg.open(name_prefix, bbox);
|
|
if (!svg.is_opened()) return;
|
|
|
|
// draw grid
|
|
svg.draw_grid(bbox, "gray", coord_t(scale_(0.05)));
|
|
|
|
// draw overhang areas
|
|
svg.draw_outline(union_ex(overhangs), colors[0]);
|
|
svg.draw_outline(union_ex(overhangs_after_offset), colors[1]);
|
|
svg.draw_outline(outlines_below, colors[2]);
|
|
|
|
// draw legend
|
|
svg.draw_text(bbox.min + Point(scale_(0), scale_(0)), ("nPoints: "+std::to_string(layer_nodes.size())+"->").c_str(), "green", 4);
|
|
svg.draw_text(bbox.min + Point(scale_(15), scale_(0)), std::to_string(lower_layer_nodes.size()).c_str(), "black", 4);
|
|
svg.draw_text(bbox.min + Point(scale_(0), scale_(1)), legends[0].c_str(), colors[0].c_str(), 4);
|
|
svg.draw_text(bbox.min + Point(scale_(0), scale_(2)), legends[1].c_str(), colors[1].c_str(), 4);
|
|
svg.draw_text(bbox.min + Point(scale_(0), scale_(3)), legends[2].c_str(), colors[2].c_str(), 4);
|
|
|
|
// draw layer nodes
|
|
svg.draw(layer_pts, "green", coord_t(scale_(0.1)));
|
|
|
|
// lower layer points
|
|
layer_pts.clear();
|
|
for (TreeSupport::Node *node : lower_layer_nodes) {
|
|
layer_pts.push_back(node->position);
|
|
}
|
|
svg.draw(layer_pts, "black", coord_t(scale_(0.1)));
|
|
|
|
// higher layer points
|
|
layer_pts.clear();
|
|
for (TreeSupport::Node* node : layer_nodes) {
|
|
if(node->parent)
|
|
layer_pts.push_back(node->parent->position);
|
|
}
|
|
svg.draw(layer_pts, "blue", coord_t(scale_(0.1)));
|
|
}
|
|
|
|
static void draw_layer_mst
|
|
(
|
|
int layer_nr,
|
|
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);
|
|
}
|
|
|
|
SVG svg(get_svg_filename(std::to_string(layer_nr), "mstree").c_str(), bbox);
|
|
if (!svg.is_opened()) return;
|
|
|
|
svg.draw(lines, "blue", coord_t(scale_(0.05)));
|
|
svg.draw_outline(outline, "yellow");
|
|
}
|
|
#endif
|
|
|
|
// Move point from inside polygon if distance>0, outside if distance<0.
|
|
// Special case: distance=0 means find the nearest point of from on the polygon contour.
|
|
// The max move distance should not excceed max_move_distance.
|
|
static unsigned int move_inside_expoly(const ExPolygon &polygon, Point& from, double distance = 0, double max_move_distance = std::numeric_limits<double>::max())
|
|
{
|
|
//TODO: This is copied from the moveInside of Polygons.
|
|
/*
|
|
We'd like to use this function as subroutine in moveInside(Polygons...), but
|
|
then we'd need to recompute the distance of the point to the polygon, which
|
|
is expensive. Or we need to return the distance. We need the distance there
|
|
to compare with the distance to other polygons.
|
|
*/
|
|
Point ret = from;
|
|
double bestDist2 = std::numeric_limits<double>::max();
|
|
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
|
const Polygon &contour = polygon.contour;
|
|
|
|
if (contour.points.size() < 2)
|
|
{
|
|
return 0;
|
|
}
|
|
Point p0 = contour.points[polygon.contour.size() - 2];
|
|
Point p1 = contour.points.back();
|
|
// because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop
|
|
// to avoid integer rounding edge cases
|
|
bool projected_p_beyond_prev_segment = dot_with_unscale(p1 - p0, from - p0) >= vsize2_with_unscale(p1 - p0);
|
|
for(const Point& p2 : polygon.contour.points)
|
|
{
|
|
// X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A));
|
|
// = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A);
|
|
// X = P projected on AB
|
|
const Point& a = p1;
|
|
const Point& b = p2;
|
|
const Point& p = from;
|
|
Point ab = b - a;
|
|
Point ap = p - a;
|
|
double ab_length2 = vsize2_with_unscale(ab);
|
|
if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
|
|
{
|
|
p1 = p2; //Skip only one of the points.
|
|
continue;
|
|
}
|
|
double dot_prod = dot_with_unscale(ab, ap);
|
|
if (dot_prod <= 0) // x is projected to before ab
|
|
{
|
|
if (projected_p_beyond_prev_segment)
|
|
{ // case which looks like: > .
|
|
projected_p_beyond_prev_segment = false;
|
|
Point& x = p1;
|
|
|
|
double dist2 = vsize2_with_unscale(x - p);
|
|
if (dist2 < bestDist2)
|
|
{
|
|
bestDist2 = dist2;
|
|
if (distance == 0)
|
|
{
|
|
ret = x;
|
|
}
|
|
else
|
|
{
|
|
// TODO: check whether it needs scale_()
|
|
Point inward_dir = turn90_ccw(normal(ab, 10.0) + normal(p1 - p0, 10.0)); // inward direction irrespective of sign of [distance]
|
|
// MM2INT(10.0) to retain precision for the eventual normalization
|
|
ret = x + normal(inward_dir, scale_(distance));
|
|
is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) * distance >= 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
projected_p_beyond_prev_segment = false;
|
|
p0 = p1;
|
|
p1 = p2;
|
|
continue;
|
|
}
|
|
}
|
|
else if (dot_prod >= ab_length2) // x is projected to beyond ab
|
|
{
|
|
projected_p_beyond_prev_segment = true;
|
|
p0 = p1;
|
|
p1 = p2;
|
|
continue;
|
|
}
|
|
else
|
|
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
|
|
projected_p_beyond_prev_segment = false;
|
|
Point x = a + ab * (dot_prod / ab_length2);
|
|
|
|
double dist2 = vsize2_with_unscale(p - x);
|
|
if (dist2 < bestDist2)
|
|
{
|
|
bestDist2 = dist2;
|
|
if (distance == 0)
|
|
{
|
|
ret = x;
|
|
}
|
|
else
|
|
{
|
|
Point inward_dir = turn90_ccw(normal(ab, scale_(distance))); // inward or outward depending on the sign of [distance]
|
|
ret = x + inward_dir;
|
|
is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) >= 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
p0 = p1;
|
|
p1 = p2;
|
|
}
|
|
|
|
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
|
|
{
|
|
// BBS. Remove this condition.
|
|
if (bestDist2 < distance * distance)
|
|
{
|
|
from = ret;
|
|
}
|
|
}
|
|
else if (bestDist2 < max_move_distance * max_move_distance)
|
|
{
|
|
from = ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Implementation assumes moving inside, but moving outside should just as well be possible.
|
|
*/
|
|
static bool move_inside_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance)
|
|
{
|
|
Point from0 = from;
|
|
Point ret = from;
|
|
std::vector<Point> valid_pts;
|
|
double bestDist2 = std::numeric_limits<double>::max();
|
|
unsigned int bestPoly = NO_INDEX;
|
|
bool is_already_on_correct_side_of_boundary = false; // whether [from] is already on the right side of the boundary
|
|
Point inward_dir;
|
|
for (unsigned int poly_idx = 0; poly_idx < polygons.size(); poly_idx++)
|
|
{
|
|
const ExPolygon poly = polygons[poly_idx];
|
|
if (poly.contour.size() < 2)
|
|
continue;
|
|
Point p0 = poly.contour[poly.contour.size()-2];
|
|
Point p1 = poly.contour.points.back();
|
|
// because we compare with vsize2_with_unscale here (no division by zero), we also need to compare by vsize2_with_unscale inside the loop
|
|
// to avoid integer rounding edge cases
|
|
bool projected_p_beyond_prev_segment = dot_with_unscale(p1 - p0, from - p0) >= vsize2_with_unscale(p1 - p0);
|
|
for(const Point p2 : poly.contour.points)
|
|
{
|
|
// X = A + Normal(B-A) * (((B-A) dot_with_unscale (P-A)) / VSize(B-A));
|
|
// = A + (B-A) * ((B-A) dot_with_unscale (P-A)) / VSize2(B-A);
|
|
// X = P projected on AB
|
|
Point a = p1;
|
|
Point b = p2;
|
|
Point p = from;
|
|
Point ab = b - a;
|
|
Point ap = p - a;
|
|
double ab_length2 = vsize2_with_unscale(ab);
|
|
if(ab_length2 <= 0) //A = B, i.e. the input polygon had two adjacent points on top of each other.
|
|
{
|
|
p1 = p2; //Skip only one of the points.
|
|
continue;
|
|
}
|
|
double dot_prod = dot_with_unscale(ab, ap);
|
|
if (dot_prod <= 0) // x is projected to before ab
|
|
{
|
|
if (projected_p_beyond_prev_segment)
|
|
{ // case which looks like: > .
|
|
projected_p_beyond_prev_segment = false;
|
|
Point& x = p1;
|
|
|
|
double dist2 = vsize2_with_unscale(x - p);
|
|
if (dist2 < bestDist2)
|
|
{
|
|
bestDist2 = dist2;
|
|
bestPoly = poly_idx;
|
|
if (distance == 0) { ret = x; }
|
|
else
|
|
{
|
|
inward_dir = turn90_ccw(normal(ab, 10.0) + normal(p1 - p0, 10.0)); // inward direction irrespective of sign of [distance]
|
|
// MM2INT(10.0) to retain precision for the eventual normalization
|
|
ret = x + normal(inward_dir, scale_(distance));
|
|
is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) * distance >= 0;
|
|
if (is_already_on_correct_side_of_boundary && dist2 < distance * distance)
|
|
valid_pts.push_back(ret-from0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
projected_p_beyond_prev_segment = false;
|
|
p0 = p1;
|
|
p1 = p2;
|
|
continue;
|
|
}
|
|
}
|
|
else if (dot_prod >= ab_length2) // x is projected to beyond ab
|
|
{
|
|
projected_p_beyond_prev_segment = true;
|
|
p0 = p1;
|
|
p1 = p2;
|
|
continue;
|
|
}
|
|
else
|
|
{ // x is projected to a point properly on the line segment (not onto a vertex). The case which looks like | .
|
|
projected_p_beyond_prev_segment = false;
|
|
Point x = a + ab * (dot_prod / ab_length2);
|
|
|
|
double dist2 = vsize2_with_unscale(p - x);
|
|
if (dist2 < bestDist2)
|
|
{
|
|
bestDist2 = dist2;
|
|
bestPoly = poly_idx;
|
|
if (distance == 0) { ret = x; }
|
|
else
|
|
{
|
|
inward_dir = turn90_ccw(normal(ab, scale_(distance))); // inward or outward depending on the sign of [distance]
|
|
ret = x + inward_dir;
|
|
is_already_on_correct_side_of_boundary = dot_with_unscale(inward_dir, p - x) >= 0;
|
|
if (is_already_on_correct_side_of_boundary && dist2<distance*distance)
|
|
valid_pts.push_back(ret-from0);
|
|
}
|
|
}
|
|
}
|
|
p0 = p1;
|
|
p1 = p2;
|
|
}
|
|
}
|
|
|
|
//if (valid_pts.size() > 1) {
|
|
// std::sort(valid_pts.begin(), valid_pts.end());
|
|
// Point v_combine = valid_pts[0] + valid_pts[1];
|
|
// if(vsize2_with_unscale(v_combine)<distance*distance)
|
|
// v_combine = normal(v_combine, scale_(distance));
|
|
// ret = v_combine + from0;
|
|
//}
|
|
|
|
if (is_already_on_correct_side_of_boundary) // when the best point is already inside and we're moving inside, or when the best point is already outside and we're moving outside
|
|
{
|
|
if (bestDist2 < distance * distance)
|
|
{
|
|
from = ret;
|
|
}
|
|
return true;
|
|
}
|
|
else if (bestDist2 < max_move_distance * max_move_distance)
|
|
{
|
|
from = ret;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static Point find_closest_ex(Point from, const ExPolygons& polygons)
|
|
{
|
|
Point closest_pt;
|
|
double min_dist2 = std::numeric_limits<double>::max();
|
|
|
|
for (const ExPolygon &poly : polygons) {
|
|
for (int i = 0; i < poly.num_contours(); i++) {
|
|
const Point* candidate = poly.contour_or_hole(i).closest_point(from);
|
|
double dist2 = vsize2_with_unscale(*candidate - from);
|
|
if (dist2 < min_dist2) {
|
|
closest_pt = *candidate;
|
|
min_dist2 = dist2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return closest_pt;
|
|
}
|
|
|
|
static bool move_outside_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance)
|
|
{
|
|
return move_inside_expolys(polygons, from, -distance, -max_move_distance);
|
|
}
|
|
|
|
static bool is_inside_ex(const ExPolygon &polygon, const Point &pt)
|
|
{
|
|
if (!get_extents(polygon).contains(pt))
|
|
return false;
|
|
|
|
return polygon.contains(pt);
|
|
}
|
|
|
|
static bool is_inside_ex(const ExPolygons &polygons, const Point &pt)
|
|
{
|
|
for (const ExPolygon &poly : polygons) {
|
|
if (is_inside_ex(poly, pt))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
Point projection_onto_ex(const ExPolygons& polygons, Point from)
|
|
{
|
|
profiler.tic();
|
|
Point projected_pt;
|
|
double min_dist = std::numeric_limits<double>::max();
|
|
|
|
for (auto poly : polygons) {
|
|
for (int i = 0; i < poly.num_contours(); i++) {
|
|
Point p = from.projection_onto(poly.contour_or_hole(i));
|
|
double dist = (from - p).cast<double>().squaredNorm();
|
|
if (dist < min_dist) {
|
|
projected_pt = p;
|
|
min_dist = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
profiler.stage_add(STAGE_projection_onto_ex, true);
|
|
return projected_pt;
|
|
}
|
|
|
|
static bool move_out_expolys(const ExPolygons& polygons, Point& from, double distance, double max_move_distance)
|
|
{
|
|
Point from0 = from;
|
|
ExPolygons polys_dilated = union_ex(offset_ex(polygons, scale_(distance)));
|
|
Point pt = projection_onto_ex(polys_dilated, from);// find_closest_ex(from, polys_dilated);
|
|
Point outward_dir = pt - from;
|
|
Point pt_max = from + normal(outward_dir, scale_(max_move_distance));
|
|
double dist2 = vsize2_with_unscale(outward_dir);
|
|
if (dist2 > SQ(max_move_distance))
|
|
pt = pt_max;
|
|
// case 5: already outside and far enough, no need to move
|
|
if (!is_inside_ex(polys_dilated, from))
|
|
return true;
|
|
else if (!is_inside_ex(polygons, from)) {
|
|
// case 4: already outside but not far enough
|
|
from = pt;
|
|
return true;
|
|
}
|
|
else {
|
|
bool pt_max_in_poly = is_inside_ex(polygons, pt_max);
|
|
if (!pt_max_in_poly) {
|
|
from = pt_max;
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static Point bounding_box_middle(const BoundingBox &bbox)
|
|
{
|
|
return (bbox.max + bbox.min) / 2;
|
|
}
|
|
|
|
TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_params)
|
|
: m_object(&object), m_slicing_params(slicing_params), m_object_config(&object.config())
|
|
{
|
|
m_raft_layers = slicing_params.base_raft_layers + slicing_params.interface_raft_layers;
|
|
|
|
SupportMaterialPattern support_pattern = m_object_config->support_base_pattern;
|
|
m_support_params.base_fill_pattern = support_pattern == smpHoneycomb ? ipHoneycomb :
|
|
m_support_params.support_density > 0.95 || m_support_params.with_sheath ? ipRectilinear :
|
|
ipSupportBase;
|
|
m_support_params.interface_fill_pattern = (m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
|
m_support_params.contact_fill_pattern = (m_object_config->support_interface_pattern == smipAuto && m_slicing_params.soluble_interface) ||
|
|
m_object_config->support_interface_pattern == smipConcentric ?
|
|
ipConcentric :
|
|
(m_support_params.interface_density > 0.95 ? ipRectilinear : ipSupportBase);
|
|
}
|
|
|
|
|
|
#define SUPPORT_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
|
void TreeSupport::detect_object_overhangs()
|
|
{
|
|
// overhangs are already detected
|
|
if (m_object->tree_support_layer_count() >= m_object->layer_count())
|
|
return;
|
|
|
|
// Create Tree Support Layers
|
|
m_object->clear_tree_support_layers();
|
|
m_object->clear_tree_support_preview_cache();
|
|
|
|
create_tree_support_layers();
|
|
m_ts_data = m_object->alloc_tree_support_preview_cache();
|
|
|
|
const PrintObjectConfig& config = m_object->config();
|
|
SupportType stype = config.support_type.value;
|
|
const coordf_t radius_sample_resolution = m_ts_data->m_radius_sample_resolution;
|
|
const coordf_t extrusion_width = config.line_width.value;
|
|
const coordf_t extrusion_width_scaled = scale_(extrusion_width);
|
|
const coordf_t max_bridge_length = scale_(config.max_bridge_length.value);
|
|
const bool bridge_no_support = max_bridge_length > 0;// config.bridge_no_support.value;
|
|
const int enforce_support_layers = config.enforce_support_layers.value;
|
|
const double area_thresh_well_supported = SQ(scale_(6)); // min: 6x6=36mm^2
|
|
const double length_thresh_well_supported = scale_(6); // min: 6mm
|
|
static const double sharp_tail_max_support_height = 8.f;
|
|
// a region is considered well supported if the number of layers below it exceeds this threshold
|
|
const int thresh_layers_below = 10 / config.layer_height;
|
|
double obj_height = m_object->size().z();
|
|
bool is_auto = (stype == stTreeAuto || stype == stHybridAuto);
|
|
|
|
struct ExPolygonComp {
|
|
bool operator()(const ExPolygon& a, const ExPolygon& b) const {
|
|
return &a < &b;
|
|
}
|
|
};
|
|
// for small overhang removal
|
|
struct OverhangCluster {
|
|
std::map<int, const ExPolygon*> layer_overhangs;
|
|
ExPolygons merged_poly;
|
|
int min_layer = 1e7;
|
|
int max_layer = 0;
|
|
coordf_t offset = 0;
|
|
OverhangCluster(const ExPolygon* expoly, int layer_nr) {
|
|
push_back(expoly, layer_nr);
|
|
}
|
|
void push_back(const ExPolygon* expoly, int layer_nr) {
|
|
layer_overhangs.emplace(layer_nr, expoly);
|
|
auto dilate1 = offset_ex(*expoly, offset);
|
|
if (!dilate1.empty())
|
|
merged_poly = union_ex(merged_poly, dilate1);
|
|
min_layer = std::min(min_layer, layer_nr);
|
|
max_layer = std::max(max_layer, layer_nr);
|
|
}
|
|
int height() {
|
|
return max_layer - min_layer + 1;
|
|
}
|
|
bool intersects(const ExPolygon& region, int layer_nr, coordf_t offset) {
|
|
if (layer_nr < 1) return false;
|
|
auto it = layer_overhangs.find(layer_nr - 1);
|
|
if (it == layer_overhangs.end()) return false;
|
|
const ExPolygon* overhang = it->second;
|
|
|
|
this->offset = offset;
|
|
auto dilate1 = offset_ex(region, offset);
|
|
return !intersection_ex(*overhang, dilate1).empty();
|
|
}
|
|
};
|
|
std::vector<OverhangCluster> overhangClusters;
|
|
std::map<const ExPolygon*, int> overhang2clusterInd;
|
|
|
|
// for sharp tail detection
|
|
struct RegionCluster {
|
|
std::map<int, const ExPolygon*> layer_regions;
|
|
ExPolygon base;
|
|
BoundingBox bbox;
|
|
int min_layer = 1e7;
|
|
int max_layer = 0;
|
|
RegionCluster(const ExPolygon* expoly, int layer_nr) {
|
|
push_back(expoly, layer_nr);
|
|
}
|
|
void push_back(const ExPolygon* expoly, int layer_nr) {
|
|
if (layer_regions.empty()) {
|
|
base = *expoly;
|
|
bbox = get_extents(base);
|
|
}
|
|
layer_regions.emplace(layer_nr, expoly);
|
|
bbox.merge(get_extents(*expoly));
|
|
min_layer = std::min(min_layer, layer_nr);
|
|
max_layer = std::max(max_layer, layer_nr);
|
|
}
|
|
int height() {
|
|
return max_layer - min_layer + 1;
|
|
}
|
|
bool intersects(const ExPolygon& region, int layer_nr, coordf_t offset) {
|
|
if (layer_nr < 1) return false;
|
|
auto it = layer_regions.find(layer_nr - 1);
|
|
if (it == layer_regions.end()) return false;
|
|
const ExPolygon* region_lower = it->second;
|
|
return !intersection_ex(*region_lower, region).empty();
|
|
}
|
|
};
|
|
std::vector<RegionCluster> regionClusters;
|
|
std::map<const ExPolygon*, int> region2clusterInd;
|
|
auto find_and_insert_cluster = [](auto& regionClusters, auto& region2clusterInd, const ExPolygon& region, int layer_nr, coordf_t offset) {
|
|
bool found = false;
|
|
for (int i = 0; i < regionClusters.size();i++) {
|
|
auto& cluster = regionClusters[i];
|
|
if (cluster.intersects(region, layer_nr, offset)) {
|
|
cluster.push_back(®ion, layer_nr);
|
|
region2clusterInd.emplace(®ion, i);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
regionClusters.emplace_back(®ion, layer_nr);
|
|
region2clusterInd.emplace(®ion, regionClusters.size() - 1);
|
|
}
|
|
};
|
|
|
|
has_sharp_tail = false;
|
|
if (std::set<SupportType>{stTreeAuto, stHybridAuto, stTree}.count(stype))// == stTreeAuto || stype == stHybridAuto || stype == stTree)
|
|
{
|
|
double threshold_rad = (config.support_threshold_angle.value < EPSILON ? 30 : config.support_threshold_angle.value+1) * M_PI / 180.;
|
|
ExPolygons regions_well_supported; // regions on buildplate or well supported
|
|
std::map<ExPolygon, int, ExPolygonComp> region_layers_below; // regions and the number of layers below
|
|
ExPolygons lower_overhang_dilated; // for small overhang
|
|
|
|
for (size_t layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++)
|
|
{
|
|
if (m_object->print()->canceled())
|
|
break;
|
|
|
|
if (!is_auto && layer_nr > enforce_support_layers)
|
|
continue;
|
|
|
|
Layer* layer = m_object->get_layer(layer_nr);
|
|
for (auto& slice : layer->lslices)
|
|
find_and_insert_cluster(regionClusters, region2clusterInd, slice, layer_nr, 0);
|
|
|
|
if (layer->lower_layer == nullptr) {
|
|
for (auto& slice : layer->lslices) {
|
|
auto bbox_size = get_extents(slice).size();
|
|
if (slice.area() > area_thresh_well_supported
|
|
|| (bbox_size.x()>length_thresh_well_supported && bbox_size.y()>length_thresh_well_supported))
|
|
regions_well_supported.emplace_back(slice);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
Layer* lower_layer = layer->lower_layer;
|
|
coordf_t lower_layer_offset = layer_nr < enforce_support_layers ? -0.15 * extrusion_width : (float)lower_layer->height / tan(threshold_rad);
|
|
coordf_t support_offset_scaled = scale_(lower_layer_offset);
|
|
// Filter out areas whose diameter that is smaller than extrusion_width. Do not use offset2() for this purpose!
|
|
ExPolygons lower_polys;// = offset2_ex(lower_layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled / 2);
|
|
for (const ExPolygon& expoly : lower_layer->lslices) {
|
|
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
|
|
lower_polys.emplace_back(expoly);
|
|
}
|
|
}
|
|
ExPolygons curr_polys;// = offset2_ex(layer->lslices, -extrusion_width_scaled / 2, extrusion_width_scaled / 2);
|
|
for (const ExPolygon& expoly : layer->lslices) {
|
|
if (!offset_ex(expoly, -extrusion_width_scaled / 2).empty()) {
|
|
curr_polys.emplace_back(expoly);
|
|
}
|
|
}
|
|
|
|
// normal overhang
|
|
ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
|
ExPolygons overhang_areas = std::move(diff_ex(curr_polys, lower_layer_offseted));
|
|
//overhang_areas = std::move(offset2_ex(overhang_areas, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
|
overhang_areas.erase(std::remove_if(overhang_areas.begin(), overhang_areas.end(), [extrusion_width_scaled](ExPolygon& area) {return offset_ex(area, -0.1 * extrusion_width_scaled).empty(); }), overhang_areas.end());
|
|
|
|
ExPolygons overhangs_sharp_tail;
|
|
if (is_auto && g_config_support_sharp_tails)
|
|
{
|
|
#if 0
|
|
// detect sharp tail and add more supports around
|
|
for (auto& lower_region : lower_layer_offseted) {
|
|
auto radius = get_extents(lower_region).radius();
|
|
auto out_of_well_supported_region = offset_ex(diff_ex({ lower_region }, regions_well_supported), -extrusion_width_scaled);
|
|
auto bbox_size = get_extents(out_of_well_supported_region).size();
|
|
double area_inter = area(intersection_ex({ lower_region }, regions_well_supported));
|
|
if ((area_inter==0 ||
|
|
((bbox_size.x()> extrusion_width_scaled && bbox_size.y() > extrusion_width_scaled) && area_inter < area_thresh_well_supported ) )
|
|
/*&& (obj_height - scale_(layer->slice_z)) > get_extents(lower_region).radius() * 5*/) {
|
|
auto lower_region_unoffseted = offset_ex(lower_region, -support_offset_scaled);
|
|
if (!lower_region_unoffseted.empty())
|
|
lower_region = lower_region_unoffseted.front();
|
|
}
|
|
}
|
|
if (!lower_layer_offseted.empty()) {
|
|
overhangs_sharp_tail = std::move(diff_ex(curr_polys, lower_layer_offseted));
|
|
//overhangs_sharp_tail = std::move(offset2_ex(overhangs_sharp_tail, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
|
overhangs_sharp_tail = diff_ex(overhangs_sharp_tail, overhang_areas);
|
|
}
|
|
if (!overhangs_sharp_tail.empty()) {
|
|
has_sharp_tail = true;
|
|
append(layer->sharp_tails, overhangs_sharp_tail);
|
|
overhang_areas = union_ex(overhang_areas, overhangs_sharp_tail);
|
|
}
|
|
#else
|
|
// BBS
|
|
const ExPolygons& lower_layer_sharptails = lower_layer->sharp_tails;
|
|
auto& lower_layer_sharptails_height = lower_layer->sharp_tails_height;
|
|
for (ExPolygon& expoly : layer->lslices) {
|
|
bool is_sharp_tail = false;
|
|
float accum_height = layer->height;
|
|
do {
|
|
// 1. nothing below
|
|
// check whether this is a sharp tail region
|
|
if (intersection_ex({expoly}, lower_polys).empty()) {
|
|
is_sharp_tail = expoly.area() < area_thresh_well_supported;
|
|
break;
|
|
}
|
|
|
|
// 2. something below
|
|
// check whether this is above a sharp tail region.
|
|
|
|
// 2.1 If no sharp tail below, this is considered as common region.
|
|
ExPolygons supported_by_lower = intersection_ex({expoly}, lower_layer_sharptails);
|
|
if (supported_by_lower.empty()) {
|
|
is_sharp_tail = false;
|
|
break;
|
|
}
|
|
|
|
// 2.2 If sharp tail below, check whether it support this region enough.
|
|
float supported_area = area(supported_by_lower);
|
|
BoundingBox bbox = get_extents(supported_by_lower);
|
|
|
|
if (supported_area > area_thresh_well_supported) {
|
|
is_sharp_tail = false;
|
|
break;
|
|
}
|
|
|
|
if (bbox.size().x() > length_thresh_well_supported && bbox.size().y() > length_thresh_well_supported) {
|
|
is_sharp_tail = false;
|
|
break;
|
|
}
|
|
|
|
// 2.3 check whether sharp tail exceed the max height
|
|
for (auto &lower_sharp_tail_height : lower_layer_sharptails_height) {
|
|
if (!intersection_ex(*lower_sharp_tail_height.first, expoly).empty()) {
|
|
accum_height += lower_sharp_tail_height.second;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (accum_height >= sharp_tail_max_support_height) {
|
|
is_sharp_tail = false;
|
|
break;
|
|
}
|
|
|
|
// 2.4 if the area grows fast than threshold, it get connected to other part or
|
|
// it has a sharp slop and will be auto supported.
|
|
ExPolygons new_overhang_expolys = diff_ex({ expoly }, lower_layer_sharptails);
|
|
if (!offset_ex(new_overhang_expolys, -5.0 * extrusion_width_scaled).empty()) {
|
|
is_sharp_tail = false;
|
|
break;
|
|
}
|
|
|
|
// 2.5 mark the expoly as sharptail
|
|
is_sharp_tail = true;
|
|
} while (0);
|
|
|
|
if (is_sharp_tail) {
|
|
has_sharp_tail = true;
|
|
ExPolygons overhang = diff_ex({expoly}, lower_layer->lslices);
|
|
layer->sharp_tails.push_back(expoly);
|
|
layer->sharp_tails_height.insert({ &expoly, accum_height });
|
|
append(overhang_areas, overhang);
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
SVG svg(get_svg_filename(std::to_string(layer->print_z), "sharp_tail"), m_object->bounding_box());
|
|
if (svg.is_opened()) svg.draw(overhang, "yellow");
|
|
#endif
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
if (bridge_no_support && overhang_areas.size()>0) {
|
|
m_object->remove_bridges_from_contacts(lower_layer, layer, extrusion_width_scaled, &overhang_areas, max_bridge_length, true);
|
|
}
|
|
|
|
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
|
for (ExPolygon& poly : overhang_areas) {
|
|
// NOTE: must push something into ts_layer->overhang_areas, can't be empty for any layer,
|
|
// otherwise remove_small_overhangs can't correctly cluster overhangs
|
|
#if 0
|
|
ExPolygons poly_simp = poly.simplify(scale_(radius_sample_resolution));
|
|
// simplify method may delete the entire polygon which is unwanted
|
|
if(!poly_simp.empty())
|
|
append(ts_layer->overhang_areas, poly_simp);
|
|
else
|
|
ts_layer->overhang_areas.emplace_back(poly);
|
|
#else
|
|
if (!offset_ex(poly, -0.1 * extrusion_width_scaled).empty())
|
|
ts_layer->overhang_areas.emplace_back(poly);
|
|
#endif
|
|
}
|
|
|
|
if (is_auto && g_config_remove_small_overhangs) {
|
|
for (auto& overhang : ts_layer->overhang_areas) {
|
|
find_and_insert_cluster(overhangClusters, overhang2clusterInd, overhang, layer_nr, extrusion_width_scaled);
|
|
}
|
|
}
|
|
|
|
if (is_auto && /*g_config_support_sharp_tails*/0)
|
|
{ // update well supported regions
|
|
ExPolygons regions_well_supported2;
|
|
// regions intersects with lower regions_well_supported or large support are also well supported
|
|
auto inters = intersection_ex(layer->lslices, regions_well_supported);
|
|
auto inters2 = intersection_ex(layer->lslices, ts_layer->overhang_areas);
|
|
inters.insert(inters.end(), inters2.begin(), inters2.end());
|
|
for (auto inter : inters) {
|
|
auto bbox_size = get_extents(inter).size();
|
|
if (inter.area() >= area_thresh_well_supported
|
|
|| (bbox_size.x()>length_thresh_well_supported && bbox_size.y()>length_thresh_well_supported) )
|
|
{
|
|
auto tmp = offset_ex(inter, support_offset_scaled);
|
|
if (!tmp.empty()) {
|
|
// if inter is a single line with only 2 valid points, clipper will return empty
|
|
regions_well_supported2.emplace_back(std::move(tmp[0]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// experimental: regions high enough is also well supported
|
|
for (auto& region : layer->lslices) {
|
|
auto cluster = regionClusters[region2clusterInd[®ion]];
|
|
if (layer_nr - cluster.min_layer > thresh_layers_below)
|
|
regions_well_supported2.push_back(region);
|
|
}
|
|
|
|
regions_well_supported = union_ex(regions_well_supported2);
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
if (!regions_well_supported.empty()) {
|
|
SVG svg(get_svg_filename(std::to_string(layer->print_z), "regions_well_supported"), m_object->bounding_box());
|
|
if (svg.is_opened())
|
|
svg.draw(regions_well_supported, "yellow");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
auto enforcers = m_object->slice_support_enforcers();
|
|
auto blockers = m_object->slice_support_blockers();
|
|
m_object->project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers);
|
|
m_object->project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers);
|
|
|
|
if (is_auto && g_config_remove_small_overhangs) {
|
|
if (blockers.size() < m_object->layer_count())
|
|
blockers.resize(m_object->layer_count());
|
|
for (auto& cluster : overhangClusters) {
|
|
auto erode1 = offset_ex(cluster.merged_poly, -1.5*extrusion_width_scaled);
|
|
//DEBUG
|
|
bool save_poly = false;
|
|
if (save_poly) {
|
|
erode1 = offset_ex(cluster.merged_poly, -1*extrusion_width_scaled);
|
|
double a = area(erode1);
|
|
erode1[0].contour.remove_duplicate_points();
|
|
a = area(erode1);
|
|
auto tmp = offset_ex(erode1, extrusion_width_scaled);
|
|
SVG svg("SVG/merged_poly_"+std::to_string(cluster.min_layer)+".svg", m_object->bounding_box());
|
|
if (svg.is_opened()) {
|
|
svg.draw_outline(cluster.merged_poly, "yellow");
|
|
svg.draw_outline(erode1, "yellow");
|
|
}
|
|
}
|
|
|
|
// 1. check overhang span size is smaller than 3mm
|
|
//auto bbox_size = get_extents(cluster.merged_poly).size();
|
|
//const double dimension_limit = scale_(3.0) + 2 * extrusion_width_scaled;
|
|
//if (bbox_size.x() > dimension_limit || bbox_size.y() > dimension_limit)
|
|
// continue;
|
|
|
|
// 2. check overhang cluster size is smaller than 3.0 * fw_scaled
|
|
if (area(erode1) > SQ(scale_(0.1)))
|
|
continue;
|
|
|
|
// 3. check whether the small overhang is sharp tail
|
|
bool is_sharp_tail = false;
|
|
for (size_t layer_id = cluster.min_layer; layer_id < cluster.max_layer; layer_id++) {
|
|
Layer* layer = m_object->get_layer(layer_id);
|
|
if (!intersection_ex(layer->sharp_tails, cluster.merged_poly).empty()) {
|
|
is_sharp_tail = true;
|
|
break;
|
|
}
|
|
}
|
|
if (is_sharp_tail) continue;
|
|
|
|
// 4. check whether the overhang cluster is cantilever (far awary from main body)
|
|
Layer* layer = m_object->get_layer(cluster.min_layer);
|
|
if (layer->lower_layer == NULL) continue;
|
|
Layer* lower_layer = layer->lower_layer;
|
|
auto cluster_boundary = intersection(cluster.merged_poly, offset(lower_layer->lslices, scale_(0.5)));
|
|
double dist_max = 0;
|
|
Points cluster_pts;
|
|
for (auto& poly : cluster.merged_poly)
|
|
append(cluster_pts, poly.contour.points);
|
|
for (auto& pt : cluster_pts) {
|
|
double dist_pt = std::numeric_limits<double>::max();
|
|
for (auto& poly : cluster_boundary) {
|
|
double d = poly.distance_to(pt);
|
|
dist_pt = std::min(dist_pt, d);
|
|
}
|
|
dist_max = std::max(dist_max, dist_pt);
|
|
}
|
|
if (dist_max > scale_(5))
|
|
continue;
|
|
|
|
for (auto it = cluster.layer_overhangs.begin(); it != cluster.layer_overhangs.end(); it++) {
|
|
int layer_nr = it->first;
|
|
auto p_overhang = it->second;
|
|
blockers[layer_nr].push_back(p_overhang->contour);
|
|
// auto dilate1 = offset_ex(*p_overhang, extrusion_width_scaled);
|
|
// auto erode1 = offset_ex(*p_overhang, -extrusion_width_scaled);
|
|
// Layer* layer = m_object->get_layer(layer_nr);
|
|
// auto inter_with_others = intersection_ex(dilate1, diff_ex(layer->lslices, *p_overhang));
|
|
//// the following cases are small overhangs:
|
|
//// 1) overhang is single line (erode1.empty()==true)
|
|
//// 2) overhang is not island (intersects with others)
|
|
// if (erode1.empty() && !inter_with_others.empty())
|
|
// blockers[layer_nr].push_back(p_overhang->contour);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) {
|
|
if (m_object->print()->canceled())
|
|
break;
|
|
|
|
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
|
|
|
if (layer_nr < blockers.size()) {
|
|
Polygons& blocker = blockers[layer_nr];
|
|
ts_layer->overhang_areas = diff_ex(ts_layer->overhang_areas, offset_ex(blocker, scale_(radius_sample_resolution)));
|
|
}
|
|
|
|
for (auto &area : ts_layer->overhang_areas) {
|
|
ts_layer->overhang_types.emplace(&area, TreeSupportLayer::Detected);
|
|
}
|
|
|
|
if (layer_nr < enforcers.size()) {
|
|
Polygons& enforcer = enforcers[layer_nr];
|
|
// coconut: enforcer can't do offset2_ex, otherwise faces with angle near 90 degrees can't have enforcers, which
|
|
// is not good. For example: tails of animals needs extra support except the lowest tip.
|
|
//enforcer = std::move(offset2_ex(enforcer, -0.1 * extrusion_width_scaled, 0.1 * extrusion_width_scaled));
|
|
for (const Polygon& poly : enforcer) {
|
|
ts_layer->overhang_areas.emplace_back(poly);
|
|
ts_layer->overhang_types.emplace(&ts_layer->overhang_areas.back(), TreeSupportLayer::Enforced);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
for (const TreeSupportLayer* layer : m_object->tree_support_layers()) {
|
|
if (layer->overhang_areas.empty())
|
|
continue;
|
|
|
|
SVG svg(get_svg_filename(std::to_string(layer->print_z), "overhang_areas"), m_object->bounding_box());
|
|
if (svg.is_opened()) {
|
|
svg.draw_outline(m_object->get_layer(layer->id())->lslices, "yellow");
|
|
svg.draw(layer->overhang_areas, "red");
|
|
for (auto& overhang : layer->overhang_areas) {
|
|
double aarea = overhang.area()/ area_thresh_well_supported;
|
|
auto pt = get_extents(overhang).center();
|
|
char x[20]; sprintf(x, "%.2f", aarea);
|
|
svg.draw_text(pt, x, "red");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void TreeSupport::create_tree_support_layers()
|
|
{
|
|
int layer_id = 0;
|
|
coordf_t raft_print_z = 0.f;
|
|
coordf_t raft_slice_z = 0.f;
|
|
for (; layer_id < m_slicing_params.base_raft_layers; layer_id++) {
|
|
raft_print_z += m_slicing_params.base_raft_layer_height;
|
|
raft_slice_z = raft_print_z - m_slicing_params.base_raft_layer_height / 2;
|
|
m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z);
|
|
}
|
|
|
|
for (; layer_id < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers; layer_id++) {
|
|
raft_print_z += m_slicing_params.interface_raft_layer_height;
|
|
raft_slice_z = raft_print_z - m_slicing_params.interface_raft_layer_height / 2;
|
|
m_object->add_tree_support_layer(layer_id, m_slicing_params.base_raft_layer_height, raft_print_z, raft_slice_z);
|
|
}
|
|
|
|
for (Layer *layer : m_object->layers()) {
|
|
TreeSupportLayer* ts_layer = m_object->add_tree_support_layer(layer->id(), layer->height, layer->print_z, layer->slice_z);
|
|
if (ts_layer->id() > m_raft_layers) {
|
|
TreeSupportLayer* lower_layer = m_object->get_tree_support_layer(ts_layer->id() - 1);
|
|
lower_layer->upper_layer = ts_layer;
|
|
ts_layer->lower_layer = lower_layer;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline BoundingBox fill_expolygon_generate_paths(
|
|
ExtrusionEntitiesPtr &dst,
|
|
ExPolygon &&expolygon,
|
|
Fill *filler,
|
|
const FillParams &fill_params,
|
|
ExtrusionRole role,
|
|
const Flow &flow)
|
|
{
|
|
Surface surface(stInternal, std::move(expolygon));
|
|
Polylines polylines;
|
|
try {
|
|
polylines = filler->fill_surface(&surface, fill_params);
|
|
} catch (InfillFailedException &) {
|
|
}
|
|
|
|
BoundingBox fill_bbox;
|
|
if (!polylines.empty()) {
|
|
fill_bbox = polylines[0].bounding_box();
|
|
for (auto& polyline : polylines)
|
|
fill_bbox.merge(polyline.bounding_box());
|
|
}
|
|
|
|
extrusion_entities_append_paths(dst, std::move(polylines), role, flow.mm3_per_mm(), flow.width(), flow.height());
|
|
|
|
return fill_bbox;
|
|
}
|
|
|
|
static inline std::vector<BoundingBox> fill_expolygons_generate_paths(
|
|
ExtrusionEntitiesPtr &dst,
|
|
ExPolygons &&expolygons,
|
|
Fill *filler,
|
|
const FillParams &fill_params,
|
|
ExtrusionRole role,
|
|
const Flow &flow)
|
|
{
|
|
std::vector<BoundingBox> fill_boxes;
|
|
for (ExPolygon& expoly : expolygons) {
|
|
auto box = fill_expolygon_generate_paths(dst, std::move(expoly), filler, fill_params, role, flow);
|
|
fill_boxes.emplace_back(box);
|
|
}
|
|
return fill_boxes;
|
|
}
|
|
|
|
static void make_perimeter_and_inner_brim(ExtrusionEntitiesPtr &dst, const Print &print, const ExPolygon &support_area, size_t wall_count, const Flow &flow, bool is_interface)
|
|
{
|
|
Polygons loops;
|
|
ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare);
|
|
|
|
std::map<ExPolygon *, int> depth_per_expoly;
|
|
std::list<ExPolygon> expoly_list;
|
|
|
|
for (ExPolygon &expoly : support_area_new) {
|
|
expoly_list.emplace_back(std::move(expoly));
|
|
depth_per_expoly.insert({&expoly_list.back(), 0});
|
|
}
|
|
|
|
while (!expoly_list.empty()) {
|
|
polygons_append(loops, to_polygons(expoly_list.front()));
|
|
|
|
auto first_iter = expoly_list.begin();
|
|
auto depth_iter = depth_per_expoly.find(&expoly_list.front());
|
|
if (depth_iter->second + 1 < wall_count) {
|
|
// shrink and then dilate to prevent overlapping and overflow
|
|
ExPolygons expolys_new = offset_ex(expoly_list.front(), -1.4 * float(flow.scaled_spacing()), jtSquare);
|
|
expolys_new = offset_ex(expolys_new, .4*float(flow.scaled_spacing()), jtSquare);
|
|
|
|
for (ExPolygon &expoly : expolys_new) {
|
|
auto new_iter = expoly_list.insert(expoly_list.begin(), expoly);
|
|
depth_per_expoly.insert({ &*new_iter, depth_iter->second + 1 });
|
|
}
|
|
}
|
|
depth_per_expoly.erase(depth_iter);
|
|
expoly_list.erase(first_iter);
|
|
}
|
|
|
|
ExtrusionRole role = is_interface ? erSupportMaterialInterface : erSupportMaterial;
|
|
extrusion_entities_append_loops(dst, std::move(loops), role,
|
|
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
|
}
|
|
|
|
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)
|
|
{
|
|
Polygons loops;
|
|
ExPolygons support_area_new = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare);
|
|
|
|
// draw infill
|
|
FillParams fill_params;
|
|
fill_params.density = support_density;
|
|
fill_params.dont_adjust = true;
|
|
ExPolygons to_infill = offset_ex(support_area, -0.5f * float(flow.scaled_spacing()), jtSquare);
|
|
std::vector<BoundingBox> fill_boxes = fill_expolygons_generate_paths(dst, std::move(to_infill), filler_support, fill_params, role, flow);
|
|
|
|
// allow wall_count to be zero, which means only draw infill
|
|
if (wall_count == 0) {
|
|
for (auto fill_bbox : fill_boxes)
|
|
{
|
|
if (filler_support->angle == 0) {
|
|
fill_bbox.min[0] -= scale_(10);
|
|
fill_bbox.max[0] += scale_(10);
|
|
}
|
|
else {
|
|
fill_bbox.min[1] -= scale_(10);
|
|
fill_bbox.max[1] += scale_(10);
|
|
}
|
|
support_area_new = diff_ex(support_area_new, offset_ex(to_expolygons({ fill_bbox.polygon() }), 0.5*flow.scaled_width()));
|
|
}
|
|
// filter out small areas
|
|
for (auto it = support_area_new.begin(); it != support_area_new.end(); ) {
|
|
if (offset_ex(*it, -flow.scaled_width()).empty())
|
|
it = support_area_new.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
|
|
{
|
|
std::map<ExPolygon*, int> depth_per_expoly;
|
|
std::list<ExPolygon> expoly_list;
|
|
|
|
for (ExPolygon& expoly : support_area_new) {
|
|
expoly_list.emplace_back(std::move(expoly));
|
|
depth_per_expoly.insert({ &expoly_list.back(), 0 });
|
|
}
|
|
|
|
while (!expoly_list.empty()) {
|
|
polygons_append(loops, to_polygons(expoly_list.front()));
|
|
|
|
auto first_iter = expoly_list.begin();
|
|
auto depth_iter = depth_per_expoly.find(&expoly_list.front());
|
|
if (depth_iter->second + 1 < wall_count) {
|
|
ExPolygons expolys_new = offset_ex(expoly_list.front(), -float(flow.scaled_spacing()), jtSquare);
|
|
|
|
for (ExPolygon& expoly : expolys_new) {
|
|
auto new_iter = expoly_list.insert(expoly_list.begin(), expoly);
|
|
depth_per_expoly.insert({ &*new_iter, depth_iter->second + 1 });
|
|
}
|
|
}
|
|
depth_per_expoly.erase(depth_iter);
|
|
expoly_list.erase(first_iter);
|
|
}
|
|
|
|
extrusion_entities_append_loops(dst, std::move(loops), role,
|
|
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
void TreeSupport::generate_toolpaths()
|
|
{
|
|
const PrintConfig &print_config = m_object->print()->config();
|
|
const PrintObjectConfig &object_config = m_object->config();
|
|
coordf_t support_extrusion_width = object_config.support_line_width.value > 0 ? object_config.support_line_width : object_config.line_width;
|
|
coordf_t nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_filament - 1);
|
|
|
|
const size_t wall_count = object_config.tree_support_wall_count.value;
|
|
const bool with_infill = object_config.tree_support_with_infill.value;
|
|
const bool contact_loops = object_config.support_interface_loop_pattern.value;
|
|
auto m_support_material_flow = support_material_flow(m_object, float(m_slicing_params.layer_height));
|
|
|
|
// 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);
|
|
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing();
|
|
coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
|
|
|
|
const coordf_t branch_radius = object_config.tree_support_branch_diameter.value / 2;
|
|
const coordf_t branch_radius_scaled = scale_(branch_radius);
|
|
|
|
if (m_object->tree_support_layers().empty())
|
|
return;
|
|
|
|
// calculate fill areas for raft layers
|
|
ExPolygons raft_areas;
|
|
if (m_object->layer_count() > 0) {
|
|
const Layer *layer = m_object->layers().front();
|
|
for (const ExPolygon &expoly : layer->lslices) {
|
|
raft_areas.push_back(expoly);
|
|
}
|
|
}
|
|
|
|
if (m_object->tree_support_layer_count() > m_raft_layers) {
|
|
const TreeSupportLayer *ts_layer = m_object->get_tree_support_layer(m_raft_layers);
|
|
for (const ExPolygon expoly : ts_layer->floor_areas)
|
|
raft_areas.push_back(expoly);
|
|
for (const ExPolygon expoly : ts_layer->roof_areas)
|
|
raft_areas.push_back(expoly);
|
|
for (const ExPolygon expoly : ts_layer->base_areas)
|
|
raft_areas.push_back(expoly);
|
|
}
|
|
|
|
raft_areas = std::move(offset_ex(raft_areas, scale_(3.)));
|
|
|
|
// generate raft tool path
|
|
if (m_raft_layers > 0)
|
|
{
|
|
ExtrusionRole raft_contour_er = m_slicing_params.base_raft_layers > 0 ? erSupportMaterial : erSupportMaterialInterface;
|
|
TreeSupportLayer *ts_layer = m_object->tree_support_layers().front();
|
|
Flow flow = m_object->print()->brim_flow();
|
|
|
|
Polygons loops;
|
|
for (const ExPolygon& expoly : raft_areas) {
|
|
loops.push_back(expoly.contour);
|
|
loops.insert(loops.end(), expoly.holes.begin(), expoly.holes.end());
|
|
}
|
|
extrusion_entities_append_loops(ts_layer->support_fills.entities, std::move(loops), raft_contour_er,
|
|
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
|
|
raft_areas = offset_ex(raft_areas, -flow.scaled_spacing() / 2.);
|
|
}
|
|
|
|
for (size_t layer_nr = 0; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) {
|
|
TreeSupportLayer *ts_layer = m_object->get_tree_support_layer(layer_nr);
|
|
coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.);
|
|
|
|
Flow support_flow = layer_nr == 0 ? m_object->print()->brim_flow() : Flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
|
|
Fill* filler_interface = Fill::new_from_type(ipRectilinear);
|
|
filler_interface->angle = layer_nr == 0 ? 90 : 0;
|
|
filler_interface->spacing = support_extrusion_width;
|
|
|
|
FillParams fill_params;
|
|
fill_params.density = interface_density;
|
|
fill_params.dont_adjust = true;
|
|
|
|
fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))),
|
|
filler_interface, fill_params, erSupportMaterial, support_flow);
|
|
}
|
|
|
|
for (size_t layer_nr = m_slicing_params.base_raft_layers;
|
|
layer_nr < m_slicing_params.base_raft_layers + m_slicing_params.interface_raft_layers;
|
|
layer_nr++)
|
|
{
|
|
TreeSupportLayer *ts_layer = m_object->get_tree_support_layer(layer_nr);
|
|
coordf_t expand_offset = (layer_nr == 0 ? 0. : -1.);
|
|
|
|
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
|
|
Fill* filler_interface = Fill::new_from_type(ipRectilinear);
|
|
filler_interface->angle = 0;
|
|
filler_interface->spacing = support_extrusion_width;
|
|
|
|
FillParams fill_params;
|
|
fill_params.density = interface_density;
|
|
fill_params.dont_adjust = true;
|
|
|
|
fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(offset_ex(raft_areas, scale_(expand_offset))),
|
|
filler_interface, fill_params, erSupportMaterialInterface, support_flow);
|
|
}
|
|
|
|
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
|
|
auto obj_size = m_object->size();
|
|
bool obj_is_vertical = obj_size.x() < obj_size.y();
|
|
int num_layers_to_change_infill_direction = int(HEIGHT_TO_SWITCH_INFILL_DIRECTION / object_config.layer_height.value); // change direction every 30mm
|
|
|
|
// generate tree support tool paths
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(m_raft_layers, m_object->tree_support_layer_count()),
|
|
[&](const tbb::blocked_range<size_t>& range)
|
|
{
|
|
for (size_t layer_id = range.begin(); layer_id < range.end(); layer_id++) {
|
|
if (m_object->print()->canceled())
|
|
break;
|
|
|
|
m_object->print()->set_status(70, (boost::format(_L("Support: generate toolpath at layer %d")) % layer_id).str());
|
|
|
|
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_id);
|
|
Flow support_flow(support_extrusion_width, ts_layer->height, nozzle_diameter);
|
|
std::unique_ptr<Fill> filler_interface = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.contact_fill_pattern));
|
|
std::unique_ptr<Fill> filler_support = std::unique_ptr<Fill>(Fill::new_from_type(m_support_params.base_fill_pattern));
|
|
filler_interface->set_bounding_box(bbox_object);
|
|
filler_support->set_bounding_box(bbox_object);
|
|
|
|
filler_interface->angle = Geometry::deg2rad(object_config.support_angle.value + 90.);//(1 - obj_is_vertical) * M_PI_2;//((1-obj_is_vertical) + int(layer_id / num_layers_to_change_infill_direction)) * M_PI_2;;//layer_id % 2 ? 0 : M_PI_2;
|
|
|
|
for (auto& area_group : ts_layer->area_groups) {
|
|
ExPolygon& poly = *area_group.first;
|
|
ExPolygons polys;
|
|
FillParams fill_params;
|
|
if (area_group.second != TreeSupportLayer::BaseType) {
|
|
// interface
|
|
if (layer_id == 0) {
|
|
Flow flow = m_raft_layers == 0 ? m_object->print()->brim_flow() : support_flow;
|
|
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, *m_object->print(), poly, wall_count, flow,
|
|
area_group.second == TreeSupportLayer::RoofType);
|
|
polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
|
|
} else if (area_group.second == TreeSupportLayer::Roof1stLayer) {
|
|
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;
|
|
}
|
|
if (area_group.second == TreeSupportLayer::Roof1stLayer) {
|
|
// roof_1st_layer
|
|
fill_params.density = interface_density;
|
|
// Note: spacing means the separation between two lines as if they are tightly extruded
|
|
filler_interface->spacing = m_support_material_interface_flow.spacing();
|
|
fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys), filler_interface.get(), fill_params, erSupportMaterial,
|
|
m_support_material_interface_flow);
|
|
} else if (area_group.second == TreeSupportLayer::FloorType) {
|
|
// floor_areas
|
|
fill_params.density = bottom_interface_density;
|
|
filler_interface->spacing = m_support_material_interface_flow.spacing();
|
|
fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys),
|
|
filler_interface.get(), fill_params, erSupportMaterialInterface, m_support_material_interface_flow);
|
|
} else if (area_group.second == TreeSupportLayer::RoofType) {
|
|
// roof_areas
|
|
fill_params.density = interface_density;
|
|
filler_interface->spacing = m_support_material_interface_flow.spacing();
|
|
/*if (contact_loops) {
|
|
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, *m_object->print(), poly,
|
|
std::numeric_limits<size_t>::max(), m_support_material_interface_flow, true);
|
|
}
|
|
else*/ {
|
|
fill_expolygons_generate_paths(ts_layer->support_fills.entities, std::move(polys),
|
|
filler_interface.get(), fill_params, erSupportMaterialInterface, m_support_material_interface_flow);
|
|
}
|
|
}
|
|
else {
|
|
// base_areas
|
|
filler_support->spacing = m_support_material_flow.spacing();
|
|
ExtrusionRole role;
|
|
Flow flow = (layer_id == 0 && m_raft_layers == 0) ? m_object->print()->brim_flow() :
|
|
(m_support_params.base_fill_pattern == ipRectilinear && (layer_id % num_layers_to_change_infill_direction == 0) ? support_transition_flow(m_object) : support_flow);
|
|
if (with_infill && layer_id > 0) {
|
|
if (m_support_params.base_fill_pattern == ipRectilinear) {
|
|
role = erSupportMaterial;// layer_id% num_layers_to_change_infill_direction == 0 ? erSupportTransition : erSupportMaterial;
|
|
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);// obj_is_vertical* M_PI_2;// (obj_is_vertical + int(layer_id / num_layers_to_change_infill_direction))* M_PI_2;
|
|
}
|
|
else {
|
|
role = erSupportMaterial;
|
|
filler_support->angle = Geometry::deg2rad(object_config.support_angle.value);// obj_is_vertical * M_PI_2 + (float)layer_id / num_layers_to_change_infill_direction * M_PI_4;
|
|
}
|
|
// only wall at the top of tree branch
|
|
if (offset(poly, -branch_radius_scaled*1.5).empty())
|
|
{
|
|
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, *m_object->print(), poly,
|
|
wall_count, flow, false);
|
|
}
|
|
// allow infill-only mode if support is thick enough
|
|
else if (offset(poly, -scale_(support_spacing * 1.5)).empty() == false)
|
|
{
|
|
make_perimeter_and_infill(ts_layer->support_fills.entities, *m_object->print(), poly, wall_count, flow, role, filler_support.get(), support_density);
|
|
}
|
|
else { // otherwise must draw 1 wall
|
|
//if (m_support_params.base_fill_pattern == ipRectilinear)
|
|
make_perimeter_and_infill(ts_layer->support_fills.entities, *m_object->print(), poly, 1, flow, role, filler_support.get(), support_density);
|
|
//else
|
|
// make_perimeter_and_inner_brim(ts_layer->support_fills.entities, *m_object->print(), poly, 1, flow, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, *m_object->print(), poly,
|
|
layer_id > 0 ? wall_count : std::numeric_limits<size_t>::max(), flow, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// sort extrusions to reduce travel, also make sure walls go before infills
|
|
chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
Polygons TreeSupport::spanning_tree_to_polygon(const std::vector<MinimumSpanningTree>& spanning_trees, Polygons layer_contours, int layer_nr)
|
|
{
|
|
Polygons polys;
|
|
auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
|
|
for (MinimumSpanningTree mst : spanning_trees) {
|
|
std::vector<Point> points = mst.vertices();
|
|
if (points.size() == 0)
|
|
continue;
|
|
std::map<Point, bool> visited;
|
|
for (int i=0;i<points.size();i++)
|
|
visited.emplace(points[i],false);
|
|
|
|
std::unordered_set<Line, LineHash> to_ignore;
|
|
for (int i = 0; i < points.size(); i++) {
|
|
if (visited[points[i]] == true)
|
|
continue;
|
|
|
|
Polygon poly;
|
|
bool has_next = true;
|
|
Point pt1 = points[i];
|
|
poly.points.push_back(pt1);
|
|
visited[pt1] = true;
|
|
|
|
while (has_next) {
|
|
const std::vector<Point>& neighbours = mst.adjacent_nodes(pt1);
|
|
if (neighbours.empty())
|
|
{
|
|
break;
|
|
}
|
|
|
|
double min_ccw = std::numeric_limits<double>::max();
|
|
Point pt_selected = neighbours[0];
|
|
has_next = false;
|
|
for (Point pt2 : neighbours) {
|
|
if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) {
|
|
auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 });
|
|
if (iter != mst_line_x_layer_contour_cache.end()) {
|
|
if (iter->second)
|
|
continue;
|
|
}
|
|
else {
|
|
Polylines pls;
|
|
pls.emplace_back(pt1, pt2);
|
|
Polylines pls_intersect = intersection_pl(pls, layer_contours);
|
|
mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() });
|
|
mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() });
|
|
if (!pls_intersect.empty())
|
|
continue;
|
|
}
|
|
|
|
if (poly.points.size() < 2 || visited[pt2]==false)
|
|
{
|
|
pt_selected = pt2;
|
|
has_next = true;
|
|
break;
|
|
}
|
|
double curr_ccw = pt2.ccw(pt1, poly.points.back());
|
|
if (curr_ccw < min_ccw)
|
|
{
|
|
min_ccw = curr_ccw;
|
|
pt_selected = pt2;
|
|
has_next = true;
|
|
}
|
|
}
|
|
}
|
|
if (has_next) {
|
|
poly.points.push_back(pt_selected);
|
|
to_ignore.insert(Line(pt1, pt_selected));
|
|
visited[pt_selected] = true;
|
|
pt1 = pt_selected;
|
|
}
|
|
}
|
|
polys.emplace_back(std::move(poly));
|
|
}
|
|
}
|
|
return polys;
|
|
}
|
|
|
|
Polygons TreeSupport::contact_nodes_to_polygon(const std::vector<Node*>& contact_nodes, Polygons layer_contours, int layer_nr, std::vector<double>& radiis, std::vector<bool>& is_interface)
|
|
{
|
|
Polygons polys;
|
|
std::vector<MinimumSpanningTree> spanning_trees;
|
|
std::vector<double> radiis_mtree;
|
|
std::vector<bool> is_interface_mtree;
|
|
// generate minimum spanning trees
|
|
{
|
|
std::map<Node*, bool> visited;
|
|
for (int i = 0; i < contact_nodes.size(); i++)
|
|
visited.emplace(contact_nodes[i], false);
|
|
std::unordered_set<Line, LineHash> to_ignore;
|
|
|
|
// generate minimum spaning trees
|
|
for (int i = 0; i < contact_nodes.size(); i++) {
|
|
Node* node = contact_nodes[i];
|
|
if (visited[node])
|
|
continue;
|
|
|
|
std::vector<Point> points_to_mstree;
|
|
double radius = 0;
|
|
Point pt1 = node->position;
|
|
points_to_mstree.push_back(pt1);
|
|
visited[node] = true;
|
|
radius += node->radius;
|
|
|
|
for (int j = i + 1; j < contact_nodes.size(); j++) {
|
|
Node* node2 = contact_nodes[j];
|
|
Point pt2 = node2->position;
|
|
// connect to this neighbor if:
|
|
// 1) both are interface or both are not
|
|
// 3) not readly added
|
|
// 4) won't cross perimeters: this is not right since we need to check all possible connections
|
|
if ((node->support_roof_layers_below > 0) == (node2->support_roof_layers_below > 0)
|
|
&& to_ignore.find(Line(pt1, pt2)) == to_ignore.end())
|
|
{
|
|
points_to_mstree.emplace_back(pt2);
|
|
visited[node2] = true;
|
|
radius += node2->radius;
|
|
}
|
|
}
|
|
|
|
spanning_trees.emplace_back(points_to_mstree);
|
|
radiis_mtree.push_back(radius / points_to_mstree.size());
|
|
is_interface_mtree.push_back(node->support_roof_layers_below > 0);
|
|
}
|
|
}
|
|
auto lines = spanning_tree_to_lines(spanning_trees);
|
|
#if 1
|
|
// convert mtree to polygon
|
|
for (int k = 0; k < spanning_trees.size(); k++) {
|
|
auto& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
|
|
MinimumSpanningTree mst = spanning_trees[k];
|
|
std::vector<Point> points = mst.vertices();
|
|
std::map<Point, bool> visited;
|
|
for (int i = 0; i < points.size(); i++)
|
|
visited.emplace(points[i], false);
|
|
|
|
std::unordered_set<Line, LineHash> to_ignore;
|
|
for (int i = 0; i < points.size(); i++) {
|
|
if (visited[points[i]])
|
|
continue;
|
|
|
|
Polygon poly;
|
|
Point pt1 = points[i];
|
|
poly.points.push_back(pt1);
|
|
visited[pt1] = true;
|
|
|
|
bool has_next = true;
|
|
while (has_next)
|
|
{
|
|
const std::vector<Point>& neighbours = mst.adjacent_nodes(pt1);
|
|
double min_ccw = -std::numeric_limits<double>::max();
|
|
Point pt_selected;
|
|
has_next = false;
|
|
for (Point pt2 : neighbours) {
|
|
if (to_ignore.find(Line(pt1, pt2)) == to_ignore.end()) {
|
|
auto iter = mst_line_x_layer_contour_cache.find({ pt1,pt2 });
|
|
if (iter != mst_line_x_layer_contour_cache.end()) {
|
|
if (iter->second)
|
|
continue;
|
|
}
|
|
else {
|
|
Polylines pls;
|
|
pls.emplace_back(pt1, pt2);
|
|
Polylines pls_intersect = intersection_pl(pls, layer_contours);
|
|
mst_line_x_layer_contour_cache.insert({ {pt1, pt2}, !pls_intersect.empty() });
|
|
mst_line_x_layer_contour_cache.insert({ {pt2, pt1}, !pls_intersect.empty() });
|
|
if (!pls_intersect.empty())
|
|
continue;
|
|
}
|
|
if (poly.points.size() < 2)
|
|
{
|
|
pt_selected = pt2;
|
|
has_next = true;
|
|
break;
|
|
}
|
|
double curr_ccw = pt2.ccw(pt1, poly.points.rbegin()[1]);
|
|
if (curr_ccw > min_ccw)
|
|
{
|
|
has_next = true;
|
|
min_ccw = curr_ccw;
|
|
pt_selected = pt2;
|
|
}
|
|
}
|
|
}
|
|
if (!has_next)
|
|
break;
|
|
|
|
poly.points.push_back(pt_selected);
|
|
to_ignore.insert(Line(pt1, pt_selected));
|
|
visited[pt_selected] = true;
|
|
pt1 = pt_selected;
|
|
}
|
|
polys.emplace_back(std::move(poly));
|
|
radiis.push_back(radiis_mtree[k]);
|
|
is_interface.push_back(is_interface_mtree[k]);
|
|
}
|
|
}
|
|
#else
|
|
polys = spanning_tree_to_polygon(spanning_trees, layer_contours, layer_nr, radiis);
|
|
#endif
|
|
return polys;
|
|
}
|
|
|
|
|
|
void TreeSupport::generate_support_areas()
|
|
{
|
|
const PrintObjectConfig &config = m_object->config();
|
|
bool tree_support_enable = config.enable_support.value &&
|
|
(config.support_type.value == stTreeAuto || config.support_type.value == stTree || config.support_type.value == stHybridAuto);
|
|
if (!tree_support_enable)
|
|
return;
|
|
|
|
std::vector<std::vector<Node*>> contact_nodes(m_object->layers().size()); //Generate empty layers to store the points in.
|
|
|
|
profiler.stage_start(STAGE_total);
|
|
|
|
// Generate overhang areas
|
|
profiler.stage_start(STAGE_DETECT_OVERHANGS);
|
|
m_object->print()->set_status(55, _L("Support: detect overhangs"));
|
|
detect_object_overhangs();
|
|
profiler.stage_finish(STAGE_DETECT_OVERHANGS);
|
|
|
|
// Generate contact points of tree support
|
|
profiler.stage_start(STAGE_GENERATE_CONTACT_NODES);
|
|
m_object->print()->set_status(56, _L("Support: generate contact points"));
|
|
generate_contact_points(contact_nodes);
|
|
profiler.stage_finish(STAGE_GENERATE_CONTACT_NODES);
|
|
|
|
//Drop nodes to lower layers.
|
|
profiler.stage_start(STAGE_DROP_DOWN_NODES);
|
|
m_object->print()->set_status(60, _L("Support: propagate branches"));
|
|
drop_nodes(contact_nodes);
|
|
profiler.stage_finish(STAGE_DROP_DOWN_NODES);
|
|
|
|
// Adjust support layer heights
|
|
adjust_layer_heights(contact_nodes);
|
|
|
|
//Generate support areas.
|
|
profiler.stage_start(STAGE_DRAW_CIRCLES);
|
|
m_object->print()->set_status(65, _L("Support: draw polygons"));
|
|
draw_circles(contact_nodes);
|
|
profiler.stage_finish(STAGE_DRAW_CIRCLES);
|
|
|
|
for (auto& layer : contact_nodes)
|
|
{
|
|
for (Node* p_node : layer)
|
|
{
|
|
delete p_node;
|
|
}
|
|
layer.clear();
|
|
}
|
|
contact_nodes.clear();
|
|
|
|
profiler.stage_start(STAGE_GENERATE_TOOLPATHS);
|
|
m_object->print()->set_status(69, _L("Support: generate toolpath"));
|
|
generate_toolpaths();
|
|
profiler.stage_finish(STAGE_GENERATE_TOOLPATHS);
|
|
|
|
profiler.stage_finish(STAGE_total);
|
|
BOOST_LOG_TRIVIAL(debug) << "tree support time " << profiler.report();
|
|
}
|
|
|
|
inline coordf_t calc_branch_radius(coordf_t base_radius, size_t layers_to_top, size_t tip_layers, double diameter_angle_scale_factor)
|
|
{
|
|
double radius;
|
|
#if 1
|
|
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
|
|
double scale = static_cast<double>(layers_to_top + 1) / tip_layers;
|
|
scale = layers_to_top < tip_layers ? (0.5 + scale / 2) : (1 + static_cast<double>(layers_to_top - tip_layers) * diameter_angle_scale_factor);
|
|
radius = scale * base_radius;
|
|
#endif
|
|
radius = std::min(radius, MAX_BRANCH_RADIUS);
|
|
return radius;
|
|
}
|
|
|
|
void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_nodes)
|
|
{
|
|
const PrintObjectConfig &config = m_object->config();
|
|
bool has_brim = m_object->print()->has_brim();
|
|
bool has_infill = config.tree_support_with_infill.value;
|
|
int bottom_gap_layers = round(m_slicing_params.gap_object_support / m_slicing_params.layer_height);
|
|
const coordf_t branch_radius = config.tree_support_branch_diameter.value / 2;
|
|
const coordf_t branch_radius_scaled = scale_(branch_radius);
|
|
Polygon branch_circle; //Pre-generate a circle with correct diameter so that we don't have to recompute those (co)sines every time.
|
|
|
|
// Use square support if there are too many nodes per layer because circle support needs much longer time to compute
|
|
// Hower circle support can be printed faster, so we prefer circle for fewer nodes case.
|
|
const bool SQUARE_SUPPORT = avg_node_per_layer > 200;
|
|
const int CIRCLE_RESOLUTION = SQUARE_SUPPORT ? 4 : 100; // The number of vertices in each circle.
|
|
|
|
|
|
for (unsigned int i = 0; i < CIRCLE_RESOLUTION; i++)
|
|
{
|
|
double angle;
|
|
if (SQUARE_SUPPORT)
|
|
angle = (double) i / CIRCLE_RESOLUTION * TAU + PI / 4.0 + nodes_angle;
|
|
else
|
|
angle = (double) i / CIRCLE_RESOLUTION * TAU;
|
|
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 Print* print = m_object->print();
|
|
const PrintConfig& print_config = print->config();
|
|
for (const PrintObject* object : print->objects())
|
|
{
|
|
size_t skirt_layers = print->has_infinite_skirt() ? object->layer_count() : std::min(size_t(print_config.skirt_height.value), object->layer_count());
|
|
brim_skirt_layers = std::max(brim_skirt_layers, skirt_layers);
|
|
}
|
|
|
|
// generate areas
|
|
const coordf_t layer_height = config.layer_height.value;
|
|
const size_t top_interface_layers = config.support_interface_top_layers.value;
|
|
const size_t bottom_interface_layers = config.support_interface_bottom_layers.value;
|
|
const size_t tip_layers = branch_radius / layer_height; //The number of layers to be shrinking the circle to create a tip. This produces a 45 degree angle.
|
|
const double diameter_angle_scale_factor = sin(tree_support_branch_diameter_angle * M_PI / 180.) * layer_height / branch_radius; //Scale factor per layer to produce the desired angle.
|
|
const coordf_t line_width = config.support_line_width;
|
|
const coordf_t line_width_scaled = scale_(line_width);
|
|
|
|
// coconut: previously std::unordered_map in m_collision_cache is not multi-thread safe which may cause programs stuck, here we change to tbb::concurrent_unordered_map
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, m_object->layer_count()),
|
|
[&](const tbb::blocked_range<size_t>& range)
|
|
{
|
|
for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++)
|
|
{
|
|
if (print->canceled())
|
|
break;
|
|
const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
|
|
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
|
assert(ts_layer != nullptr);
|
|
|
|
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
|
|
if (curr_layer_nodes.empty())
|
|
continue;
|
|
|
|
Node* first_node = curr_layer_nodes.front();
|
|
ts_layer->print_z = first_node->print_z;
|
|
ts_layer->height = first_node->height;
|
|
|
|
if (ts_layer->height < EPSILON)
|
|
continue;
|
|
|
|
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;
|
|
|
|
//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());
|
|
#if 1
|
|
for (const Node* p_node : contact_nodes[layer_nr])
|
|
{
|
|
if (print->canceled())
|
|
break;
|
|
|
|
const Node& node = *p_node;
|
|
ExPolygon area;
|
|
if (node.type == ePolygon) {
|
|
area = offset_ex({ *node.overhang }, scale_(m_ts_data->m_xy_distance))[0];
|
|
}
|
|
else {
|
|
Polygon circle;
|
|
size_t layers_to_top = node.distance_to_top;
|
|
double scale;
|
|
if (top_interface_layers>0) { // if has infill, branch circles should be larger
|
|
scale = static_cast<double>(layers_to_top + 1) / tip_layers;
|
|
scale = layers_to_top < tip_layers ? (0.5 + scale / 2) : (1 + static_cast<double>(layers_to_top - tip_layers) * diameter_angle_scale_factor);
|
|
} else {
|
|
scale = calc_branch_radius(1, node.distance_to_top, tip_layers, diameter_angle_scale_factor);
|
|
scale = std::min(scale, MAX_BRANCH_RADIUS / branch_radius);
|
|
}
|
|
for (auto iter = branch_circle.points.begin(); iter != branch_circle.points.end(); iter++)
|
|
{
|
|
Point corner = (*iter) * scale;
|
|
circle.append(node.position + corner);
|
|
}
|
|
|
|
if (layer_nr == 0 && m_raft_layers == 0) {
|
|
double brim_width = layers_to_top * layer_height / (scale * branch_radius) * 0.5;
|
|
circle = offset(circle, scale_(brim_width))[0];
|
|
}
|
|
area = ExPolygon(circle);
|
|
}
|
|
|
|
if (node.support_roof_layers_below == 1)
|
|
{
|
|
roof_1st_layer.emplace_back(area);
|
|
}
|
|
else if (node.support_roof_layers_below > 0)
|
|
{
|
|
roof_areas.emplace_back(area);
|
|
}
|
|
else
|
|
{
|
|
base_areas.emplace_back(area);
|
|
}
|
|
|
|
if (layer_nr < brim_skirt_layers)
|
|
ts_layer->lslices.emplace_back(area);
|
|
}
|
|
#else
|
|
// some nodes may not have radius set
|
|
for (Node* p_node : contact_nodes[layer_nr])
|
|
{
|
|
size_t layers_to_top = p_node->distance_to_top;// std::min(node.distance_to_top, (size_t)300);
|
|
double scale = static_cast<double>(layers_to_top + 1) / tip_layers;
|
|
scale = layers_to_top < tip_layers ? (0.5 + scale / 2) : (1 + static_cast<double>(layers_to_top - tip_layers) * diameter_angle_scale_factor);
|
|
p_node->radius = scale * branch_radius;
|
|
}
|
|
{
|
|
// now this method is extremely slow. Need to optimize the speed before we can use it.
|
|
Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(layer_nr));
|
|
std::vector<double> radiis;
|
|
std::vector<bool> is_interface;
|
|
//Polygons lines = spanning_tree_to_polygon(m_spanning_trees[layer_nr], layer_contours, layer_nr, radiis);
|
|
Polygons lines = contact_nodes_to_polygon(contact_nodes[layer_nr], layer_contours, layer_nr, radiis, is_interface);
|
|
|
|
for (int k = 0; k < lines.size(); k++) {
|
|
auto line = lines[k];
|
|
double radius = radiis[k];
|
|
Polygons line_expanded;
|
|
if (line.size() == 1)
|
|
{
|
|
Polygon circle;
|
|
double scale = radiis[k] / branch_radius;
|
|
for (auto iter = branch_circle.points.begin(); iter != branch_circle.points.end(); iter++)
|
|
{
|
|
Point corner = (*iter) * scale;
|
|
circle.append(line.first_point() + corner);
|
|
}
|
|
line_expanded.emplace_back(circle);
|
|
}
|
|
else {
|
|
line_expanded = offset(line, scale_(radius), jtRound, scale_(g_config_tree_support_collision_resolution));
|
|
}
|
|
if (line_expanded.empty())
|
|
continue;
|
|
if (is_interface[k])
|
|
roof_areas.emplace_back(line_expanded[0]);
|
|
else
|
|
base_areas.emplace_back(line_expanded[0]);
|
|
if (layer_nr < brim_skirt_layers)
|
|
ts_layer->lslices.emplace_back(line_expanded[0]);
|
|
|
|
//if (radius > config.support_base_pattern_spacing * 2)
|
|
// ts_layer->need_infill = true;
|
|
}
|
|
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
draw_contours_and_nodes_to_svg( layer_nr, base_areas, to_expolygons(lines), m_ts_data->m_layer_outlines_below[layer_nr], {}, {}, "circles", { "lines","base_areas","outlines" });
|
|
#endif
|
|
}
|
|
#endif
|
|
ts_layer->lslices = std::move(union_ex(ts_layer->lslices));
|
|
|
|
//Must update bounding box which is used in avoid crossing perimeter
|
|
ts_layer->lslices_bboxes.clear();
|
|
ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size());
|
|
for (const ExPolygon &expoly : ts_layer->lslices)
|
|
ts_layer->lslices_bboxes.emplace_back(get_extents(expoly));
|
|
ts_layer->backup_untyped_slices();
|
|
|
|
m_object->print()->set_status(65, (boost::format( _L("Support: generate polygons at layer %d")) % layer_nr).str());
|
|
|
|
// join roof segments
|
|
double contact_dist_scaled = scale_(m_slicing_params.gap_support_object);
|
|
roof_areas = std::move(offset2_ex(roof_areas, contact_dist_scaled, -contact_dist_scaled));
|
|
roof_1st_layer = std::move(offset2_ex(roof_1st_layer, contact_dist_scaled, -contact_dist_scaled));
|
|
|
|
// avoid object
|
|
auto avoid_region_interface = m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr);
|
|
roof_areas = std::move(diff_ex(roof_areas, avoid_region_interface));
|
|
roof_1st_layer = std::move(diff_ex(roof_1st_layer, avoid_region_interface));
|
|
|
|
// roof_1st_layer and roof_areas may intersect, so need to subtract roof_areas from roof_1st_layer
|
|
roof_1st_layer = std::move(diff_ex(roof_1st_layer, roof_areas));
|
|
|
|
// let supports touch objects when brim is on
|
|
auto avoid_region = m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr);
|
|
base_areas = std::move(diff_ex(base_areas, avoid_region));
|
|
base_areas = std::move(diff_ex(base_areas, roof_areas));
|
|
base_areas = std::move(diff_ex(base_areas, roof_1st_layer));
|
|
|
|
if (SQUARE_SUPPORT) {
|
|
// simplify support contours
|
|
ExPolygons base_areas_simplified;
|
|
for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified, SimplifyMethodDP); }
|
|
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)
|
|
{
|
|
const Layer* below_layer = m_object->get_layer(layer_nr - bottom_interface_layers);
|
|
ExPolygons bottom_interface = std::move(intersection_ex(base_areas, below_layer->lslices));
|
|
floor_areas.insert(floor_areas.end(), bottom_interface.begin(), bottom_interface.end());
|
|
}
|
|
if (floor_areas.empty() == false) {
|
|
floor_areas = std::move(diff_ex(floor_areas, avoid_region_interface));
|
|
floor_areas = std::move(offset2_ex(floor_areas, contact_dist_scaled, -contact_dist_scaled));
|
|
base_areas = std::move(diff_ex(base_areas, offset_ex(floor_areas, 10)));
|
|
}
|
|
}
|
|
if (bottom_gap_layers > 0 && layer_nr > bottom_gap_layers) {
|
|
const Layer* below_layer = m_object->get_layer(layer_nr - bottom_gap_layers);
|
|
ExPolygons bottom_gap = std::move(intersection_ex(floor_areas, below_layer->lslices));
|
|
if (!bottom_gap.empty()) {
|
|
floor_areas = std::move(diff_ex(floor_areas, bottom_gap));
|
|
}
|
|
}
|
|
|
|
auto &area_groups = ts_layer->area_groups;
|
|
for (auto &area : ts_layer->base_areas) area_groups.emplace_back(&area, TreeSupportLayer::BaseType);
|
|
for (auto &area : ts_layer->roof_areas) area_groups.emplace_back(&area, TreeSupportLayer::RoofType);
|
|
for (auto &area : ts_layer->floor_areas) area_groups.emplace_back(&area, TreeSupportLayer::FloorType);
|
|
for (auto &area : ts_layer->roof_1st_layer) area_groups.emplace_back(&area, TreeSupportLayer::Roof1stLayer);
|
|
|
|
for (auto &area_group : area_groups) {
|
|
auto expoly = area_group.first;
|
|
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());
|
|
}
|
|
|
|
}
|
|
});
|
|
|
|
#if 1
|
|
// move the holes to contour so they can be well supported
|
|
if (!has_infill) {
|
|
// check if poly's contour intersects with expoly's contour
|
|
auto intersects_contour = [](Polygon poly, ExPolygon expoly, Point &pt_on_poly, Point &pt_on_expoly, Point &pt_far_on_poly, float dist_thresh = 0.01) {
|
|
float min_dist = std::numeric_limits<float>::max();
|
|
float max_dist = 0;
|
|
for (auto from : poly.points) {
|
|
for (int i = 0; i < expoly.num_contours(); i++) {
|
|
const Point *candidate = expoly.contour_or_hole(i).closest_point(from);
|
|
double dist2 = vsize2_with_unscale(*candidate - from);
|
|
if (dist2 < min_dist) {
|
|
min_dist = dist2;
|
|
pt_on_poly = from;
|
|
pt_on_expoly = *candidate;
|
|
}
|
|
if (dist2 > max_dist) {
|
|
max_dist = dist2;
|
|
pt_far_on_poly = from;
|
|
}
|
|
if (dist2 < dist_thresh) { return true; }
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// polygon pointer: depth, direction, farPoint
|
|
std::map<const Polygon *, std::tuple<int, Point, Point>> holePropagationInfos;
|
|
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
|
|
if (print->canceled()) break;
|
|
m_object->print()->set_status(66, (boost::format(_L("Support: fix holes at layer %d")) % layer_nr).str());
|
|
|
|
const std::vector<Node *> &curr_layer_nodes = contact_nodes[layer_nr];
|
|
TreeSupportLayer * ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
|
assert(ts_layer != nullptr);
|
|
|
|
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
|
|
if (curr_layer_nodes.empty()) continue;
|
|
if (ts_layer->height < EPSILON) continue;
|
|
if (ts_layer->area_groups.empty()) continue;
|
|
|
|
int layer_nr_lower = layer_nr - 1;
|
|
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
|
|
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
|
|
}
|
|
auto &area_groups_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups;
|
|
|
|
for (const auto &area_group : ts_layer->area_groups) {
|
|
if (area_group.second != TreeSupportLayer::BaseType) continue;
|
|
const auto area = area_group.first;
|
|
for (const auto &hole : area->holes) {
|
|
// auto hole_bbox = get_extents(hole).polygon();
|
|
for (auto &area_group_lower : area_groups_lower) {
|
|
if (area_group.second != TreeSupportLayer::BaseType) continue;
|
|
auto &base_area_lower = *area_group_lower.first;
|
|
Point pt_on_poly, pt_on_expoly, pt_far_on_poly;
|
|
// if a hole doesn't intersect with lower layer's contours, add a hole to lower layer and move it slightly to the contour
|
|
if (base_area_lower.contour.contains(hole.points.front()) && !intersects_contour(hole, base_area_lower, pt_on_poly, pt_on_expoly, pt_far_on_poly)) {
|
|
Polygon hole_lower = hole;
|
|
Point direction = normal(pt_on_expoly - pt_on_poly, line_width_scaled / 2);
|
|
hole_lower.translate(direction);
|
|
// note to expand a hole, we need to do negative offset
|
|
auto hole_expanded = offset(hole_lower, -line_width_scaled / 4, ClipperLib::JoinType::jtSquare);
|
|
if (!hole_expanded.empty()) {
|
|
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
|
holePropagationInfos.insert({&base_area_lower.holes.back(), {25, direction, pt_far_on_poly}});
|
|
}
|
|
break;
|
|
} else if (holePropagationInfos.find(&hole) != holePropagationInfos.end() && std::get<0>(holePropagationInfos[&hole]) > 0 &&
|
|
base_area_lower.contour.contains(std::get<2>(holePropagationInfos[&hole]))) {
|
|
Polygon hole_lower = hole;
|
|
auto && direction = std::get<1>(holePropagationInfos[&hole]);
|
|
hole_lower.translate(direction);
|
|
// note to shrink a hole, we need to do positive offset
|
|
auto hole_expanded = offset(hole_lower, line_width_scaled / 2, ClipperLib::JoinType::jtSquare);
|
|
Point farPoint = std::get<2>(holePropagationInfos[&hole]) + direction * 2;
|
|
if (!hole_expanded.empty()) {
|
|
base_area_lower.holes.push_back(std::move(hole_expanded[0]));
|
|
holePropagationInfos.insert({&base_area_lower.holes.back(), {std::get<0>(holePropagationInfos[&hole]) - 1, direction, farPoint}});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
{
|
|
// if roof1 interface is inside a hole, need to expand the interface
|
|
for (auto &roof1 : ts_layer->roof_1st_layer) {
|
|
//if (hole.contains(roof1.contour.points.front()) && hole.contains(roof1.contour.bounding_box().center()))
|
|
bool is_inside_hole = std::all_of(roof1.contour.points.begin(), roof1.contour.points.end(), [&hole](Point &pt) { return hole.contains(pt); });
|
|
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();
|
|
auto tmp = union_({roof1.contour}, {hole_reoriented});
|
|
if (!tmp.empty()) roof1.contour = tmp[0];
|
|
|
|
// make sure 1) roof1 and object 2) roof1 and roof, won't intersect
|
|
// Note: We can't replace roof1 directly, as we have recorded its address.
|
|
// So instead we need to replace its members one by one.
|
|
auto tmp1 = diff_ex(roof1, m_ts_data->get_collision((layer_nr == 0 && has_brim) ? config.brim_object_gap : m_ts_data->m_xy_distance, layer_nr));
|
|
tmp1 = diff_ex(tmp1, ts_layer->roof_areas);
|
|
if (!tmp1.empty()) {
|
|
roof1.contour = std::move(tmp1[0].contour);
|
|
roof1.holes = std::move(tmp1[0].holes);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
for (int layer_nr = m_object->layer_count() - 1; layer_nr > 0; layer_nr--) {
|
|
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
|
|
ExPolygons& base_areas = ts_layer->base_areas;
|
|
ExPolygons& roof_areas = ts_layer->roof_areas;
|
|
ExPolygons& roof_1st_layer = ts_layer->roof_1st_layer;
|
|
ExPolygons& floor_areas = ts_layer->floor_areas;
|
|
if (base_areas.empty() && roof_areas.empty() && roof_1st_layer.empty()) continue;
|
|
char fname[10]; sprintf(fname, "%d_%.2f", layer_nr, ts_layer->print_z);
|
|
draw_contours_and_nodes_to_svg(-1, base_areas, roof_areas, roof_1st_layer, {}, {}, get_svg_filename(fname, "circles"), {"base", "roof", "roof1st"});
|
|
}
|
|
#endif
|
|
|
|
TreeSupportLayerPtrs& ts_layers = m_object->tree_support_layers();
|
|
auto iter = std::remove_if(ts_layers.begin(), ts_layers.end(), [](TreeSupportLayer* ts_layer) { return ts_layer->height < EPSILON; });
|
|
ts_layers.erase(iter, ts_layers.end());
|
|
for (int layer_nr = 0; layer_nr < ts_layers.size(); layer_nr++) {
|
|
ts_layers[layer_nr]->upper_layer = layer_nr != ts_layers.size() - 1 ? ts_layers[layer_nr + 1] : nullptr;
|
|
ts_layers[layer_nr]->lower_layer = layer_nr > 0 ? ts_layers[layer_nr - 1] : nullptr;
|
|
}
|
|
}
|
|
|
|
void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
|
|
{
|
|
const PrintObjectConfig &config = m_object->config();
|
|
//Use Minimum Spanning Tree to connect the points on each layer and move them while dropping them down.
|
|
const coordf_t layer_height = config.layer_height.value;
|
|
const double angle = config.tree_support_branch_angle.value * M_PI / 180.;
|
|
const double tan_angle = tan(angle);
|
|
const coordf_t max_move_distance = (angle < M_PI / 2) ? (coordf_t)(tan_angle * layer_height) : std::numeric_limits<coordf_t>::max();
|
|
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.
|
|
const double diameter_angle_scale_factor = sin(tree_support_branch_diameter_angle * M_PI / 180.) * layer_height / branch_radius; //Scale factor per layer to produce the desired angle.
|
|
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;
|
|
|
|
std::unordered_set<Node*> to_free_node_set;
|
|
m_spanning_trees.resize(contact_nodes.size());
|
|
//m_mst_line_x_layer_contour_caches.resize(contact_nodes.size());
|
|
|
|
{// get outlines below and avoidance area using tbb
|
|
//m_object->print()->set_status(59, "Support: preparing avoidance regions ");
|
|
// get all the possible radiis
|
|
std::vector<std::set<coordf_t> > all_layer_radius(m_highest_overhang_layer+1);
|
|
std::vector<std::set<size_t> > all_layer_node_dist(m_highest_overhang_layer+1);
|
|
for (size_t layer_nr = m_highest_overhang_layer; layer_nr > 0; layer_nr--)
|
|
{
|
|
auto& layer_contact_nodes = contact_nodes[layer_nr];
|
|
auto& layer_radius = all_layer_radius[layer_nr];
|
|
auto& layer_node_dist = all_layer_node_dist[layer_nr];
|
|
if (!layer_contact_nodes.empty()) {
|
|
for (Node* p_node : layer_contact_nodes) {
|
|
layer_node_dist.emplace(p_node->distance_to_top);
|
|
}
|
|
}
|
|
if (layer_nr < m_highest_overhang_layer) {
|
|
for (auto node_dist : all_layer_node_dist[layer_nr + 1])
|
|
layer_node_dist.emplace(node_dist+1);
|
|
}
|
|
for (auto node_dist : layer_node_dist) {
|
|
layer_radius.emplace(calc_branch_radius(branch_radius, node_dist, tip_layers, diameter_angle_scale_factor));
|
|
}
|
|
}
|
|
// parallel pre-compute avoidance
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(1, m_highest_overhang_layer),
|
|
[&](const tbb::blocked_range<size_t>& range) {
|
|
for (size_t layer_nr = range.begin(); layer_nr < range.end(); layer_nr++) {
|
|
for (auto node_dist : all_layer_node_dist[layer_nr])
|
|
{
|
|
m_ts_data->get_avoidance(0, layer_nr - 1);
|
|
m_ts_data->get_avoidance(calc_branch_radius(branch_radius, node_dist, tip_layers, diameter_angle_scale_factor), layer_nr - 1);
|
|
}
|
|
}
|
|
});
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "before m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size();
|
|
}
|
|
|
|
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.
|
|
{
|
|
if (m_object->print()->canceled())
|
|
break;
|
|
|
|
auto& layer_contact_nodes = contact_nodes[layer_nr];
|
|
std::deque<std::pair<size_t, Node*>> unsupported_branch_leaves; // All nodes that are leaves on this layer that would result in unsupported ('mid-air') branches.
|
|
const Layer* ts_layer = m_object->get_tree_support_layer(layer_nr);
|
|
if (layer_contact_nodes.empty())
|
|
continue;
|
|
m_object->print()->set_status(60, (boost::format(_L("Support: propagate branches at layer %d")) % layer_nr).str());
|
|
|
|
for (Node* p_node : layer_contact_nodes)
|
|
{
|
|
if (p_node->type == ePolygon) {
|
|
Node* next_node = new Node(*p_node);
|
|
next_node->distance_to_top++;
|
|
next_node->support_roof_layers_below--;
|
|
next_node->print_z -= m_object->get_layer(layer_nr)->height;
|
|
next_node->to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], next_node->position);
|
|
contact_nodes[layer_nr - 1].emplace_back(next_node);
|
|
}
|
|
}
|
|
|
|
Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(layer_nr));
|
|
//std::unordered_map<Line, bool, LineHash>& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
|
|
std::unordered_map<Line, bool, LineHash> mst_line_x_layer_contour_cache;
|
|
auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b)
|
|
{
|
|
auto iter = mst_line_x_layer_contour_cache.find({ a, b });
|
|
if (iter != mst_line_x_layer_contour_cache.end()) {
|
|
if (iter->second)
|
|
return true;
|
|
}
|
|
else {
|
|
profiler.tic();
|
|
Line ln(b, a);
|
|
Lines pls_intersect = intersection_ln(ln, layer_contours);
|
|
mst_line_x_layer_contour_cache.insert({ {a, b}, !pls_intersect.empty() });
|
|
mst_line_x_layer_contour_cache.insert({ ln, !pls_intersect.empty() });
|
|
profiler.stage_add(STAGE_intersection_ln, true);
|
|
if (!pls_intersect.empty())
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
//Group together all nodes for each part.
|
|
const ExPolygons& parts = m_ts_data->get_avoidance(0, layer_nr);
|
|
std::vector<std::unordered_map<Point, Node*, PointHash>> nodes_per_part(1 + parts.size()); //All nodes that aren't inside a part get grouped together in the 0th part.
|
|
for (Node* p_node : layer_contact_nodes)
|
|
{
|
|
const Node& node = *p_node;
|
|
|
|
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;
|
|
}
|
|
if (node.type == ePolygon) {
|
|
// polygon node do not merge or move
|
|
continue;
|
|
}
|
|
/* Find which part this node is located in and group the nodes in
|
|
* the same part together. Since nodes have a radius and the
|
|
* avoidance areas are offset by that radius, the set of parts may
|
|
* be different per node. Here we consider a node to be inside the
|
|
* part that is closest. The node may be inside a bigger part that
|
|
* is actually two parts merged together due to an offset. In that
|
|
* case we may incorrectly keep two nodes separate, but at least
|
|
* every node falls into some group.
|
|
*/
|
|
coordf_t closest_part_distance2 = std::numeric_limits<coordf_t>::max();
|
|
size_t closest_part = -1;
|
|
for (size_t part_index = 0; part_index < parts.size(); part_index++)
|
|
{
|
|
//constexpr bool border_result = true;
|
|
if (is_inside_ex(parts[part_index], node.position)) //If it's inside, the distance is 0 and this part is considered the best.
|
|
{
|
|
closest_part = part_index;
|
|
closest_part_distance2 = 0;
|
|
break;
|
|
}
|
|
|
|
Point closest_point = *parts[part_index].contour.closest_point(node.position);
|
|
const coordf_t distance2 = vsize2_with_unscale(node.position - closest_point);
|
|
if (distance2 < closest_part_distance2)
|
|
{
|
|
closest_part_distance2 = distance2;
|
|
closest_part = part_index;
|
|
}
|
|
}
|
|
//Put it in the best one.
|
|
nodes_per_part[closest_part + 1][node.position] = p_node; //Index + 1 because the 0th index is the outside part.
|
|
}
|
|
|
|
//Create a MST for every part.
|
|
profiler.tic();
|
|
//std::vector<MinimumSpanningTree>& spanning_trees = m_spanning_trees[layer_nr];
|
|
std::vector<MinimumSpanningTree> spanning_trees;
|
|
for (const std::unordered_map<Point, Node*, PointHash>& group : nodes_per_part)
|
|
{
|
|
std::vector<Point> points_to_buildplate;
|
|
for (const std::pair<const Point, Node*>& entry : group)
|
|
{
|
|
points_to_buildplate.emplace_back(entry.first); //Just the position of the node.
|
|
}
|
|
spanning_trees.emplace_back(points_to_buildplate);
|
|
}
|
|
profiler.stage_add(STAGE_MinimumSpanningTree,true);
|
|
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
coordf_t branch_radius_temp = 0;
|
|
coordf_t max_y = std::numeric_limits<coordf_t>::min();
|
|
draw_layer_mst(layer_nr, spanning_trees, m_object->get_layer(layer_nr)->lslices);
|
|
#endif
|
|
for (size_t group_index = 0; group_index < nodes_per_part.size(); group_index++)
|
|
{
|
|
const MinimumSpanningTree& mst = spanning_trees[group_index];
|
|
//In the first pass, merge all nodes that are close together.
|
|
std::unordered_set<Node*> to_delete;
|
|
for (const std::pair<const Point, Node*>& entry : nodes_per_part[group_index])
|
|
{
|
|
Node* p_node = entry.second;
|
|
Node& node = *p_node;
|
|
if (to_delete.find(p_node) != to_delete.end())
|
|
{
|
|
continue; //Delete this node (don't create a new node for it on the next layer).
|
|
}
|
|
const std::vector<Point>& neighbours = mst.adjacent_nodes(node.position);
|
|
if (neighbours.size() == 1 && vsize2_with_unscale(neighbours[0] - node.position) < max_move_distance2 && mst.adjacent_nodes(neighbours[0]).size() == 1) //We have just two nodes left, and they're very close!
|
|
{
|
|
//Insert a completely new node and let both original nodes fade.
|
|
Point next_position = (node.position + neighbours[0]) / 2; //Average position of the two nodes.
|
|
|
|
const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.distance_to_top, tip_layers, diameter_angle_scale_factor);
|
|
|
|
auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr - 1);
|
|
if (group_index == 0)
|
|
{
|
|
//Avoid collisions.
|
|
const coordf_t max_move_between_samples = max_move_distance + radius_sample_resolution + EPSILON; //100 micron extra for rounding errors.
|
|
move_out_expolys(avoid_layer, next_position, radius_sample_resolution + EPSILON, max_move_between_samples);
|
|
}
|
|
|
|
Node* neighbour = nodes_per_part[group_index][neighbours[0]];
|
|
size_t new_distance_to_top = std::max(node.distance_to_top, neighbour->distance_to_top) + 1;
|
|
size_t new_support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour->support_roof_layers_below) - 1;
|
|
|
|
const bool to_buildplate = !is_inside_ex(m_ts_data->get_avoidance(0, layer_nr - 1), next_position);
|
|
Node* next_node = new Node(next_position, new_distance_to_top, node.skin_direction, new_support_roof_layers_below, to_buildplate, p_node,p_node->print_z,p_node->height);
|
|
next_node->movement = next_position - node.position;
|
|
contact_nodes[layer_nr - 1].push_back(next_node);
|
|
|
|
// Make sure the next pass doesn't drop down either of these (since that already happened).
|
|
node.merged_neighbours.push_front(neighbour);
|
|
to_delete.insert(neighbour);
|
|
to_delete.insert(p_node);
|
|
}
|
|
else if (neighbours.size() > 1) //Don't merge leaf nodes because we would then incur movement greater than the maximum move distance.
|
|
{
|
|
//Remove all neighbours that are too close and merge them into this node.
|
|
for (const Point& neighbour : neighbours)
|
|
{
|
|
if (vsize2_with_unscale(neighbour - node.position) < max_move_distance2)
|
|
{
|
|
Node* neighbour_node = nodes_per_part[group_index][neighbour];
|
|
node.distance_to_top = std::max(node.distance_to_top, neighbour_node->distance_to_top);
|
|
node.support_roof_layers_below = std::max(node.support_roof_layers_below, neighbour_node->support_roof_layers_below);
|
|
node.merged_neighbours.push_front(neighbour_node);
|
|
node.merged_neighbours.insert_after(node.merged_neighbours.end(), neighbour_node->merged_neighbours.begin(), neighbour_node->merged_neighbours.end());
|
|
to_delete.insert(neighbour_node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//In the second pass, move all middle nodes.
|
|
for (const std::pair<const Point, Node*>& entry : nodes_per_part[group_index])
|
|
{
|
|
Node* p_node = entry.second;
|
|
const Node& node = *p_node;
|
|
if (to_delete.find(p_node) != to_delete.end())
|
|
{
|
|
continue;
|
|
}
|
|
//If the branch falls completely inside a collision area (the entire branch would be removed by the X/Y offset), delete it.
|
|
if (group_index > 0 && is_inside_ex(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position))
|
|
{
|
|
const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.distance_to_top, tip_layers, diameter_angle_scale_factor);
|
|
Point to_outside = projection_onto_ex(m_ts_data->get_collision(m_ts_data->m_xy_distance, layer_nr), node.position);
|
|
double dist2_to_outside = vsize2_with_unscale(node.position - to_outside);
|
|
if (dist2_to_outside >= branch_radius_node * branch_radius_node) //Too far inside.
|
|
{
|
|
if (support_on_buildplate_only)
|
|
{
|
|
unsupported_branch_leaves.push_front({ layer_nr, p_node });
|
|
}
|
|
/*else {
|
|
Node* pn = p_node;
|
|
for (int i = 0; i < bottom_interface_layers && pn; i++, pn = pn->parent)
|
|
pn->support_floor_layers_above = bottom_interface_layers - i;
|
|
to_delete.insert(p_node);
|
|
}*/
|
|
continue;
|
|
}
|
|
// if the link between parent and current is cut by contours, delete this branch
|
|
if (p_node->parent && intersection_ln({p_node->position, p_node->parent->position}, layer_contours).empty()==false)
|
|
{
|
|
//unsupported_branch_leaves.push_front({ layer_nr, p_node });
|
|
Node* pn = p_node->parent;
|
|
for (int i = 0; i < bottom_interface_layers && pn; i++, pn = pn->parent)
|
|
pn->support_floor_layers_above = bottom_interface_layers - i;
|
|
to_delete.insert(p_node);
|
|
continue;
|
|
}
|
|
}
|
|
Point next_layer_vertex = node.position;
|
|
Point move_to_neighbor_center;
|
|
const std::vector<Point> neighbours = mst.adjacent_nodes(node.position);
|
|
// 1. do not merge neighbors under 5mm
|
|
// 2. Only merge node with single neighbor in distance between [max_move_distance, 10mm/layer_height]
|
|
float dist2_to_first_neighbor = neighbours.empty() ? 0 : vsize2_with_unscale(neighbours[0] - node.position);
|
|
if (ts_layer->print_z > DO_NOT_MOVER_UNDER_MM &&
|
|
(neighbours.size() > 1 || (neighbours.size() == 1 && dist2_to_first_neighbor >= max_move_distance2 && dist2_to_first_neighbor < SQ(10/layer_height)*max_move_distance2))) //Only nodes that aren't about to collapse.
|
|
{
|
|
//Move towards the average position of all neighbours.
|
|
Point sum_direction(0, 0);
|
|
for (const Point& neighbour : neighbours)
|
|
{
|
|
Point direction = neighbour - node.position;
|
|
Node *neighbour_node = nodes_per_part[group_index][neighbour];
|
|
coordf_t branch_bottom_radius = calc_branch_radius(branch_radius, node.distance_to_top + layer_nr, tip_layers, diameter_angle_scale_factor);
|
|
coordf_t neighbour_bottom_radius = calc_branch_radius(branch_radius, neighbour_node->distance_to_top + layer_nr, tip_layers, diameter_angle_scale_factor);
|
|
const coordf_t min_overlap = branch_radius;
|
|
double max_converge_distance = tan_angle * (ts_layer->print_z - DO_NOT_MOVER_UNDER_MM) + branch_bottom_radius + neighbour_bottom_radius - min_overlap;
|
|
if (vsize2_with_unscale(direction) > max_converge_distance * max_converge_distance)
|
|
continue;
|
|
|
|
if (is_line_cut_by_contour(node.position, neighbour))
|
|
continue;
|
|
|
|
sum_direction += direction;
|
|
}
|
|
|
|
if(vsize2_with_unscale(sum_direction) <= max_move_distance2)
|
|
{
|
|
move_to_neighbor_center = sum_direction;
|
|
}
|
|
else
|
|
{
|
|
move_to_neighbor_center = normal(sum_direction, scale_(max_move_distance));
|
|
}
|
|
// add momentum to force smooth movement
|
|
move_to_neighbor_center = move_to_neighbor_center * 0.5 + p_node->movement * 0.5;
|
|
}
|
|
|
|
const coordf_t branch_radius_node = calc_branch_radius(branch_radius, node.distance_to_top, tip_layers, diameter_angle_scale_factor);
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
if (node.position(1) > max_y) {
|
|
max_y = node.position(1);
|
|
branch_radius_temp = branch_radius_node;
|
|
}
|
|
#endif
|
|
auto avoid_layer = m_ts_data->get_avoidance(branch_radius_node, layer_nr - 1);
|
|
|
|
#if 1
|
|
Point to_outside = projection_onto_ex(avoid_layer, node.position);
|
|
Point movement = to_outside - node.position;
|
|
double movelength2 = vsize2_with_unscale(movement);
|
|
// 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
|
|
if (is_line_cut_by_contour(node.position, to_outside) || movelength2 > max_move_distance2 * SQ(layer_nr))
|
|
movement = Point(0, 0);
|
|
else if (movelength2 > max_move_distance2) {
|
|
if (is_inside_ex(avoid_layer, node.position))
|
|
movement = normal(movement, scale_(max_move_distance));
|
|
else
|
|
movement = Point(0, 0); // point is already outside contour, no need to move
|
|
}
|
|
// move to the averaged direction of neighbor center and contour edge if they are roughly same direction
|
|
if (movement.dot(move_to_neighbor_center) >= 0)
|
|
movement = movement + move_to_neighbor_center;
|
|
// Cant do this. Otherwise we'll get a lot of supports in-the-air (nodes terminated too early)
|
|
//else
|
|
// movement = move_to_neighbor_center; // otherwise move to neighbor center first
|
|
|
|
if (vsize2_with_unscale(movement) > max_move_distance2)
|
|
movement = normal(movement, scale_(max_move_distance));
|
|
#else
|
|
Point movement = move_to_neighbor_center;
|
|
#endif
|
|
next_layer_vertex += movement;
|
|
|
|
|
|
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.
|
|
bool is_outside = move_out_expolys(avoid_layer, next_layer_vertex, radius_sample_resolution + EPSILON, max_move_between_samples);
|
|
if (!is_outside) {
|
|
Point candidate_vertex = node.position;
|
|
is_outside = move_out_expolys(avoid_layer, candidate_vertex, radius_sample_resolution + EPSILON, max_move_between_samples);
|
|
if (is_outside) {
|
|
next_layer_vertex = candidate_vertex;
|
|
}
|
|
}
|
|
}
|
|
|
|
const bool to_buildplate = !is_inside_ex(m_ts_data->m_layer_outlines[layer_nr], next_layer_vertex);// !is_inside_ex(m_ts_data->get_avoidance(m_ts_data->m_xy_distance, layer_nr - 1), next_layer_vertex);
|
|
Node * next_node = new Node(next_layer_vertex, node.distance_to_top + 1, node.skin_direction, node.support_roof_layers_below - 1, to_buildplate, p_node,
|
|
m_object->get_layer(layer_nr - 1)->print_z, m_object->get_layer(layer_nr-1)->height);
|
|
next_node->movement = movement;
|
|
contact_nodes[layer_nr - 1].push_back(next_node);
|
|
}
|
|
}
|
|
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
draw_contours_and_nodes_to_svg(layer_nr, m_ts_data->get_avoidance(0, layer_nr), m_ts_data->get_avoidance(branch_radius_temp, layer_nr), m_ts_data->m_layer_outlines_below[layer_nr],
|
|
contact_nodes[layer_nr], contact_nodes[layer_nr - 1], "contact_points", { "overhang","avoid","outline" }, { "blue","red","yellow" });
|
|
#endif
|
|
|
|
// Prune all branches that couldn't find support on either the model or the buildplate (resulting in 'mid-air' branches).
|
|
for (;! unsupported_branch_leaves.empty(); unsupported_branch_leaves.pop_back())
|
|
{
|
|
const auto& entry = unsupported_branch_leaves.back();
|
|
Node* i_node = entry.second;
|
|
for (size_t i_layer = entry.first; i_node != nullptr; ++i_layer, i_node = i_node->parent)
|
|
{
|
|
std::vector<Node*>::iterator to_erase = std::find(contact_nodes[i_layer].begin(), contact_nodes[i_layer].end(), i_node);
|
|
if (to_erase != contact_nodes[i_layer].end())
|
|
{
|
|
to_free_node_set.insert(*to_erase);
|
|
contact_nodes[i_layer].erase(to_erase);
|
|
to_free_node_set.insert(i_node);
|
|
|
|
for (Node* neighbour : i_node->merged_neighbours)
|
|
{
|
|
unsupported_branch_leaves.push_front({ i_layer, neighbour });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "after m_avoidance_cache.size()=" << m_ts_data->m_avoidance_cache.size();
|
|
|
|
for (Node *node : to_free_node_set)
|
|
{
|
|
delete node;
|
|
}
|
|
to_free_node_set.clear();
|
|
}
|
|
|
|
void TreeSupport::adjust_layer_heights(std::vector<std::vector<Node*>>& contact_nodes)
|
|
{
|
|
if (contact_nodes.empty())
|
|
return;
|
|
|
|
const PrintObjectConfig& config = m_object->config();
|
|
if (!config.independent_support_layer_height) {
|
|
for (int layer_nr = 0; layer_nr < contact_nodes.size(); layer_nr++) {
|
|
std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
|
|
for (Node* node : curr_layer_nodes) {
|
|
node->print_z = m_object->get_layer(layer_nr)->print_z;
|
|
node->height = m_object->get_layer(layer_nr)->height;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// extreme layer_id
|
|
std::vector<int> extremes;
|
|
const coordf_t layer_height = config.layer_height.value;
|
|
const coordf_t max_layer_height = m_slicing_params.max_layer_height;
|
|
const size_t bot_intf_layers = config.support_interface_bottom_layers.value;
|
|
const size_t top_intf_layers = config.support_interface_top_layers.value;
|
|
|
|
// if already using max layer height, no need to adjust
|
|
if (layer_height == max_layer_height) return;
|
|
|
|
extremes.push_back(0);
|
|
for (Node* node : contact_nodes[0]) {
|
|
node->print_z = m_object->get_layer(0)->print_z;
|
|
node->height = m_object->get_layer(0)->height;
|
|
}
|
|
|
|
for (int layer_nr = 1; layer_nr < contact_nodes.size(); layer_nr++) {
|
|
std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
|
|
for (Node* node : curr_layer_nodes) {
|
|
if (node->support_roof_layers_below == top_intf_layers || node->support_floor_layers_above == bot_intf_layers) {
|
|
extremes.push_back(layer_nr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (extremes.back() == layer_nr) {
|
|
// contact layer use the same print_z and layer height with object layer
|
|
for (Node* node : curr_layer_nodes) {
|
|
node->print_z = m_object->get_layer(layer_nr)->print_z;
|
|
node->height = m_object->get_layer(layer_nr)->height;
|
|
}
|
|
}
|
|
}
|
|
|
|
// schedule new layer heights and print_z
|
|
for (size_t idx_extreme = 0; idx_extreme < extremes.size(); idx_extreme++) {
|
|
int extr2_layer_nr = extremes[idx_extreme];
|
|
coordf_t extr2z = m_object->get_layer(extr2_layer_nr)->bottom_z();
|
|
int extr1_layer_nr = idx_extreme == 0 ? -1 : extremes[idx_extreme - 1];
|
|
coordf_t extr1z = idx_extreme == 0 ? 0.f : m_object->get_layer(extr1_layer_nr)->print_z;
|
|
coordf_t dist = extr2z - extr1z;
|
|
|
|
// Insert intermediate layers.
|
|
size_t n_layers_extra = size_t(ceil(dist / m_slicing_params.max_suport_layer_height));
|
|
if (n_layers_extra <= 1)
|
|
continue;
|
|
|
|
coordf_t step = dist / coordf_t(n_layers_extra);
|
|
coordf_t print_z = extr1z + step;
|
|
assert(step >= layer_height - EPSILON);
|
|
for (int layer_nr = extr1_layer_nr + 1; layer_nr < extr2_layer_nr; layer_nr++) {
|
|
std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
|
|
if (curr_layer_nodes.empty()) continue;
|
|
|
|
if (std::abs(print_z - curr_layer_nodes[0]->print_z) < step / 2 + EPSILON) {
|
|
for (Node* node : curr_layer_nodes) {
|
|
node->print_z = print_z;
|
|
node->height = step;
|
|
}
|
|
print_z += step;
|
|
}
|
|
else {
|
|
// can't clear curr_layer_nodes, or the model will have empty layers
|
|
for (Node* node : curr_layer_nodes) {
|
|
node->print_z = 0.0;
|
|
node->height = 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::Node*>>& contact_nodes)
|
|
{
|
|
const PrintObjectConfig &config = m_object->config();
|
|
const coordf_t point_spread = scale_(config.tree_support_branch_distance.value);
|
|
|
|
//First generate grid points to cover the entire area of the print.
|
|
BoundingBox bounding_box = m_object->bounding_box();
|
|
const Point bounding_box_size = bounding_box.max - bounding_box.min;
|
|
constexpr double rotate_angle = 22.0 / 180.0 * M_PI;
|
|
constexpr double thresh_big_overhang = SQ(scale_(10));
|
|
|
|
const auto center = bounding_box_middle(bounding_box);
|
|
const auto sin_angle = std::sin(rotate_angle);
|
|
const auto cos_angle = std::cos(rotate_angle);
|
|
const auto rotated_dims = Point(
|
|
bounding_box_size(0) * cos_angle + bounding_box_size(1) * sin_angle,
|
|
bounding_box_size(0) * sin_angle + bounding_box_size(1) * cos_angle) / 2;
|
|
|
|
std::vector<Point> grid_points;
|
|
for (auto x = -rotated_dims(0); x < rotated_dims(0); x += point_spread) {
|
|
for (auto y = -rotated_dims(1); y < rotated_dims(1); y += point_spread) {
|
|
Point pt(x, y);
|
|
pt.rotate(cos_angle, sin_angle);
|
|
pt += center;
|
|
if (bounding_box.contains(pt)) {
|
|
grid_points.push_back(pt);
|
|
}
|
|
}
|
|
}
|
|
|
|
const coordf_t layer_height = config.layer_height.value;
|
|
coordf_t z_distance_top = m_slicing_params.gap_support_object;
|
|
// BBS: add extra distance if thick bridge is enabled
|
|
// Note: normal support uses print_z, but tree support uses integer layers, so we need to subtract layer_height
|
|
if (!m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
|
|
z_distance_top += m_object->layers()[0]->regions()[0]->region().bridging_height_avg(m_object->print()->config()) - layer_height;
|
|
}
|
|
const size_t z_distance_top_layers = round_up_divide(scale_(z_distance_top), scale_(layer_height)) + 1; //Support must always be 1 layer below overhang.
|
|
|
|
const size_t support_roof_layers = config.support_interface_top_layers.value + 1; // BBS: add a normal support layer below interface
|
|
coordf_t thresh_angle = config.support_threshold_angle.value < EPSILON ? 30.f : config.support_threshold_angle.value;
|
|
coordf_t half_overhang_distance = scale_(tan(thresh_angle * M_PI / 180.0) * layer_height / 2);
|
|
|
|
// fix bug of generating support for very thin objects
|
|
if (m_object->layers().size() <= z_distance_top_layers + 1)
|
|
return;
|
|
|
|
m_highest_overhang_layer = 0;
|
|
int nonempty_layers = 0;
|
|
std::vector<Slic3r::Vec3f> all_nodes;
|
|
for (size_t layer_nr = 1; layer_nr < m_object->layers().size() - z_distance_top_layers; layer_nr++)
|
|
{
|
|
if (m_object->print()->canceled())
|
|
break;
|
|
auto ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers + z_distance_top_layers);
|
|
const ExPolygons &overhang = ts_layer->overhang_areas;
|
|
auto & curr_nodes = contact_nodes[layer_nr];
|
|
if (overhang.empty())
|
|
continue;
|
|
|
|
m_highest_overhang_layer = std::max(m_highest_overhang_layer, layer_nr);
|
|
auto print_z = m_object->get_layer(layer_nr)->print_z;
|
|
auto height = m_object->get_layer(layer_nr)->height;
|
|
|
|
for (const ExPolygon &overhang_part : overhang)
|
|
{
|
|
BoundingBox overhang_bounds = get_extents(overhang_part);
|
|
if (config.support_type.value==stHybridAuto && overhang_part.area() > thresh_big_overhang) {
|
|
Point candidate = overhang_bounds.center();
|
|
if (!overhang_part.contains(candidate))
|
|
move_inside_expoly(overhang_part, candidate);
|
|
Node *contact_node = new Node(candidate, 0, (layer_nr + z_distance_top_layers) % 2, support_roof_layers, true, Node::NO_PARENT, print_z, height);
|
|
contact_node->type = ePolygon;
|
|
contact_node->overhang = &overhang_part;
|
|
curr_nodes.emplace_back(contact_node);
|
|
continue;
|
|
}
|
|
|
|
overhang_bounds.inflated(half_overhang_distance);
|
|
bool added = false; //Did we add a point this way?
|
|
for (Point candidate : grid_points)
|
|
{
|
|
if (overhang_bounds.contains(candidate))
|
|
{
|
|
// BBS: move_inside_expoly shouldn't be used if candidate is already inside, as it moves point to boundary and the inside is not well supported!
|
|
bool is_inside = is_inside_ex(overhang_part, candidate);
|
|
if (!is_inside) {
|
|
constexpr coordf_t distance_inside = 0; // Move point towards the border of the polygon if it is closer than half the overhang distance: Catch points that
|
|
// fall between overhang areas on constant surfaces.
|
|
move_inside_expoly(overhang_part, candidate, distance_inside, half_overhang_distance);
|
|
is_inside = is_inside_ex(overhang_part, candidate);
|
|
}
|
|
if (is_inside)
|
|
{
|
|
// collision radius has to be 0 or the supports are too few at curved slopes
|
|
//if (!is_inside_ex(m_ts_data->get_collision(0, layer_nr), candidate))
|
|
{
|
|
constexpr size_t distance_to_top = 0;
|
|
constexpr bool to_buildplate = true;
|
|
Node* contact_node = new Node(candidate, distance_to_top, (layer_nr + z_distance_top_layers) % 2, support_roof_layers, to_buildplate, Node::NO_PARENT,print_z,height);
|
|
curr_nodes.emplace_back(contact_node);
|
|
added = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!added) //If we didn't add any points due to bad luck, we want to add one anyway such that loose parts are also supported.
|
|
{
|
|
auto bbox = overhang_part.contour.bounding_box();
|
|
Points candidates;
|
|
if (ts_layer->overhang_types[&overhang_part] == TreeSupportLayer::Detected)
|
|
candidates = {bbox.min, bounding_box_middle(bbox), bbox.max};
|
|
else
|
|
candidates = {bounding_box_middle(bbox)};
|
|
|
|
for (Point candidate : candidates) {
|
|
if (!overhang_part.contains(candidate))
|
|
move_inside_expoly(overhang_part, candidate);
|
|
constexpr size_t distance_to_top = 0;
|
|
constexpr bool to_buildplate = true;
|
|
Node * contact_node = new Node(candidate, distance_to_top, layer_nr % 2, support_roof_layers, to_buildplate, Node::NO_PARENT, print_z, height);
|
|
curr_nodes.emplace_back(contact_node);
|
|
}
|
|
}
|
|
if (ts_layer->overhang_types[&overhang_part] == TreeSupportLayer::Detected) {
|
|
// add points at corners
|
|
auto &points = overhang_part.contour.points;
|
|
for (int i = 0; i < points.size(); i++) {
|
|
auto pt = points[i];
|
|
auto v1 = (pt - points[(i - 1 + points.size()) % points.size()]).normalized();
|
|
auto v2 = (pt - points[(i + 1) % points.size()]).normalized();
|
|
if (v1.dot(v2) > -0.7) {
|
|
Node *contact_node = new Node(pt, 0, layer_nr % 2, support_roof_layers, true, Node::NO_PARENT, print_z, height);
|
|
curr_nodes.emplace_back(contact_node);
|
|
}
|
|
}
|
|
} else if(ts_layer->overhang_types[&overhang_part] == TreeSupportLayer::Enforced){
|
|
// remove close points in Enforcers
|
|
auto above_nodes = contact_nodes[layer_nr - 1];
|
|
if (!curr_nodes.empty() && !above_nodes.empty()) {
|
|
for (auto it = curr_nodes.begin(); it != curr_nodes.end();) {
|
|
bool is_duplicate = false;
|
|
Slic3r::Vec3f curr_pt((*it)->position(0), (*it)->position(1), scale_((*it)->print_z));
|
|
for (auto &pt : all_nodes) {
|
|
auto dif = curr_pt - pt;
|
|
if (dif.norm() < scale_(2)) {
|
|
delete (*it);
|
|
it = curr_nodes.erase(it);
|
|
is_duplicate = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!is_duplicate) it++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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)); }
|
|
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
|
|
draw_contours_and_nodes_to_svg(layer_nr, overhang, m_ts_data->m_layer_outlines_below[layer_nr], {},
|
|
contact_nodes[layer_nr], {}, "init_contact_points", { "overhang","outlines","" });
|
|
#endif
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
void TreeSupport::insert_dropped_node(std::vector<Node*>& nodes_layer, Node* p_node)
|
|
{
|
|
std::vector<Node*>::iterator conflicting_node_it = std::find(nodes_layer.begin(), nodes_layer.end(), p_node);
|
|
if (conflicting_node_it == nodes_layer.end()) //No conflict.
|
|
{
|
|
nodes_layer.emplace_back(p_node);
|
|
return;
|
|
}
|
|
|
|
Node* conflicting_node = *conflicting_node_it;
|
|
conflicting_node->distance_to_top = std::max(conflicting_node->distance_to_top, p_node->distance_to_top);
|
|
conflicting_node->support_roof_layers_below = std::max(conflicting_node->support_roof_layers_below, p_node->support_roof_layers_below);
|
|
}
|
|
|
|
TreeSupportData::TreeSupportData(const PrintObject &object, coordf_t xy_distance, coordf_t max_move, coordf_t radius_sample_resolution)
|
|
: m_xy_distance(xy_distance), m_max_move(max_move), m_radius_sample_resolution(radius_sample_resolution)
|
|
{
|
|
for (std::size_t layer_nr = 0; layer_nr < object.layers().size(); ++layer_nr)
|
|
{
|
|
const Layer* layer = object.get_layer(layer_nr);
|
|
m_layer_outlines.push_back(ExPolygons());
|
|
ExPolygons& outline = m_layer_outlines.back();
|
|
for (const ExPolygon& poly : layer->lslices) {
|
|
poly.simplify(scale_(m_radius_sample_resolution), &outline);
|
|
}
|
|
|
|
if (layer_nr == 0)
|
|
m_layer_outlines_below.push_back(outline);
|
|
else
|
|
m_layer_outlines_below.push_back(union_ex(m_layer_outlines_below.end()[-1], outline));
|
|
}
|
|
}
|
|
|
|
const ExPolygons& TreeSupportData::get_collision(coordf_t radius, size_t layer_nr) const
|
|
{
|
|
profiler.tic();
|
|
radius = ceil_radius(radius);
|
|
RadiusLayerPair key{radius, layer_nr};
|
|
const auto it = m_collision_cache.find(key);
|
|
const ExPolygons& collision = it != m_collision_cache.end() ? it->second : calculate_collision(key);
|
|
profiler.stage_add(STAGE_get_collision, true);
|
|
return collision;
|
|
}
|
|
|
|
const ExPolygons& TreeSupportData::get_avoidance(coordf_t radius, size_t layer_nr) const
|
|
{
|
|
profiler.tic();
|
|
radius = ceil_radius(radius);
|
|
RadiusLayerPair key{radius, layer_nr};
|
|
const auto it = m_avoidance_cache.find(key);
|
|
const ExPolygons& avoidance = it != m_avoidance_cache.end() ? it->second : calculate_avoidance(key);
|
|
|
|
profiler.stage_add(STAGE_GET_AVOIDANCE, true);
|
|
return avoidance;
|
|
}
|
|
|
|
Polygons TreeSupportData::get_contours(size_t layer_nr) const
|
|
{
|
|
Polygons contours;
|
|
for (const ExPolygon expoly : m_layer_outlines[layer_nr]) {
|
|
contours.push_back(expoly.contour);
|
|
}
|
|
|
|
return contours;
|
|
}
|
|
|
|
Polygons TreeSupportData::get_contours_with_holes(size_t layer_nr) const
|
|
{
|
|
Polygons contours;
|
|
for (const ExPolygon expoly : m_layer_outlines[layer_nr]) {
|
|
for(int i=0;i<expoly.num_contours();i++)
|
|
contours.push_back(expoly.contour_or_hole(i));
|
|
}
|
|
return contours;
|
|
}
|
|
|
|
coordf_t TreeSupportData::ceil_radius(coordf_t radius) const
|
|
{
|
|
#if 0
|
|
size_t factor = (size_t)(radius / m_radius_sample_resolution);
|
|
coordf_t remains = radius - m_radius_sample_resolution * factor;
|
|
if (remains > EPSILON) {
|
|
return radius + m_radius_sample_resolution - remains;
|
|
}
|
|
else {
|
|
return radius;
|
|
}
|
|
#else
|
|
coordf_t resolution = m_radius_sample_resolution;
|
|
return ceil(radius / resolution) * resolution;
|
|
#endif
|
|
}
|
|
|
|
const ExPolygons& TreeSupportData::calculate_collision(const RadiusLayerPair& key) const
|
|
{
|
|
const auto& radius = key.first;
|
|
const auto& layer_nr = key.second;
|
|
|
|
assert(layer_nr < m_layer_outlines.size());
|
|
|
|
ExPolygons collision_areas = std::move(offset_ex(m_layer_outlines[layer_nr], scale_(radius)));
|
|
const auto ret = m_collision_cache.insert({ key, std::move(collision_areas) });
|
|
return ret.first->second;
|
|
}
|
|
|
|
const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& key) const
|
|
{
|
|
const auto& radius = key.first;
|
|
const auto& layer_idx = key.second;
|
|
#if 0
|
|
if (layer_idx == 0)
|
|
{
|
|
m_avoidance_cache[key] = get_collision(radius, 0);
|
|
return m_avoidance_cache[key];
|
|
}
|
|
|
|
// Avoidance for a given layer depends on all layers beneath it so could have very deep recursion depths if
|
|
// called at high layer heights. We can limit the reqursion depth to N by checking if the layer N
|
|
// below the current one exists and if not, forcing the calculation of that layer. This may cause another recursion
|
|
// if the layer at 2N below the current one but we won't exceed our limit unless there are N*N uncalculated layers
|
|
// below our current one.
|
|
constexpr auto max_recursion_depth = 100;
|
|
// Check if we would exceed the recursion limit by trying to process this layer
|
|
if (layer_nr >= max_recursion_depth
|
|
&& m_avoidance_cache.find({radius, layer_nr - max_recursion_depth}) == m_avoidance_cache.end())
|
|
{
|
|
// Force the calculation of the layer `max_recursion_depth` below our current one, ignoring the result.
|
|
get_avoidance(radius, layer_nr - max_recursion_depth);
|
|
}
|
|
|
|
ExPolygons avoidance_areas = std::move(offset_ex(get_avoidance(radius, layer_nr - 1), scale_(-m_max_move)));
|
|
const ExPolygons& collision = get_collision(radius, layer_nr);
|
|
avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end());
|
|
avoidance_areas = std::move(union_ex(avoidance_areas));
|
|
const auto ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)});
|
|
assert(ret.second);
|
|
#else
|
|
ExPolygons avoidance_areas = std::move(offset_ex(m_layer_outlines_below[layer_idx], scale_(m_xy_distance+radius)));
|
|
const auto ret = m_avoidance_cache.insert({ key, std::move(avoidance_areas) });
|
|
assert(ret.second);
|
|
#endif
|
|
return ret.first->second;
|
|
}
|
|
|
|
} //namespace Slic3r
|