NEW: enable lightning infill pattern for model and tree support

Change-Id: I6e2cbfdd30f8d222f88301ed0c8cc89e21cfdc24
(cherry picked from commit ddfee7c069cfc42685be509d48b8c609e1dc0cfc)
This commit is contained in:
xiang.zeng 2022-07-06 12:16:37 +08:00 committed by Lane.Wei
parent f331d5998e
commit 9cf95696a4
22 changed files with 1014 additions and 321 deletions

View file

@ -11,6 +11,8 @@
#include "I18N.hpp"
#include <libnest2d/backends/libslic3r/geometries.hpp>
#include "Fill/FillBase.hpp"
#define _L(s) Slic3r::I18N::translate(s)
@ -283,6 +285,49 @@ static void draw_layer_mst
svg.draw(lines, "blue", coord_t(scale_(0.05)));
svg.draw_outline(outline, "yellow");
}
static void draw_two_overhangs_to_svg(TreeSupportLayer* ts_layer, const ExPolygons& overhangs1, const ExPolygons& overhangs2)
{
if (overhangs1.empty() && overhangs2.empty())
return;
BoundingBox bbox1 = get_extents(overhangs1);
BoundingBox bbox2 = get_extents(overhangs2);
bbox1.merge(bbox2);
SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "two_overhangs"), bbox1);
if (!svg.is_opened()) return;
svg.draw(union_ex(overhangs1), "blue");
svg.draw(union_ex(overhangs2), "red");
}
static void draw_polylines(TreeSupportLayer* ts_layer, Polylines& polylines)
{
if (polylines.empty())
return;
BoundingBox bbox = get_extents(polylines);
SVG svg(get_svg_filename(std::to_string(ts_layer->print_z), "lightnings"), bbox);
if (!svg.is_opened()) return;
int id = 0;
for (Polyline& pline : polylines)
{
int i1, i2;
for (size_t i = 0; i < pline.size() - 1; i++)
{
i1 = i;
i2 = i + 1;
svg.draw(Line(pline.points[i1], pline.points[i2]), "blue");
svg.draw(pline.points[i1], "red");
id++;
svg.draw_text(pline.points[i1], std::to_string(id).c_str(), "black", 1);
}
svg.draw(pline.points[i2], "red");
id++;
svg.draw_text(pline.points[i2], std::to_string(id).c_str(), "black", 1);
}
}
#endif
// Move point from inside polygon if distance>0, outside if distance<0.
@ -641,7 +686,11 @@ TreeSupport::TreeSupport(PrintObject& object, const SlicingParameters &slicing_p
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.base_fill_pattern =
#if HAS_LIGHTNING_INFILL
support_pattern == smpLightning ? ipLightning :
#endif
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);
@ -1507,7 +1556,7 @@ void TreeSupport::generate_toolpaths()
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 (with_infill && layer_id > 0 && m_support_params.base_fill_pattern != ipLightning) {
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;
@ -1541,6 +1590,67 @@ void TreeSupport::generate_toolpaths()
}
}
}
if (with_infill && m_support_params.base_fill_pattern == ipLightning)
{
double print_z = ts_layer->print_z;
if (printZ_to_lightninglayer.find(print_z) == printZ_to_lightninglayer.end())
continue;
//TODO:
//1.the second parameter of convertToLines seems to decide how long the lightning should be trimmed from its root, so that the root wont overlap/detach the support contour.
// whether current value works correctly remained to be tested
//2.related to previous one, that lightning roots need to be trimed more when support has multiple walls
//3.function connect_infill() and variable 'params' helps create connection pattern along contours between two lightning roots,
// strengthen lightnings while it may make support harder. decide to enable it or not. if yes, proper values for params are remained to be tested
auto& lightning_layer = generator->getTreesForLayer(printZ_to_lightninglayer[print_z]);
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);
ExPolygons areas = offset_ex(ts_layer->base_areas, -flow.scaled_spacing());
for (auto& area : areas)
{
Polylines polylines = lightning_layer.convertToLines(to_polygons(area), 0);
for (auto itr = polylines.begin(); itr != polylines.end();)
{
if (itr->length() < scale_(1.0))
itr = polylines.erase(itr);
else
itr++;
}
Polylines opt_polylines;
#if 1
//this wont create connection patterns along contours
append(opt_polylines, chain_polylines(std::move(polylines)));
#else
//this will create connection patterns along contours
FillParams params;
params.anchor_length = float(Fill::infill_anchor * 0.01 * flow.spacing());
params.anchor_length_max = Fill::infill_anchor_max;
params.anchor_length = std::min(params.anchor_length, params.anchor_length_max);
Fill::connect_infill(std::move(polylines), area, opt_polylines, flow.spacing(), params);
#endif
extrusion_entities_append_paths(ts_layer->support_fills.entities, opt_polylines, erSupportMaterial,
float(flow.mm3_per_mm()), float(flow.width()), float(flow.height()));
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
std::string prefix = "./SVG/";
std::string suffix = ".svg";
std::string name = prefix + "trees_polyline" + "_" + std::to_string(ts_layer->print_z) /*+ "_" + std::to_string(rand_num)*/ + suffix;
BoundingBox bbox = get_extents(ts_layer->base_areas);
SVG svg(name, bbox);
svg.draw(ts_layer->base_areas, "blue");
svg.draw(generator->Overhangs()[printZ_to_lightninglayer[print_z]], "red");
for (auto& line : opt_polylines)
{
svg.draw(line, "yellow");
}
#endif
}
}
// sort extrusions to reduce travel, also make sure walls go before infills
chain_and_reorder_extrusion_entities(ts_layer->support_fills.entities);
@ -1895,6 +2005,15 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
const coordf_t line_width = config.support_line_width;
const coordf_t line_width_scaled = scale_(line_width);
const bool with_lightning_infill = config.tree_support_with_infill.value && config.support_base_pattern.value == smpLightning;
coordf_t support_extrusion_width = config.support_line_width.value > 0 ? config.support_line_width : config.line_width;
const size_t wall_count = config.tree_support_wall_count.value;
const PrintObjectConfig& object_config = m_object->config();
auto m_support_material_flow = support_material_flow(m_object, float(m_slicing_params.layer_height));
coordf_t support_spacing = object_config.support_base_pattern_spacing.value + m_support_material_flow.spacing();
coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
// 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()),
@ -2060,100 +2179,167 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
});
#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; }
if (with_lightning_infill)
{
const bool global_lightning_infill = true;
std::vector<Polygons> contours;
std::vector<Polygons> overhangs;
for (int layer_nr = 1; layer_nr < m_object->layer_count(); layer_nr++) {
if (print->canceled()) break;
const std::vector<Node*>& curr_layer_nodes = contact_nodes[layer_nr];
TreeSupportLayer* ts_layer = m_object->get_tree_support_layer(layer_nr + m_raft_layers);
assert(ts_layer != nullptr);
// skip if current layer has no points. This fixes potential crash in get_collision (see jira BBL001-355)
if (curr_layer_nodes.empty()) continue;
if (ts_layer->height < EPSILON) continue;
if (ts_layer->area_groups.empty()) continue;
ExPolygons& base_areas = ts_layer->base_areas;
int layer_nr_lower = layer_nr - 1;
for (layer_nr_lower; layer_nr_lower >= 0; layer_nr_lower--) {
if (!m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->area_groups.empty()) break;
}
TreeSupportLayer* lower_layer = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers);
ExPolygons& base_areas_lower = m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers)->base_areas;
ExPolygons overhang;
if (layer_nr_lower == 0)
continue;
if (global_lightning_infill)
{
//search overhangs globally
overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas));
}
else
{
//search overhangs only on floating islands
for (auto& base_area : base_areas)
for (auto& hole : base_area.holes)
{
Polygon rev_hole = hole;
rev_hole.make_counter_clockwise();
ExPolygons ex_hole = to_expolygons(ExPolygon(rev_hole));
for (auto& other_area : base_areas)
//if (&other_area != &base_area)
ex_hole = std::move(diff_ex(ex_hole, other_area));
overhang = std::move(union_ex(overhang, ex_hole));
}
overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width))));
}
overhangs.emplace_back(to_polygons(overhang));
contours.emplace_back(to_polygons(base_areas_lower)); //cant guarantee for 100% success probability, infill fails sometimes
printZ_to_lightninglayer[lower_layer->print_z] = overhangs.size() - 1;
#ifdef SUPPORT_TREE_DEBUG_TO_SVG
draw_two_overhangs_to_svg(m_object->get_tree_support_layer(layer_nr_lower + m_raft_layers), base_areas_lower, to_expolygons(overhangs.back()));
#endif
}
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());
generator = std::make_unique<FillLightning::Generator>(m_object, contours, overhangs, support_density);
}
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);
else if (!has_infill) {
// move the holes to contour so they can be well supported
// 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}});
// 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;
}
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}});
if (dist2 > max_dist) {
max_dist = dist2;
pt_far_on_poly = from;
}
break;
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) {
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); });
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});
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
@ -2163,38 +2349,38 @@ void TreeSupport::draw_circles(const std::vector<std::vector<Node*>>& contact_no
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);
}
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"});
}
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;
}
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)