Update the codes to 01.01.00.10 for the formal release

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. ...
This commit is contained in:
lane.wei 2022-07-22 17:46:10 +08:00 committed by Lane.Wei
parent e1528e4299
commit e9e4d75877
267 changed files with 10326 additions and 32228 deletions

View file

@ -9,15 +9,11 @@
#include "SVG.hpp"
#include "ShortestPath.hpp"
#include "I18N.hpp"
#include <libnest2d/backends/libslic3r/geometries.hpp>
#define _L(s) Slic3r::I18N::translate(s)
#define SQUARE_SUPPORT 0
#if SQUARE_SUPPORT
#define CIRCLE_RESOLUTION 4 // 100 //The number of vertices in each circle.
#else
#define CIRCLE_RESOLUTION 100 //The number of vertices in each circle.
#endif
#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
@ -31,7 +27,7 @@
#define TAU (2.0 * M_PI)
#define NO_INDEX (std::numeric_limits<unsigned int>::max())
//#define SUPPORT_TREE_DEBUG_TO_SVG
#define SUPPORT_TREE_DEBUG_TO_SVG
namespace Slic3r
{
@ -225,14 +221,18 @@ static void draw_contours_and_nodes_to_svg
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(get_svg_filename(std::to_string(layer_nr), name_prefix), bbox);
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(union_ex(overhangs), colors[0]);
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]);
@ -661,6 +661,8 @@ void TreeSupport::detect_object_overhangs()
// 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();
@ -932,7 +934,7 @@ void TreeSupport::detect_object_overhangs()
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);
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);
@ -1081,9 +1083,6 @@ void TreeSupport::detect_object_overhangs()
}
}
total_overhang_area = 0;
max_overhang_area = 0;
total_overhang_layer_cnt = 0;
for (int layer_nr = 0; layer_nr < m_object->layer_count(); layer_nr++) {
if (m_object->print()->canceled())
break;
@ -1095,6 +1094,10 @@ void TreeSupport::detect_object_overhangs()
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
@ -1102,15 +1105,9 @@ void TreeSupport::detect_object_overhangs()
//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);
}
}
if (!ts_layer->overhang_areas.empty()) {
float a = area(ts_layer->overhang_areas);
total_overhang_area += a;
max_overhang_area = std::max(max_overhang_area, a);
total_overhang_layer_cnt++;
}
}
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
@ -1825,17 +1822,25 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
{
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++)
{
#if SQUARE_SUPPORT
double angle = (double)i / CIRCLE_RESOLUTION * TAU + TAU/8.0;
#else
double angle = (double)i / CIRCLE_RESOLUTION * TAU;
#endif
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));
}
@ -1850,7 +1855,6 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
}
// generate areas
const coordf_t circle_side_length = 2 * branch_radius * sin(M_PI / CIRCLE_RESOLUTION); //Side length of a regular polygon.
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;
@ -2024,16 +2028,12 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
base_areas = std::move(diff_ex(base_areas, roof_areas));
base_areas = std::move(diff_ex(base_areas, roof_1st_layer));
#if SQUARE_SUPPORT
if (m_object->print()->config().enable_arc_fitting.value == false) {
// simplify support contours if arc fitting is disabled
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);
}
for (auto &area : base_areas) { area.simplify(scale_(line_width / 2), &base_areas_simplified, SimplifyMethodDP); }
base_areas = std::move(base_areas_simplified);
}
#endif
//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)
@ -2073,15 +2073,13 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
}),
expoly->holes.end());
}
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
draw_contours_and_nodes_to_svg(layer_nr, base_areas, roof_areas, roof_1st_layer, {}, {}, "circles", { "base","roof","roof1st" });
#endif
}
});
#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();
@ -2099,18 +2097,15 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
max_dist = dist2;
pt_far_on_poly = from;
}
if (dist2 < dist_thresh) {
return true;
}
if (dist2 < dist_thresh) { return true; }
}
}
return false;
};
std::map<const Polygon *, int> holeDepth;
std::map<const Polygon *, Point> holeDiretions;
std::map<const Polygon *, Point> holeFarPoints;
for (int layer_nr = m_object->layer_count()-1; layer_nr >0; layer_nr--) {
// 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());
@ -2127,47 +2122,88 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
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;
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 == 1 || area_group.second == 2) continue;
const auto base_area = area_group.first;
for (const auto &hole : base_area->holes) {
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 == 1 || area_group.second == 2) continue;
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);
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]));
holeDepth.insert({&base_area_lower.holes.back(), 15});
holeDiretions.insert({&base_area_lower.holes.back(), direction});
holeFarPoints.insert({&base_area_lower.holes.back(), pt_far_on_poly});
holePropagationInfos.insert({&base_area_lower.holes.back(), {25, direction, pt_far_on_poly}});
}
break;
} else if (holeDepth.find(&hole) != holeDepth.end() && holeDepth[&hole] > 0 && base_area_lower.contour.contains(holeFarPoints[&hole])) {
} 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;
hole_lower.translate(holeDiretions[&hole]);
Point farPoint = holeFarPoints[&hole] + holeDiretions[&hole];
{
base_area_lower.holes.push_back(std::move(hole_lower));
holeDepth.insert({&base_area_lower.holes.back(), holeDepth[&hole]-1});
holeDiretions.insert({&base_area_lower.holes.back(), holeDiretions[&hole]});
holeFarPoints.insert({&base_area_lower.holes.back(), farPoint});
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();
@ -2257,6 +2293,7 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
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);
}
}
@ -2290,10 +2327,6 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
for (Node* p_node : layer_contact_nodes)
{
const Node& node = *p_node;
if (node.type == ePolygon) {
// polygon node do not merge or move
continue;
}
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.
{
@ -2305,6 +2338,10 @@ void TreeSupport::drop_nodes(std::vector<std::vector<Node*>>& contact_nodes)
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
@ -2720,7 +2757,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
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 && g_config_thick_bridges) {
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.
@ -2729,17 +2766,20 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
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);
m_highest_overhang_layer = 0;
// 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;
const ExPolygons &overhang = m_object->get_tree_support_layer(layer_nr + m_raft_layers + z_distance_top_layers)->overhang_areas;
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;
@ -2757,7 +2797,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
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;
contact_nodes[layer_nr].emplace_back(contact_node);
curr_nodes.emplace_back(contact_node);
continue;
}
@ -2783,7 +2823,7 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
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);
contact_nodes[layer_nr].emplace_back(contact_node);
curr_nodes.emplace_back(contact_node);
added = true;
}
}
@ -2793,38 +2833,81 @@ void TreeSupport::generate_contact_points(std::vector<std::vector<TreeSupport::N
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 = { bbox.min, bounding_box_middle(bbox), bbox.max };
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);
contact_nodes[layer_nr].emplace_back(contact_node);
curr_nodes.emplace_back(contact_node);
}
}
// 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);
contact_nodes[layer_nr].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)