WIP Tree Supports: Added anchors at the starts of tree support loops,

extruding the support loops in consistent (CCW) orientation,
always starting with the anchor.
This commit is contained in:
Vojtech Bubnik 2022-08-30 17:23:20 +02:00
parent 0cb2036509
commit eacacc7317
3 changed files with 220 additions and 6 deletions

View file

@ -179,6 +179,13 @@ private:
ExtrusionRole m_role;
};
class ExtrusionPathOriented : public ExtrusionPath
{
public:
ExtrusionPathOriented(ExtrusionRole role, double mm3_per_mm, float width, float height) : ExtrusionPath(role, mm3_per_mm, width, height) {}
virtual bool can_reverse() const { return false; }
};
typedef std::vector<ExtrusionPath> ExtrusionPaths;
// Single continuous extrusion path, possibly with varying extrusion thickness, extrusion height or bridging / non bridging.
@ -330,12 +337,12 @@ inline void extrusion_paths_append(ExtrusionPaths &dst, Polylines &&polylines, E
polylines.clear();
}
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height)
inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, const Polylines &polylines, ExtrusionRole role, double mm3_per_mm, float width, float height, bool can_reverse = true)
{
dst.reserve(dst.size() + polylines.size());
for (const Polyline &polyline : polylines)
if (polyline.is_valid()) {
ExtrusionPath *extrusion_path = new ExtrusionPath(role, mm3_per_mm, width, height);
ExtrusionPath* extrusion_path = can_reverse ? new ExtrusionPath(role, mm3_per_mm, width, height) : new ExtrusionPathOriented(role, mm3_per_mm, width, height);
dst.push_back(extrusion_path);
extrusion_path->polyline = polyline;
}

View file

@ -8,6 +8,8 @@
#include "Point.hpp"
#include "MutablePolygon.hpp"
#include <clipper/clipper_z.hpp>
#include <cmath>
#include <memory>
#include <boost/log/trivial.hpp>
@ -3232,6 +3234,207 @@ static inline void fill_expolygons_generate_paths(
fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow);
}
static inline void tree_supports_generate_paths(
ExtrusionEntitiesPtr &dst,
const Polygons &polygons,
const Flow &flow)
{
// Offset expolygon inside, returns number of expolygons collected (0 or 1).
// Vertices of output paths are marked with Z = source contour index of the expoly.
// Vertices at the intersection of source contours are marked with Z = -1.
auto shrink_expolygon_with_contour_idx = [](const Slic3r::ExPolygon &expoly, const float delta, ClipperLib::JoinType joinType, double miterLimit, ClipperLib_Z::Paths &out) -> int
{
assert(delta > 0);
auto append_paths_with_z = [](ClipperLib::Paths &src, coord_t contour_idx, ClipperLib_Z::Paths &dst) {
dst.reserve(next_highest_power_of_2(dst.size() + src.size()));
for (const ClipperLib::Path &contour : src) {
ClipperLib_Z::Path tmp;
tmp.reserve(contour.size());
for (const Point &p : contour)
tmp.emplace_back(p.x(), p.y(), contour_idx);
dst.emplace_back(std::move(tmp));
}
};
// 1) Offset the outer contour.
ClipperLib_Z::Paths contours;
{
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(delta * 0.005);
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths contours_raw;
co.Execute(contours_raw, - delta);
if (contours_raw.empty())
// No need to try to offset the holes.
return 0;
append_paths_with_z(contours_raw, 0, contours);
}
if (expoly.holes.empty()) {
// No need to subtract holes from the offsetted expolygon, we are done.
append(out, std::move(contours));
} else {
// 2) Offset the holes one by one, collect the offsetted holes.
ClipperLib_Z::Paths holes;
{
for (const Polygon &hole : expoly.holes) {
ClipperLib::ClipperOffset co;
if (joinType == jtRound)
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(delta * 0.005);
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out2;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.Execute(out2, delta);
append_paths_with_z(out2, 1 + (&hole - expoly.holes.data()), holes);
}
}
// 3) Subtract holes from the contours.
if (holes.empty()) {
// No hole remaining after an offset. Just copy the outer contour.
append(out, std::move(contours));
} else {
// Negative offset. There is a chance, that the offsetted hole intersects the outer contour.
// Subtract the offsetted holes from the offsetted contours.
ClipperLib_Z::Clipper clipper;
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) {
//pt.z() = std::max(std::max(e1bot.z(), e1top.z()), std::max(e2bot.z(), e2top.z()));
// Just mark the intersection.
pt.z() = -1;
});
clipper.AddPaths(contours, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(holes, ClipperLib_Z::ptClip, true);
ClipperLib_Z::Paths output;
clipper.Execute(ClipperLib_Z::ctDifference, output, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
if (! output.empty()) {
append(out, std::move(output));
} else {
// The offsetted holes have eaten up the offsetted outer contour.
return 0;
}
}
}
return 1;
};
const double spacing = flow.scaled_spacing();
// Clip the sheath path to avoid the extruder to get exactly on the first point of the loop.
const double clip_length = spacing * 0.15;
const double anchor_length = spacing * 6.;
ClipperLib_Z::Paths anchor_candidates;
for (ExPolygon &expoly : closing_ex(polygons, float(SCALED_EPSILON), float(SCALED_EPSILON + 0.5*flow.scaled_width()))) {
// Try to produce one more perimeter to place the seam anchor.
// First genrate a 2nd perimeter loop as a source for anchor candidates.
// The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection.
anchor_candidates.clear();
shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates);
// Orient all contours CCW.
for (auto &path : anchor_candidates)
if (ClipperLib_Z::Area(path) < 0)
std::reverse(path.begin(), path.end());
// Draw the perimeters.
Polylines polylines;
polylines.reserve(expoly.holes.size() + 1);
for (size_t idx_loop = 0; idx_loop <= expoly.holes.size(); ++ idx_loop) {
// Open the loop with a seam.
const Polygon &loop = idx_loop == 0 ? expoly.contour : expoly.holes[idx_loop - 1];
Polyline pl(loop.points);
// Orient all contours CCW.
if (loop.area() < 0)
pl.reverse();
pl.points.emplace_back(pl.points.front());
pl.clip_end(clip_length);
if (pl.size() < 2)
continue;
// Find the foot of the seam point on anchor_candidates. Only pick an anchor point that was created by offsetting the source contour.
ClipperLib_Z::Path *closest_contour = nullptr;
Vec2d closest_point;
int closest_point_idx = -1;
double closest_point_t;
double d2min = std::numeric_limits<double>::max();
Vec2d seam_pt = pl.back().cast<double>();
for (ClipperLib_Z::Path &path : anchor_candidates)
for (int i = 0; i < path.size(); ++ i) {
int j = next_idx_modulo(i, path);
if (path[i].z() == idx_loop || path[j].z() == idx_loop) {
Vec2d pi(path[i].x(), path[i].y());
Vec2d pj(path[j].x(), path[j].y());
Vec2d v = pj - pi;
Vec2d w = seam_pt - pi;
auto l2 = v.squaredNorm();
auto t = std::clamp((l2 == 0) ? 0 : v.dot(w) / l2, 0., 1.);
if ((path[i].z() == idx_loop || t > EPSILON) && (path[j].z() == idx_loop || t < 1. - EPSILON)) {
// Closest point.
Vec2d fp = pi + v * t;
double d2 = (fp - seam_pt).squaredNorm();
if (d2 < d2min) {
d2min = d2;
closest_contour = &path;
closest_point = fp;
closest_point_idx = i;
closest_point_t = t;
}
}
}
}
if (d2min < sqr(flow.scaled_width() * 3.)) {
// Try to cut an anchor from the closest_contour.
// Both closest_contour and pl are CCW oriented.
pl.points.emplace_back(closest_point.cast<coord_t>());
const ClipperLib_Z::Path &path = *closest_contour;
double remaining_length = anchor_length - (seam_pt - closest_point).norm();
int i = closest_point_idx;
int j = next_idx_modulo(i, *closest_contour);
Vec2d pi(path[i].x(), path[i].y());
Vec2d pj(path[j].x(), path[j].y());
Vec2d v = pj - pi;
double l = v.norm();
if (remaining_length < (1. - closest_point_t) * l) {
// Just trim the current line.
pl.points.emplace_back((closest_point + v * (remaining_length / l)).cast<coord_t>());
} else {
// Take the rest of the current line, continue with the other lines.
pl.points.emplace_back(path[j].x(), path[j].y());
pi = pj;
for (i = j; path[i].z() == idx_loop && remaining_length > 0; i = j, pi = pj) {
j = next_idx_modulo(i, path);
pj = Vec2d(path[j].x(), path[j].y());
v = pj - pi;
l = v.norm();
if (i == closest_point_idx) {
// Back at the first segment. Most likely this should not happen and we may end the anchor.
break;
}
if (remaining_length <= l) {
pl.points.emplace_back((pi + v * (remaining_length / l)).cast<coord_t>());
break;
}
pl.points.emplace_back(path[j].x(), path[j].y());
remaining_length -= l;
}
}
}
// Start with the anchor.
pl.reverse();
polylines.emplace_back(std::move(pl));
}
extrusion_entities_append_paths(dst, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height(),
// Disable reversal of the path, always start with the anchor, always print CCW.
false);
}
}
static inline void fill_expolygons_with_sheath_generate_paths(
ExtrusionEntitiesPtr &dst,
const Polygons &polygons,
@ -3245,7 +3448,12 @@ static inline void fill_expolygons_with_sheath_generate_paths(
if (polygons.empty())
return;
if (! with_sheath) {
if (with_sheath) {
if (density == 0) {
tree_supports_generate_paths(dst, polygons, flow);
return;
}
} else {
fill_expolygons_generate_paths(dst, closing_ex(polygons, float(SCALED_EPSILON)), filler, density, role, flow);
return;
}
@ -3277,8 +3485,7 @@ static inline void fill_expolygons_with_sheath_generate_paths(
}
extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
// Fill in the rest.
if (density > 0)
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
if (no_sort && ! eec->empty())
dst.emplace_back(eec.release());
}

View file

@ -1102,7 +1102,7 @@ void TreeSupport::generateInitialAreas(
;
const size_t support_roof_layers = mesh_group_settings.support_roof_enable ? (mesh_group_settings.support_roof_height + mesh_config.layer_height / 2) / mesh_config.layer_height : 0;
const bool roof_enabled = support_roof_layers != 0;
const bool force_tip_to_roof = (mesh_config.min_radius * mesh_config.min_radius * M_PI > mesh_group_settings.minimum_roof_area) && roof_enabled;
const bool force_tip_to_roof = sqr<double>(mesh_config.min_radius) * M_PI > mesh_group_settings.minimum_roof_area && roof_enabled;
//FIXME mesh_group_settings.support_angle does not apply to enforcers and also it does not apply to automatic support angle (by half the external perimeter width).
const coord_t max_overhang_speed = (mesh_group_settings.support_angle < 0.5 * M_PI) ? (coord_t)(tan(mesh_group_settings.support_angle) * mesh_config.layer_height) : std::numeric_limits<coord_t>::max();
const size_t max_overhang_insert_lag = std::max((size_t)round_up_divide(mesh_config.xy_distance, max_overhang_speed / 2), 2 * mesh_config.z_distance_top_layers); // cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. The 2*z_distance_delta is only a catch for when the support angle is very high.