mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-14 18:27:58 -06:00
Improve fuzzy skin with modifier (#6759)
* Pass all compatible regions to perimeter generator * Simplify & unify fuzzify detection * Simplify `to_thick_polyline` * Group regions by fuzzy skin settings * Initial code structure of multi-regional fuzzy skin * Determine fuzzy type by all compatible regions * Add fuzzy region debug * Implement the line split algorithm * Do splitted fuzzy in classic mode * Disable debug macros * Fix infinit loop issue when segment points are out of order * Fix path connection * Implement splitted fuzzy in Arachne mode
This commit is contained in:
parent
3d3633f110
commit
9d3d242453
12 changed files with 707 additions and 92 deletions
316
src/libslic3r/Algorithm/LineSplit.cpp
Normal file
316
src/libslic3r/Algorithm/LineSplit.cpp
Normal file
|
@ -0,0 +1,316 @@
|
||||||
|
#include "LineSplit.hpp"
|
||||||
|
|
||||||
|
#include "AABBTreeLines.hpp"
|
||||||
|
#include "SVG.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
//#define DEBUG_SPLIT_LINE
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace Algorithm {
|
||||||
|
|
||||||
|
#ifdef DEBUG_SPLIT_LINE
|
||||||
|
static std::atomic<std::uint32_t> g_dbg_id = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Z for points from clip polygon
|
||||||
|
static constexpr auto CLIP_IDX = std::numeric_limits<ClipperLib_Z::cInt>::max();
|
||||||
|
|
||||||
|
static void cb_split_line(const ClipperZUtils::ZPoint& e1bot,
|
||||||
|
const ClipperZUtils::ZPoint& e1top,
|
||||||
|
const ClipperZUtils::ZPoint& e2bot,
|
||||||
|
const ClipperZUtils::ZPoint& e2top,
|
||||||
|
ClipperZUtils::ZPoint& pt)
|
||||||
|
{
|
||||||
|
coord_t zs[4]{e1bot.z(), e1top.z(), e2bot.z(), e2top.z()};
|
||||||
|
std::sort(zs, zs + 4);
|
||||||
|
pt.z() = -(zs[0] + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_src(const ClipperZUtils::ZPoint& p) { return p.z() >= 0 && p.z() != CLIP_IDX; }
|
||||||
|
static bool is_clip(const ClipperZUtils::ZPoint& p) { return p.z() == CLIP_IDX; }
|
||||||
|
static bool is_new(const ClipperZUtils::ZPoint& p) { return p.z() < 0; }
|
||||||
|
static size_t to_src_idx(const ClipperZUtils::ZPoint& p)
|
||||||
|
{
|
||||||
|
assert(!is_clip(p));
|
||||||
|
if (is_src(p)) {
|
||||||
|
return p.z();
|
||||||
|
} else {
|
||||||
|
return -p.z() - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Point to_point(const ClipperZUtils::ZPoint& p) { return {p.x(), p.y()}; }
|
||||||
|
|
||||||
|
using SplitNode = std::vector<ClipperZUtils::ZPath*>;
|
||||||
|
|
||||||
|
// Note: p cannot be one of the line end
|
||||||
|
static bool point_on_line(const Point& p, const Line& l)
|
||||||
|
{
|
||||||
|
// Check collinear
|
||||||
|
const auto d1 = l.b - l.a;
|
||||||
|
const auto d2 = p - l.a;
|
||||||
|
if (d1.x() * d2.y() != d1.y() * d2.x()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure p is in between line.a and line.b
|
||||||
|
if (l.a.x() != l.b.x())
|
||||||
|
return (p.x() > l.a.x()) == (p.x() < l.b.x());
|
||||||
|
else
|
||||||
|
return (p.y() > l.a.y()) == (p.y() < l.b.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& clip, bool closed)
|
||||||
|
{
|
||||||
|
assert(path.size() > 1);
|
||||||
|
#ifdef DEBUG_SPLIT_LINE
|
||||||
|
const auto dbg_path_points = ClipperZUtils::from_zpath<false>(path);
|
||||||
|
BoundingBox dbg_bbox = get_extents(clip);
|
||||||
|
dbg_bbox.merge(get_extents(dbg_path_points));
|
||||||
|
dbg_bbox.offset(scale_(1.));
|
||||||
|
const std::uint32_t dbg_id = g_dbg_id++;
|
||||||
|
{
|
||||||
|
::Slic3r::SVG svg(debug_out_path("do_split_line_%d_input.svg", dbg_id).c_str(), dbg_bbox);
|
||||||
|
svg.draw(clip, "red", 0.5);
|
||||||
|
svg.draw_outline(clip, "red");
|
||||||
|
svg.draw(Polyline{dbg_path_points});
|
||||||
|
svg.draw(dbg_path_points);
|
||||||
|
svg.Close();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ClipperZUtils::ZPaths intersections;
|
||||||
|
// Perform an intersection
|
||||||
|
{
|
||||||
|
// Convert clip polygon to closed contours
|
||||||
|
ClipperZUtils::ZPaths clip_path;
|
||||||
|
for (const auto& exp : clip) {
|
||||||
|
clip_path.emplace_back(ClipperZUtils::to_zpath<false>(exp.contour.points, CLIP_IDX));
|
||||||
|
for (const Polygon& hole : exp.holes)
|
||||||
|
clip_path.emplace_back(ClipperZUtils::to_zpath<false>(hole.points, CLIP_IDX));
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperLib_Z::Clipper zclipper;
|
||||||
|
zclipper.PreserveCollinear(true);
|
||||||
|
zclipper.ZFillFunction(cb_split_line);
|
||||||
|
zclipper.AddPaths(clip_path, ClipperLib_Z::ptClip, true);
|
||||||
|
zclipper.AddPath(path, ClipperLib_Z::ptSubject, false);
|
||||||
|
ClipperLib_Z::PolyTree polytree;
|
||||||
|
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||||
|
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), intersections);
|
||||||
|
}
|
||||||
|
if (intersections.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_SPLIT_LINE
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (const auto& segment : intersections) {
|
||||||
|
::Slic3r::SVG svg(debug_out_path("do_split_line_%d_seg_%d.svg", dbg_id, i).c_str(), dbg_bbox);
|
||||||
|
svg.draw(clip, "red", 0.5);
|
||||||
|
svg.draw_outline(clip, "red");
|
||||||
|
const auto segment_points = ClipperZUtils::from_zpath<false>(segment);
|
||||||
|
svg.draw(Polyline{segment_points});
|
||||||
|
for (const ClipperZUtils::ZPoint& p : segment) {
|
||||||
|
const auto z = p.z();
|
||||||
|
if (is_new(p)) {
|
||||||
|
svg.draw(to_point(p), "yellow");
|
||||||
|
} else if (is_clip(p)) {
|
||||||
|
svg.draw(to_point(p), "red");
|
||||||
|
} else {
|
||||||
|
svg.draw(to_point(p), "black");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svg.Close();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Connect the intersection back to the remaining loop
|
||||||
|
std::vector<SplitNode> split_chain;
|
||||||
|
{
|
||||||
|
// AABBTree over source paths.
|
||||||
|
// Only built if necessary, that is if any of the clipped segment has first point came from clip polygon,
|
||||||
|
// and we need to find out which source edge that point came from.
|
||||||
|
AABBTreeLines::LinesDistancer<Line> aabb_tree;
|
||||||
|
const auto resolve_clip_point = [&path, &aabb_tree](ClipperZUtils::ZPoint& zp) {
|
||||||
|
if (!is_clip(zp)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aabb_tree.get_lines().empty()) {
|
||||||
|
Lines lines;
|
||||||
|
lines.reserve(path.size() - 1);
|
||||||
|
for (auto it = path.begin() + 1; it != path.end(); ++it) {
|
||||||
|
lines.emplace_back(to_point(it[-1]), to_point(*it));
|
||||||
|
}
|
||||||
|
aabb_tree = AABBTreeLines::LinesDistancer(lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Point p = to_point(zp);
|
||||||
|
const auto possible_edges = aabb_tree.all_lines_in_radius(p, SCALED_EPSILON);
|
||||||
|
assert(!possible_edges.empty());
|
||||||
|
for (const size_t l : possible_edges) {
|
||||||
|
// Check if the point is on the line
|
||||||
|
const Line line(to_point(path[l]), to_point(path[l + 1]));
|
||||||
|
if (p == line.a) {
|
||||||
|
zp.z() = path[l].z();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (p == line.b) {
|
||||||
|
zp.z() = path[l + 1].z();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (point_on_line(p, line)) {
|
||||||
|
zp.z() = -(path[l].z() + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is_clip(zp)) {
|
||||||
|
// Too bad! Couldn't find the src edge, so we just pick the first one and hope it works
|
||||||
|
zp.z() = -(path[possible_edges[0]].z() + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
split_chain.assign(path.size(), {});
|
||||||
|
for (ClipperZUtils::ZPath& segment : intersections) {
|
||||||
|
assert(segment.size() >= 2);
|
||||||
|
// Resolve all clip points
|
||||||
|
std::for_each(segment.begin(), segment.end(), resolve_clip_point);
|
||||||
|
|
||||||
|
// Ensure the point order in segment
|
||||||
|
std::sort(segment.begin(), segment.end(), [&path](const ClipperZUtils::ZPoint& a, const ClipperZUtils::ZPoint& b) -> bool {
|
||||||
|
if (is_new(a) && is_new(b) && a.z() == b.z()) {
|
||||||
|
// Make sure a point is closer to the src point than b
|
||||||
|
const auto src = to_point(path[-a.z() - 1]);
|
||||||
|
return (to_point(a) - src).squaredNorm() < (to_point(b) - src).squaredNorm();
|
||||||
|
}
|
||||||
|
const auto a_idx = to_src_idx(a);
|
||||||
|
const auto b_idx = to_src_idx(b);
|
||||||
|
if (a_idx == b_idx) {
|
||||||
|
// On same line, prefer the src point first
|
||||||
|
return is_src(a);
|
||||||
|
} else {
|
||||||
|
return a_idx < b_idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chain segment back to the original path
|
||||||
|
ClipperZUtils::ZPoint& front = segment.front();
|
||||||
|
const ClipperZUtils::ZPoint* previous_src_point;
|
||||||
|
if (is_src(front)) {
|
||||||
|
// The segment starts with a point from src path, which means apart from the last point,
|
||||||
|
// all other points on this segment should come from the src path or the clip polygon
|
||||||
|
|
||||||
|
// Connect the segment to the src path
|
||||||
|
auto& node = split_chain[front.z()];
|
||||||
|
node.insert(node.begin(), &segment);
|
||||||
|
|
||||||
|
previous_src_point = &front;
|
||||||
|
} else if (is_new(front)) {
|
||||||
|
const auto id = -front.z() - 1; // Get the src path index
|
||||||
|
const ClipperZUtils::ZPoint& src_p = path[id]; // Get the corresponding src point
|
||||||
|
const auto dist2 = (front - src_p).block<2, 1>(0,0).squaredNorm(); // Distance between the src point and current point
|
||||||
|
// Find the place on the src line that current point should lie on
|
||||||
|
auto& node = split_chain[id];
|
||||||
|
auto it = std::find_if(node.begin(), node.end(), [dist2, &src_p](const ClipperZUtils::ZPath* p) {
|
||||||
|
const ClipperZUtils::ZPoint& p_front = p->front();
|
||||||
|
if (is_src(p_front)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto dist2_2 = (p_front - src_p).block<2, 1>(0, 0).squaredNorm();
|
||||||
|
return dist2_2 > dist2;
|
||||||
|
});
|
||||||
|
// Insert this split
|
||||||
|
node.insert(it, &segment);
|
||||||
|
|
||||||
|
previous_src_point = &src_p;
|
||||||
|
} else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once we figured out the start point, we can then normalize the remaining points on the segment
|
||||||
|
for (ClipperZUtils::ZPoint& p : segment) {
|
||||||
|
assert(!is_new(p) || p == front || p == segment.back()); // Only the first and last point can be a new intersection
|
||||||
|
if (is_src(p)) {
|
||||||
|
previous_src_point = &p;
|
||||||
|
} else if (is_clip(p)) {
|
||||||
|
// Treat point from clip polygon as new point
|
||||||
|
p.z() = -(previous_src_point->z() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we reconstruct the final path by connecting splits
|
||||||
|
SplittedLine result;
|
||||||
|
size_t idx = 0;
|
||||||
|
while (idx < split_chain.size()) {
|
||||||
|
const ClipperZUtils::ZPoint& p = path[idx];
|
||||||
|
const auto& node = split_chain[idx];
|
||||||
|
if (node.empty()) {
|
||||||
|
result.emplace_back(to_point(p), false, idx);
|
||||||
|
idx++;
|
||||||
|
} else {
|
||||||
|
if (!is_src(node.front()->front())) {
|
||||||
|
const auto& last = result.back();
|
||||||
|
if (result.empty() || last.get_src_index() != to_src_idx(p)) {
|
||||||
|
result.emplace_back(to_point(p), false, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto segment : node) {
|
||||||
|
for (const ClipperZUtils::ZPoint& sp : *segment) {
|
||||||
|
assert(!is_clip(sp.z()));
|
||||||
|
result.emplace_back(to_point(sp), true, sp.z());
|
||||||
|
}
|
||||||
|
result.back().clipped = false; // Mark the end of the clipped line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the next start point
|
||||||
|
const auto back = result.back().src_idx;
|
||||||
|
if (back < 0) {
|
||||||
|
auto next_idx = -back - 1;
|
||||||
|
if (next_idx == idx) {
|
||||||
|
next_idx++;
|
||||||
|
} else if (split_chain[next_idx].empty()) {
|
||||||
|
next_idx++;
|
||||||
|
}
|
||||||
|
idx = next_idx;
|
||||||
|
} else {
|
||||||
|
result.pop_back();
|
||||||
|
idx = back;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUG_SPLIT_LINE
|
||||||
|
{
|
||||||
|
::Slic3r::SVG svg(debug_out_path("do_split_line_%d_result.svg", dbg_id).c_str(), dbg_bbox);
|
||||||
|
svg.draw(clip, "red", 0.5);
|
||||||
|
svg.draw_outline(clip, "red");
|
||||||
|
for (auto it = result.begin() + 1; it != result.end(); ++it) {
|
||||||
|
const auto& a = *(it - 1);
|
||||||
|
const auto& b = *it;
|
||||||
|
const bool clipped = a.clipped;
|
||||||
|
const Line l(a.p, b.p);
|
||||||
|
svg.draw(l, clipped ? "yellow" : "black");
|
||||||
|
}
|
||||||
|
svg.Close();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (closed) {
|
||||||
|
// Remove last point which was duplicated
|
||||||
|
result.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Algorithm
|
||||||
|
} // Slic3r
|
71
src/libslic3r/Algorithm/LineSplit.hpp
Normal file
71
src/libslic3r/Algorithm/LineSplit.hpp
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#ifndef SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_
|
||||||
|
#define SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_
|
||||||
|
|
||||||
|
#include "clipper2/clipper.core.h"
|
||||||
|
#include "ClipperZUtils.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace Algorithm {
|
||||||
|
|
||||||
|
struct SplitLineJunction
|
||||||
|
{
|
||||||
|
Point p;
|
||||||
|
|
||||||
|
// true if the line between this point and the next point is inside the clip polygon (or on the edge of the clip polygon)
|
||||||
|
bool clipped;
|
||||||
|
|
||||||
|
// Index from the original input.
|
||||||
|
// - If this junction is presented in the source polygon/polyline, this is the index of the point with in the source;
|
||||||
|
// - if this point in a new point that caused by the intersection, this will be -(1+index of the first point of the source line involved in this intersection);
|
||||||
|
// - if this junction came from the clip polygon, it will be treated as new point.
|
||||||
|
int64_t src_idx;
|
||||||
|
|
||||||
|
SplitLineJunction(const Point& p, bool clipped, int64_t src_idx)
|
||||||
|
: p(p)
|
||||||
|
, clipped(clipped)
|
||||||
|
, src_idx(src_idx) {}
|
||||||
|
|
||||||
|
bool is_src() const { return src_idx >= 0; }
|
||||||
|
size_t get_src_index() const
|
||||||
|
{
|
||||||
|
if (is_src()) {
|
||||||
|
return src_idx;
|
||||||
|
} else {
|
||||||
|
return -src_idx - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using SplittedLine = std::vector<SplitLineJunction>;
|
||||||
|
|
||||||
|
SplittedLine do_split_line(const ClipperZUtils::ZPath& path, const ExPolygons& clip, bool closed);
|
||||||
|
|
||||||
|
// Return the splitted line, or empty if no intersection found
|
||||||
|
template<class PathType>
|
||||||
|
SplittedLine split_line(const PathType& path, const ExPolygons& clip, bool closed)
|
||||||
|
{
|
||||||
|
if (path.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the input path into an open ZPath
|
||||||
|
ClipperZUtils::ZPath p;
|
||||||
|
p.reserve(path.size() + closed ? 1 : 0);
|
||||||
|
ClipperLib_Z::cInt z = 0;
|
||||||
|
for (const auto& point : path) {
|
||||||
|
p.emplace_back(point.x(), point.y(), z);
|
||||||
|
z++;
|
||||||
|
}
|
||||||
|
if (closed) {
|
||||||
|
// duplicate the first point at the end to make a closed path open
|
||||||
|
p.emplace_back(p.front());
|
||||||
|
p.back().z() = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
return do_split_line(p, clip, closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Algorithm
|
||||||
|
} // Slic3r
|
||||||
|
|
||||||
|
#endif /* SRC_LIBSLIC3R_ALGORITHM_LINE_SPLIT_HPP_ */
|
|
@ -40,6 +40,10 @@ struct ExtrusionJunction
|
||||||
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
|
ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index);
|
||||||
|
|
||||||
bool operator==(const ExtrusionJunction& other) const;
|
bool operator==(const ExtrusionJunction& other) const;
|
||||||
|
|
||||||
|
coord_t x() const { return p.x(); }
|
||||||
|
coord_t y() const { return p.y(); }
|
||||||
|
coord_t z() const { return w; }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)
|
inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b)
|
||||||
|
|
|
@ -194,27 +194,8 @@ struct ExtrusionLine
|
||||||
double area() const;
|
double area() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions)
|
template<class PathType>
|
||||||
{
|
static inline Slic3r::ThickPolyline to_thick_polyline(const PathType &path)
|
||||||
assert(line_junctions.size() >= 2);
|
|
||||||
Slic3r::ThickPolyline out;
|
|
||||||
out.points.emplace_back(line_junctions.front().p);
|
|
||||||
out.width.emplace_back(line_junctions.front().w);
|
|
||||||
out.points.emplace_back(line_junctions[1].p);
|
|
||||||
out.width.emplace_back(line_junctions[1].w);
|
|
||||||
|
|
||||||
auto it_prev = line_junctions.begin() + 1;
|
|
||||||
for (auto it = line_junctions.begin() + 2; it != line_junctions.end(); ++it) {
|
|
||||||
out.points.emplace_back(it->p);
|
|
||||||
out.width.emplace_back(it_prev->w);
|
|
||||||
out.width.emplace_back(it->w);
|
|
||||||
it_prev = it;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path &path)
|
|
||||||
{
|
{
|
||||||
assert(path.size() >= 2);
|
assert(path.size() >= 2);
|
||||||
Slic3r::ThickPolyline out;
|
Slic3r::ThickPolyline out;
|
||||||
|
|
|
@ -30,6 +30,8 @@ set(lisbslic3r_sources
|
||||||
AABBTreeLines.hpp
|
AABBTreeLines.hpp
|
||||||
AABBMesh.hpp
|
AABBMesh.hpp
|
||||||
AABBMesh.cpp
|
AABBMesh.cpp
|
||||||
|
Algorithm/LineSplit.hpp
|
||||||
|
Algorithm/LineSplit.cpp
|
||||||
Algorithm/PathSorting.hpp
|
Algorithm/PathSorting.hpp
|
||||||
Algorithm/RegionExpansion.hpp
|
Algorithm/RegionExpansion.hpp
|
||||||
Algorithm/RegionExpansion.cpp
|
Algorithm/RegionExpansion.cpp
|
||||||
|
|
|
@ -557,6 +557,8 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d
|
||||||
{ return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
|
{ return PolyTreeToExPolygons(expolygons_offset_pt(expolygons, delta, joinType, miterLimit)); }
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||||
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
||||||
|
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType, double miterLimit)
|
||||||
|
{ return PolyTreeToExPolygons(expolygons_offset_pt(surfaces, delta, joinType, miterLimit)); }
|
||||||
|
|
||||||
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
Polygons offset2(const ExPolygons &expolygons, const float delta1, const float delta2, ClipperLib::JoinType joinType, double miterLimit)
|
||||||
{
|
{
|
||||||
|
|
|
@ -344,6 +344,7 @@ Slic3r::ExPolygons offset_ex(const Slic3r::Polygons &polygons, const float delta
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygon &expolygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
|
Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit);
|
||||||
// BBS
|
// BBS
|
||||||
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
inline Slic3r::ExPolygons offset_ex(const Slic3r::Polygon &polygon, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||||
{
|
{
|
||||||
|
|
|
@ -182,10 +182,6 @@ void Layer::make_perimeters()
|
||||||
&& config.detect_thin_wall == other_config.detect_thin_wall
|
&& config.detect_thin_wall == other_config.detect_thin_wall
|
||||||
&& config.infill_wall_overlap == other_config.infill_wall_overlap
|
&& config.infill_wall_overlap == other_config.infill_wall_overlap
|
||||||
&& config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap
|
&& config.top_bottom_infill_wall_overlap == other_config.top_bottom_infill_wall_overlap
|
||||||
&& config.fuzzy_skin == other_config.fuzzy_skin
|
|
||||||
&& config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness
|
|
||||||
&& config.fuzzy_skin_point_distance == other_config.fuzzy_skin_point_distance
|
|
||||||
&& config.fuzzy_skin_first_layer == other_config.fuzzy_skin_first_layer
|
|
||||||
&& config.seam_slope_type == other_config.seam_slope_type
|
&& config.seam_slope_type == other_config.seam_slope_type
|
||||||
&& config.seam_slope_conditional == other_config.seam_slope_conditional
|
&& config.seam_slope_conditional == other_config.seam_slope_conditional
|
||||||
&& config.scarf_angle_threshold == other_config.scarf_angle_threshold
|
&& config.scarf_angle_threshold == other_config.scarf_angle_threshold
|
||||||
|
@ -208,7 +204,7 @@ void Layer::make_perimeters()
|
||||||
|
|
||||||
if (layerms.size() == 1) { // optimization
|
if (layerms.size() == 1) { // optimization
|
||||||
(*layerm)->fill_surfaces.surfaces.clear();
|
(*layerm)->fill_surfaces.surfaces.clear();
|
||||||
(*layerm)->make_perimeters((*layerm)->slices, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons);
|
(*layerm)->make_perimeters((*layerm)->slices, {*layerm}, &(*layerm)->fill_surfaces, &(*layerm)->fill_no_overlap_expolygons);
|
||||||
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
|
(*layerm)->fill_expolygons = to_expolygons((*layerm)->fill_surfaces.surfaces);
|
||||||
} else {
|
} else {
|
||||||
SurfaceCollection new_slices;
|
SurfaceCollection new_slices;
|
||||||
|
@ -232,7 +228,7 @@ void Layer::make_perimeters()
|
||||||
SurfaceCollection fill_surfaces;
|
SurfaceCollection fill_surfaces;
|
||||||
//BBS
|
//BBS
|
||||||
ExPolygons fill_no_overlap;
|
ExPolygons fill_no_overlap;
|
||||||
layerm_config->make_perimeters(new_slices, &fill_surfaces, &fill_no_overlap);
|
layerm_config->make_perimeters(new_slices, layerms, &fill_surfaces, &fill_no_overlap);
|
||||||
|
|
||||||
// assign fill_surfaces to each layer
|
// assign fill_surfaces to each layer
|
||||||
if (!fill_surfaces.surfaces.empty()) {
|
if (!fill_surfaces.surfaces.empty()) {
|
||||||
|
|
|
@ -78,7 +78,7 @@ public:
|
||||||
void slices_to_fill_surfaces_clipped();
|
void slices_to_fill_surfaces_clipped();
|
||||||
void prepare_fill_surfaces();
|
void prepare_fill_surfaces();
|
||||||
//BBS
|
//BBS
|
||||||
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap);
|
void make_perimeters(const SurfaceCollection &slices, const LayerRegionPtrs &compatible_regions, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap);
|
||||||
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
void process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered);
|
||||||
double infill_area_threshold() const;
|
double infill_area_threshold() const;
|
||||||
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
// Trim surfaces by trimming polygons. Used by the elephant foot compensation at the 1st layer.
|
||||||
|
|
|
@ -68,7 +68,7 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap)
|
void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRegionPtrs &compatible_regions, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap)
|
||||||
{
|
{
|
||||||
this->perimeters.clear();
|
this->perimeters.clear();
|
||||||
this->thin_fills.clear();
|
this->thin_fills.clear();
|
||||||
|
@ -85,6 +85,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
|
||||||
PerimeterGenerator g(
|
PerimeterGenerator g(
|
||||||
// input:
|
// input:
|
||||||
&slices,
|
&slices,
|
||||||
|
&compatible_regions,
|
||||||
this->layer()->height,
|
this->layer()->height,
|
||||||
this->flow(frPerimeter),
|
this->flow(frPerimeter),
|
||||||
®ion_config,
|
®ion_config,
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include "libslic3r/AABBTreeLines.hpp"
|
#include "libslic3r/AABBTreeLines.hpp"
|
||||||
|
#include "Print.hpp"
|
||||||
|
#include "Algorithm/LineSplit.hpp"
|
||||||
static const int overhang_sampling_number = 6;
|
static const int overhang_sampling_number = 6;
|
||||||
static const double narrow_loop_length_threshold = 10;
|
static const double narrow_loop_length_threshold = 10;
|
||||||
static const double min_degree_gap = 0.1;
|
static const double min_degree_gap = 0.1;
|
||||||
|
@ -29,6 +31,8 @@ static const int max_overhang_degree = overhang_sampling_number - 1;
|
||||||
//we think it's small detail area and will generate smaller line width for it
|
//we think it's small detail area and will generate smaller line width for it
|
||||||
static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE = 0.22;
|
static constexpr double SMALLER_EXT_INSET_OVERLAP_TOLERANCE = 0.22;
|
||||||
|
|
||||||
|
//#define DEBUG_FUZZY
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
// Produces a random value between 0 and 1. Thread-safe.
|
// Produces a random value between 0 and 1. Thread-safe.
|
||||||
|
@ -51,13 +55,11 @@ public:
|
||||||
bool is_smaller_width_perimeter;
|
bool is_smaller_width_perimeter;
|
||||||
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
||||||
unsigned short depth;
|
unsigned short depth;
|
||||||
// Should this contur be fuzzyfied on path generation?
|
|
||||||
bool fuzzify;
|
|
||||||
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
||||||
std::vector<PerimeterGeneratorLoop> children;
|
std::vector<PerimeterGeneratorLoop> children;
|
||||||
|
|
||||||
PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool fuzzify, bool is_small_width_perimeter = false) :
|
PerimeterGeneratorLoop(const Polygon &polygon, unsigned short depth, bool is_contour, bool is_small_width_perimeter = false) :
|
||||||
polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth), fuzzify(fuzzify) {}
|
polygon(polygon), is_contour(is_contour), is_smaller_width_perimeter(is_small_width_perimeter), depth(depth) {}
|
||||||
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
|
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
|
||||||
bool is_external() const { return this->depth == 0; }
|
bool is_external() const { return this->depth == 0; }
|
||||||
// An island, which may have holes, but it does not have another internal island.
|
// An island, which may have holes, but it does not have another internal island.
|
||||||
|
@ -65,23 +67,30 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
// Thanks Cura developers for this function.
|
// Thanks Cura developers for this function.
|
||||||
static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuzzy_skin_point_distance)
|
static void fuzzy_polyline(Points& poly, bool closed, const FuzzySkinConfig& cfg)
|
||||||
{
|
{
|
||||||
const double min_dist_between_points = fuzzy_skin_point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
|
const double min_dist_between_points = cfg.point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
|
||||||
const double range_random_point_dist = fuzzy_skin_point_distance / 2.;
|
const double range_random_point_dist = cfg.point_distance / 2.;
|
||||||
double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point
|
double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point
|
||||||
Point* p0 = &poly.points.back();
|
Point* p0 = &poly.back();
|
||||||
Points out;
|
Points out;
|
||||||
out.reserve(poly.points.size());
|
out.reserve(poly.size());
|
||||||
for (Point &p1 : poly.points)
|
for (Point &p1 : poly)
|
||||||
{ // 'a' is the (next) new point between p0 and p1
|
{
|
||||||
|
if (!closed) {
|
||||||
|
// Skip the first point for open path
|
||||||
|
closed = true;
|
||||||
|
p0 = &p1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 'a' is the (next) new point between p0 and p1
|
||||||
Vec2d p0p1 = (p1 - *p0).cast<double>();
|
Vec2d p0p1 = (p1 - *p0).cast<double>();
|
||||||
double p0p1_size = p0p1.norm();
|
double p0p1_size = p0p1.norm();
|
||||||
double p0pa_dist = dist_left_over;
|
double p0pa_dist = dist_left_over;
|
||||||
for (; p0pa_dist < p0p1_size;
|
for (; p0pa_dist < p0p1_size;
|
||||||
p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist)
|
p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist)
|
||||||
{
|
{
|
||||||
double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness;
|
double r = random_value() * (cfg.thickness * 2.) - cfg.thickness;
|
||||||
out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>());
|
out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>());
|
||||||
}
|
}
|
||||||
dist_left_over = p0pa_dist - p0p1_size;
|
dist_left_over = p0pa_dist - p0p1_size;
|
||||||
|
@ -95,14 +104,14 @@ static void fuzzy_polygon(Polygon &poly, double fuzzy_skin_thickness, double fuz
|
||||||
-- point_idx;
|
-- point_idx;
|
||||||
}
|
}
|
||||||
if (out.size() >= 3)
|
if (out.size() >= 3)
|
||||||
poly.points = std::move(out);
|
poly = std::move(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thanks Cura developers for this function.
|
// Thanks Cura developers for this function.
|
||||||
static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy_skin_thickness, double fuzzy_skin_point_dist)
|
static void fuzzy_extrusion_line(std::vector<Arachne::ExtrusionJunction>& ext_lines, const FuzzySkinConfig& cfg)
|
||||||
{
|
{
|
||||||
const double min_dist_between_points = fuzzy_skin_point_dist * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
|
const double min_dist_between_points = cfg.point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value
|
||||||
const double range_random_point_dist = fuzzy_skin_point_dist / 2.;
|
const double range_random_point_dist = cfg.point_distance / 2.;
|
||||||
double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point
|
double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point
|
||||||
|
|
||||||
auto* p0 = &ext_lines.front();
|
auto* p0 = &ext_lines.front();
|
||||||
|
@ -119,7 +128,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy
|
||||||
double p0p1_size = p0p1.norm();
|
double p0p1_size = p0p1.norm();
|
||||||
double p0pa_dist = dist_left_over;
|
double p0pa_dist = dist_left_over;
|
||||||
for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) {
|
for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) {
|
||||||
double r = random_value() * (fuzzy_skin_thickness * 2.) - fuzzy_skin_thickness;
|
double r = random_value() * (cfg.thickness * 2.) - cfg.thickness;
|
||||||
out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
|
out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
|
||||||
}
|
}
|
||||||
dist_left_over = p0pa_dist - p0p1_size;
|
dist_left_over = p0pa_dist - p0p1_size;
|
||||||
|
@ -138,7 +147,7 @@ static void fuzzy_extrusion_line(Arachne::ExtrusionLine& ext_lines, double fuzzy
|
||||||
out.front().p = out.back().p;
|
out.front().p = out.back().p;
|
||||||
|
|
||||||
if (out.size() >= 3)
|
if (out.size() >= 3)
|
||||||
ext_lines.junctions = std::move(out);
|
ext_lines = std::move(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
using PerimeterGeneratorLoops = std::vector<PerimeterGeneratorLoop>;
|
using PerimeterGeneratorLoops = std::vector<PerimeterGeneratorLoop>;
|
||||||
|
@ -453,6 +462,24 @@ static bool detect_steep_overhang(const PrintRegionConfig *config,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const size_t loop_idx, const bool is_contour)
|
||||||
|
{
|
||||||
|
const auto fuzziy_type = config.type;
|
||||||
|
|
||||||
|
if (fuzziy_type == FuzzySkinType::None) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!config.fuzzy_first_layer && layer_id <= 0) {
|
||||||
|
// Do not fuzzy first layer unless told to
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls;
|
||||||
|
const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls);
|
||||||
|
|
||||||
|
return is_contour ? fuzzify_contours : fuzzify_holes;
|
||||||
|
}
|
||||||
|
|
||||||
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls,
|
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls,
|
||||||
bool &steep_overhang_contour, bool &steep_overhang_hole)
|
bool &steep_overhang_contour, bool &steep_overhang_hole)
|
||||||
{
|
{
|
||||||
|
@ -480,9 +507,6 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
||||||
} else {
|
} else {
|
||||||
loop_role = loop.is_contour? elrDefault : elrHole;
|
loop_role = loop.is_contour? elrDefault : elrHole;
|
||||||
}
|
}
|
||||||
|
|
||||||
// detect overhanging/bridging perimeters
|
|
||||||
ExtrusionPaths paths;
|
|
||||||
|
|
||||||
// BBS: get lower polygons series, width, mm3_per_mm
|
// BBS: get lower polygons series, width, mm3_per_mm
|
||||||
const std::vector<Polygons> *lower_polygons_series;
|
const std::vector<Polygons> *lower_polygons_series;
|
||||||
|
@ -510,24 +534,113 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
||||||
extrusion_mm3_per_mm = perimeter_generator.mm3_per_mm();
|
extrusion_mm3_per_mm = perimeter_generator.mm3_per_mm();
|
||||||
extrusion_width = perimeter_generator.perimeter_flow.width();
|
extrusion_width = perimeter_generator.perimeter_flow.width();
|
||||||
}
|
}
|
||||||
|
const Polygon& polygon = *([&perimeter_generator, &loop, &fuzzified]() ->const Polygon* {
|
||||||
|
const auto& regions = perimeter_generator.regions_by_fuzzify;
|
||||||
|
if (regions.size() == 1) { // optimization
|
||||||
|
const auto& config = regions.begin()->first;
|
||||||
|
const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop.depth, loop.is_contour);
|
||||||
|
if (!fuzzify) {
|
||||||
|
return &loop.polygon;
|
||||||
|
}
|
||||||
|
|
||||||
|
fuzzified = loop.polygon;
|
||||||
|
fuzzy_polyline(fuzzified.points, true, config);
|
||||||
|
return &fuzzified;
|
||||||
|
}
|
||||||
|
|
||||||
const Polygon &polygon = loop.fuzzify ? fuzzified : loop.polygon;
|
// Find all affective regions
|
||||||
if (loop.fuzzify) {
|
std::vector<std::pair<const FuzzySkinConfig&, const ExPolygons&>> fuzzified_regions;
|
||||||
|
fuzzified_regions.reserve(regions.size());
|
||||||
|
for (const auto & region : regions) {
|
||||||
|
if (should_fuzzify(region.first, perimeter_generator.layer_id, loop.depth, loop.is_contour)) {
|
||||||
|
fuzzified_regions.emplace_back(region.first, region.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fuzzified_regions.empty()) {
|
||||||
|
return &loop.polygon;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_FUZZY
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (const auto & r : fuzzified_regions) {
|
||||||
|
BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces);
|
||||||
|
bbox.offset(scale_(1.));
|
||||||
|
::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id, loop.is_contour ? 0 : 1, loop.depth, i).c_str(), bbox);
|
||||||
|
svg.draw_outline(perimeter_generator.slices->surfaces);
|
||||||
|
svg.draw_outline(loop.polygon, "green");
|
||||||
|
svg.draw(r.second, "red", 0.5);
|
||||||
|
svg.draw_outline(r.second, "red");
|
||||||
|
svg.Close();
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Split the loops into lines with different config, and fuzzy them separately
|
||||||
fuzzified = loop.polygon;
|
fuzzified = loop.polygon;
|
||||||
fuzzy_polygon(fuzzified, scaled<float>(perimeter_generator.config->fuzzy_skin_thickness.value), scaled<float>(perimeter_generator.config->fuzzy_skin_point_distance.value));
|
for (const auto& r : fuzzified_regions) {
|
||||||
}
|
const auto splitted = Algorithm::split_line(fuzzified, r.second, true);
|
||||||
|
if (splitted.empty()) {
|
||||||
|
// No intersection, skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzzy splitted polygon
|
||||||
|
if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
|
||||||
|
// The entire polygon is fuzzified
|
||||||
|
fuzzy_polyline(fuzzified.points, true, r.first);
|
||||||
|
} else {
|
||||||
|
Points segment;
|
||||||
|
segment.reserve(splitted.size());
|
||||||
|
fuzzified.points.clear();
|
||||||
|
|
||||||
|
const auto fuzzy_current_segment = [&segment, &fuzzified, &r]() {
|
||||||
|
fuzzified.points.push_back(segment.front());
|
||||||
|
const auto back = segment.back();
|
||||||
|
fuzzy_polyline(segment, false, r.first);
|
||||||
|
fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end());
|
||||||
|
fuzzified.points.push_back(back);
|
||||||
|
segment.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& p : splitted) {
|
||||||
|
if (p.clipped) {
|
||||||
|
segment.push_back(p.p);
|
||||||
|
} else {
|
||||||
|
if (segment.empty()) {
|
||||||
|
fuzzified.points.push_back(p.p);
|
||||||
|
} else {
|
||||||
|
segment.push_back(p.p);
|
||||||
|
fuzzy_current_segment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!segment.empty()) {
|
||||||
|
// Close the loop
|
||||||
|
segment.push_back(splitted.front().p);
|
||||||
|
fuzzy_current_segment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &fuzzified;
|
||||||
|
}());
|
||||||
|
|
||||||
|
ExtrusionPaths paths;
|
||||||
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
|
if (perimeter_generator.config->detect_overhang_wall && perimeter_generator.layer_id > perimeter_generator.object_config->raft_layers) {
|
||||||
|
// detect overhanging/bridging perimeters
|
||||||
|
|
||||||
// get non 100% overhang paths by intersecting this loop with the grown lower slices
|
// get non 100% overhang paths by intersecting this loop with the grown lower slices
|
||||||
// prepare grown lower layer slices for overhang detection
|
// prepare grown lower layer slices for overhang detection
|
||||||
BoundingBox bbox(polygon.points);
|
BoundingBox bbox(polygon.points);
|
||||||
bbox.offset(SCALED_EPSILON);
|
bbox.offset(SCALED_EPSILON);
|
||||||
|
|
||||||
// Always reverse extrusion if use fuzzy skin: https://github.com/SoftFever/OrcaSlicer/pull/2413#issuecomment-1769735357
|
// Always reverse extrusion if use fuzzy skin: https://github.com/SoftFever/OrcaSlicer/pull/2413#issuecomment-1769735357
|
||||||
if (overhangs_reverse && perimeter_generator.config->fuzzy_skin != FuzzySkinType::None) {
|
if (overhangs_reverse && perimeter_generator.has_fuzzy_skin) {
|
||||||
if (loop.is_contour) {
|
if (loop.is_contour) {
|
||||||
steep_overhang_contour = true;
|
steep_overhang_contour = true;
|
||||||
} else if (perimeter_generator.config->fuzzy_skin != FuzzySkinType::External) {
|
} else if (perimeter_generator.has_fuzzy_hole) {
|
||||||
steep_overhang_hole = true;
|
steep_overhang_hole = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -548,7 +661,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
||||||
|
|
||||||
remain_polines = diff_pl({polygon}, lower_polygons_series_clipped);
|
remain_polines = diff_pl({polygon}, lower_polygons_series_clipped);
|
||||||
|
|
||||||
bool detect_overhang_degree = perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None;
|
bool detect_overhang_degree = perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && !perimeter_generator.has_fuzzy_skin;
|
||||||
|
|
||||||
if (!detect_overhang_degree) {
|
if (!detect_overhang_degree) {
|
||||||
if (!inside_polines.empty())
|
if (!inside_polines.empty())
|
||||||
|
@ -762,8 +875,6 @@ struct PerimeterGeneratorArachneExtrusion
|
||||||
Arachne::ExtrusionLine* extrusion = nullptr;
|
Arachne::ExtrusionLine* extrusion = nullptr;
|
||||||
// Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop.
|
// Indicates if closed ExtrusionLine is a contour or a hole. Used it only when ExtrusionLine is a closed loop.
|
||||||
bool is_contour = false;
|
bool is_contour = false;
|
||||||
// Should this extrusion be fuzzyfied on path generation?
|
|
||||||
bool fuzzify = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -866,8 +977,77 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||||
const bool is_external = extrusion->inset_idx == 0;
|
const bool is_external = extrusion->inset_idx == 0;
|
||||||
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
|
ExtrusionRole role = is_external ? erExternalPerimeter : erPerimeter;
|
||||||
|
|
||||||
if (pg_extrusion.fuzzify)
|
const auto& regions = perimeter_generator.regions_by_fuzzify;
|
||||||
fuzzy_extrusion_line(*extrusion, scaled<float>(perimeter_generator.config->fuzzy_skin_thickness.value), scaled<float>(perimeter_generator.config->fuzzy_skin_point_distance.value));
|
const bool is_contour = !extrusion->is_closed || pg_extrusion.is_contour;
|
||||||
|
if (regions.size() == 1) { // optimization
|
||||||
|
const auto& config = regions.begin()->first;
|
||||||
|
const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour);
|
||||||
|
if (fuzzify)
|
||||||
|
fuzzy_extrusion_line(extrusion->junctions, config);
|
||||||
|
} else {
|
||||||
|
// Find all affective regions
|
||||||
|
std::vector<std::pair<const FuzzySkinConfig&, const ExPolygons&>> fuzzified_regions;
|
||||||
|
fuzzified_regions.reserve(regions.size());
|
||||||
|
for (const auto& region : regions) {
|
||||||
|
if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) {
|
||||||
|
fuzzified_regions.emplace_back(region.first, region.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fuzzified_regions.empty()) {
|
||||||
|
// Split the loops into lines with different config, and fuzzy them separately
|
||||||
|
for (const auto& r : fuzzified_regions) {
|
||||||
|
const auto splitted = Algorithm::split_line(*extrusion, r.second, false);
|
||||||
|
if (splitted.empty()) {
|
||||||
|
// No intersection, skip
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fuzzy splitted extrusion
|
||||||
|
if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) {
|
||||||
|
// The entire polygon is fuzzified
|
||||||
|
fuzzy_extrusion_line(extrusion->junctions, r.first);
|
||||||
|
} else {
|
||||||
|
const auto current_ext = extrusion->junctions;
|
||||||
|
std::vector<Arachne::ExtrusionJunction> segment;
|
||||||
|
segment.reserve(current_ext.size());
|
||||||
|
extrusion->junctions.clear();
|
||||||
|
|
||||||
|
const auto fuzzy_current_segment = [&segment, extrusion, &r]() {
|
||||||
|
extrusion->junctions.push_back(segment.front());
|
||||||
|
const auto back = segment.back();
|
||||||
|
fuzzy_extrusion_line(segment, r.first);
|
||||||
|
extrusion->junctions.insert(extrusion->junctions.end(), segment.begin(), segment.end());
|
||||||
|
extrusion->junctions.push_back(back);
|
||||||
|
segment.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto to_ex_junction = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction {
|
||||||
|
Arachne::ExtrusionJunction res = current_ext[j.get_src_index()];
|
||||||
|
if (!j.is_src()) {
|
||||||
|
res.p = j.p;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& p : splitted) {
|
||||||
|
if (p.clipped) {
|
||||||
|
segment.push_back(to_ex_junction(p));
|
||||||
|
} else {
|
||||||
|
if (segment.empty()) {
|
||||||
|
extrusion->junctions.push_back(to_ex_junction(p));
|
||||||
|
} else {
|
||||||
|
segment.push_back(to_ex_junction(p));
|
||||||
|
fuzzy_current_segment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!segment.empty()) {
|
||||||
|
fuzzy_current_segment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ExtrusionPaths paths;
|
ExtrusionPaths paths;
|
||||||
// detect overhanging/bridging perimeters
|
// detect overhanging/bridging perimeters
|
||||||
|
@ -904,10 +1084,10 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||||
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
|
is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow);
|
||||||
|
|
||||||
// Always reverse extrusion if use fuzzy skin: https://github.com/SoftFever/OrcaSlicer/pull/2413#issuecomment-1769735357
|
// Always reverse extrusion if use fuzzy skin: https://github.com/SoftFever/OrcaSlicer/pull/2413#issuecomment-1769735357
|
||||||
if (overhangs_reverse && perimeter_generator.config->fuzzy_skin != FuzzySkinType::None) {
|
if (overhangs_reverse && perimeter_generator.has_fuzzy_skin) {
|
||||||
if (pg_extrusion.is_contour) {
|
if (pg_extrusion.is_contour) {
|
||||||
steep_overhang_contour = true;
|
steep_overhang_contour = true;
|
||||||
} else if (perimeter_generator.config->fuzzy_skin != FuzzySkinType::External) {
|
} else if (perimeter_generator.has_fuzzy_hole) {
|
||||||
steep_overhang_hole = true;
|
steep_overhang_hole = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -938,7 +1118,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
|
if (perimeter_generator.config->overhang_speed_classic && perimeter_generator.config->enable_overhang_speed && !perimeter_generator.has_fuzzy_skin) {
|
||||||
|
|
||||||
Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow;
|
Flow flow = is_external ? perimeter_generator.ext_perimeter_flow : perimeter_generator.perimeter_flow;
|
||||||
std::map<double, std::vector<Polygons>> clipper_serise;
|
std::map<double, std::vector<Polygons>> clipper_serise;
|
||||||
|
@ -1031,7 +1211,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
|
||||||
|
|
||||||
chain_and_reorder_extrusion_paths(paths, &start_point);
|
chain_and_reorder_extrusion_paths(paths, &start_point);
|
||||||
|
|
||||||
if (perimeter_generator.config->enable_overhang_speed && perimeter_generator.config->fuzzy_skin == FuzzySkinType::None) {
|
if (perimeter_generator.config->enable_overhang_speed && !perimeter_generator.has_fuzzy_skin) {
|
||||||
// BBS: filter the speed
|
// BBS: filter the speed
|
||||||
smooth_overhang_level(paths);
|
smooth_overhang_level(paths);
|
||||||
}
|
}
|
||||||
|
@ -1642,8 +1822,48 @@ static void reorient_perimeters(ExtrusionEntityCollection &entities, bool steep_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void group_region_by_fuzzify(PerimeterGenerator& g)
|
||||||
|
{
|
||||||
|
g.regions_by_fuzzify.clear();
|
||||||
|
g.has_fuzzy_skin = false;
|
||||||
|
g.has_fuzzy_hole = false;
|
||||||
|
|
||||||
|
std::unordered_map<FuzzySkinConfig, SurfacesPtr> regions;
|
||||||
|
for (auto region : *g.compatible_regions) {
|
||||||
|
const auto& region_config = region->region().config();
|
||||||
|
const FuzzySkinConfig cfg{
|
||||||
|
region_config.fuzzy_skin,
|
||||||
|
scaled<coord_t>(region_config.fuzzy_skin_thickness.value),
|
||||||
|
scaled<coord_t>(region_config.fuzzy_skin_point_distance.value),
|
||||||
|
region_config.fuzzy_skin_first_layer
|
||||||
|
};
|
||||||
|
auto& surfaces = regions[cfg];
|
||||||
|
for (const auto& surface : region->slices.surfaces) {
|
||||||
|
surfaces.push_back(&surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.type != FuzzySkinType::None) {
|
||||||
|
g.has_fuzzy_skin = true;
|
||||||
|
if (cfg.type != FuzzySkinType::External) {
|
||||||
|
g.has_fuzzy_hole = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regions.size() == 1) { // optimization
|
||||||
|
g.regions_by_fuzzify[regions.begin()->first] = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& it : regions) {
|
||||||
|
g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PerimeterGenerator::process_classic()
|
void PerimeterGenerator::process_classic()
|
||||||
{
|
{
|
||||||
|
group_region_by_fuzzify(*this);
|
||||||
|
|
||||||
// other perimeters
|
// other perimeters
|
||||||
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
|
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
|
||||||
coord_t perimeter_width = this->perimeter_flow.scaled_width();
|
coord_t perimeter_width = this->perimeter_flow.scaled_width();
|
||||||
|
@ -1716,7 +1936,7 @@ void PerimeterGenerator::process_classic()
|
||||||
|
|
||||||
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
||||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||||
//BBS: reorder the surface to reduce the travel time
|
//BBS: reorder the surface to reduce the travel time
|
||||||
ExPolygons surface_exp;
|
ExPolygons surface_exp;
|
||||||
for (const Surface &surface : all_surfaces)
|
for (const Surface &surface : all_surfaces)
|
||||||
|
@ -1845,32 +2065,29 @@ void PerimeterGenerator::process_classic()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const bool fuzzify_layer = (this->config->fuzzy_skin_first_layer || this->layer_id>0) && this->config->fuzzy_skin != FuzzySkinType::None;
|
|
||||||
const bool fuzzify_contours = fuzzify_layer && (i == 0 || this->config->fuzzy_skin == FuzzySkinType::AllWalls);
|
|
||||||
const bool fuzzify_holes = fuzzify_contours && (this->config->fuzzy_skin == FuzzySkinType::All || this->config->fuzzy_skin == FuzzySkinType::AllWalls);
|
|
||||||
for (const ExPolygon& expolygon : offsets) {
|
for (const ExPolygon& expolygon : offsets) {
|
||||||
// Outer contour may overlap with an inner contour,
|
// Outer contour may overlap with an inner contour,
|
||||||
// inner contour may overlap with another inner contour,
|
// inner contour may overlap with another inner contour,
|
||||||
// outer contour may overlap with itself.
|
// outer contour may overlap with itself.
|
||||||
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
|
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
|
||||||
// compensate for the depth of intersection.
|
// compensate for the depth of intersection.
|
||||||
contours[i].emplace_back(expolygon.contour, i, true, fuzzify_contours);
|
contours[i].emplace_back(expolygon.contour, i, true);
|
||||||
|
|
||||||
if (!expolygon.holes.empty()) {
|
if (!expolygon.holes.empty()) {
|
||||||
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
||||||
for (const Polygon& hole : expolygon.holes)
|
for (const Polygon& hole : expolygon.holes)
|
||||||
holes[i].emplace_back(hole, i, false, fuzzify_holes);
|
holes[i].emplace_back(hole, i, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: save perimeter loop which use smaller width
|
//BBS: save perimeter loop which use smaller width
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
for (const ExPolygon& expolygon : offsets_with_smaller_width) {
|
for (const ExPolygon& expolygon : offsets_with_smaller_width) {
|
||||||
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, fuzzify_contours, true));
|
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true, true));
|
||||||
if (!expolygon.holes.empty()) {
|
if (!expolygon.holes.empty()) {
|
||||||
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
holes[i].reserve(holes[i].size() + expolygon.holes.size());
|
||||||
for (const Polygon& hole : expolygon.holes)
|
for (const Polygon& hole : expolygon.holes)
|
||||||
holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, fuzzify_contours, true));
|
holes[i].emplace_back(PerimeterGeneratorLoop(hole, i, false, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2607,6 +2824,8 @@ void bringContoursToFront(std::vector<PerimeterGeneratorArachneExtrusion>& order
|
||||||
// "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling"
|
// "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling"
|
||||||
void PerimeterGenerator::process_arachne()
|
void PerimeterGenerator::process_arachne()
|
||||||
{
|
{
|
||||||
|
group_region_by_fuzzify(*this);
|
||||||
|
|
||||||
// other perimeters
|
// other perimeters
|
||||||
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
|
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
|
||||||
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
|
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
|
||||||
|
@ -2635,7 +2854,7 @@ void PerimeterGenerator::process_arachne()
|
||||||
|
|
||||||
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
process_no_bridge(all_surfaces, perimeter_spacing, ext_perimeter_width);
|
||||||
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
// BBS: don't simplify too much which influence arc fitting when export gcode if arc_fitting is enabled
|
||||||
double surface_simplify_resolution = (print_config->enable_arc_fitting && this->config->fuzzy_skin == FuzzySkinType::None) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
double surface_simplify_resolution = (print_config->enable_arc_fitting && !this->has_fuzzy_skin) ? 0.2 * m_scaled_resolution : m_scaled_resolution;
|
||||||
// we need to process each island separately because we might have different
|
// we need to process each island separately because we might have different
|
||||||
// extra perimeters for each one
|
// extra perimeters for each one
|
||||||
for (const Surface& surface : all_surfaces) {
|
for (const Surface& surface : all_surfaces) {
|
||||||
|
@ -2845,7 +3064,7 @@ void PerimeterGenerator::process_arachne()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& best_path = all_extrusions[best_candidate];
|
auto& best_path = all_extrusions[best_candidate];
|
||||||
ordered_extrusions.push_back({ best_path, best_path->is_contour(), false });
|
ordered_extrusions.push_back({ best_path, best_path->is_contour() });
|
||||||
processed[best_candidate] = true;
|
processed[best_candidate] = true;
|
||||||
for (size_t unlocked_idx : blocking[best_candidate])
|
for (size_t unlocked_idx : blocking[best_candidate])
|
||||||
blocked[unlocked_idx]--;
|
blocked[unlocked_idx]--;
|
||||||
|
@ -2857,21 +3076,6 @@ void PerimeterGenerator::process_arachne()
|
||||||
current_position = best_path->junctions.back().p; //Pick the other end from where we started.
|
current_position = best_path->junctions.back().p; //Pick the other end from where we started.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bool fuzzify_layer = (this->config->fuzzy_skin_first_layer || this->layer_id>0) && this->config->fuzzy_skin != FuzzySkinType::None;
|
|
||||||
if (fuzzify_layer) {
|
|
||||||
for (PerimeterGeneratorArachneExtrusion& extrusion : ordered_extrusions) {
|
|
||||||
if (this->config->fuzzy_skin == FuzzySkinType::AllWalls) {
|
|
||||||
extrusion.fuzzify = true;
|
|
||||||
} else if (extrusion.extrusion->inset_idx == 0) {
|
|
||||||
if (extrusion.extrusion->is_closed && this->config->fuzzy_skin == FuzzySkinType::External) {
|
|
||||||
extrusion.fuzzify = extrusion.is_contour;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
extrusion.fuzzify = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer
|
// printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer
|
||||||
if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer
|
if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer
|
||||||
|
|
|
@ -3,17 +3,50 @@
|
||||||
|
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "Layer.hpp"
|
||||||
#include "Flow.hpp"
|
#include "Flow.hpp"
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "SurfaceCollection.hpp"
|
#include "SurfaceCollection.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
struct FuzzySkinConfig
|
||||||
|
{
|
||||||
|
FuzzySkinType type;
|
||||||
|
coord_t thickness;
|
||||||
|
coord_t point_distance;
|
||||||
|
bool fuzzy_first_layer;
|
||||||
|
|
||||||
|
bool operator==(const FuzzySkinConfig& r) const
|
||||||
|
{
|
||||||
|
return type == r.type && thickness == r.thickness && point_distance == r.point_distance && fuzzy_first_layer == r.fuzzy_first_layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const FuzzySkinConfig& r) const { return !(*this == r); }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template<> struct hash<Slic3r::FuzzySkinConfig>
|
||||||
|
{
|
||||||
|
size_t operator()(const Slic3r::FuzzySkinConfig& c) const noexcept
|
||||||
|
{
|
||||||
|
std::size_t seed = std::hash<Slic3r::FuzzySkinType>{}(c.type);
|
||||||
|
boost::hash_combine(seed, std::hash<coord_t>{}(c.thickness));
|
||||||
|
boost::hash_combine(seed, std::hash<coord_t>{}(c.point_distance));
|
||||||
|
boost::hash_combine(seed, std::hash<bool>{}(c.fuzzy_first_layer));
|
||||||
|
return seed;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class PerimeterGenerator {
|
class PerimeterGenerator {
|
||||||
public:
|
public:
|
||||||
// Inputs:
|
// Inputs:
|
||||||
const SurfaceCollection *slices;
|
const SurfaceCollection *slices;
|
||||||
|
const LayerRegionPtrs *compatible_regions;
|
||||||
const ExPolygons *upper_slices;
|
const ExPolygons *upper_slices;
|
||||||
const ExPolygons *lower_slices;
|
const ExPolygons *lower_slices;
|
||||||
double layer_height;
|
double layer_height;
|
||||||
|
@ -41,10 +74,14 @@ public:
|
||||||
std::pair<double, double> m_external_overhang_dist_boundary;
|
std::pair<double, double> m_external_overhang_dist_boundary;
|
||||||
std::pair<double, double> m_smaller_external_overhang_dist_boundary;
|
std::pair<double, double> m_smaller_external_overhang_dist_boundary;
|
||||||
|
|
||||||
|
bool has_fuzzy_skin = false;
|
||||||
|
bool has_fuzzy_hole = false;
|
||||||
|
std::unordered_map<FuzzySkinConfig, ExPolygons> regions_by_fuzzify;
|
||||||
|
|
||||||
PerimeterGenerator(
|
PerimeterGenerator(
|
||||||
// Input:
|
// Input:
|
||||||
const SurfaceCollection* slices,
|
const SurfaceCollection* slices,
|
||||||
|
const LayerRegionPtrs *compatible_regions,
|
||||||
double layer_height,
|
double layer_height,
|
||||||
Flow flow,
|
Flow flow,
|
||||||
const PrintRegionConfig* config,
|
const PrintRegionConfig* config,
|
||||||
|
@ -60,7 +97,7 @@ public:
|
||||||
SurfaceCollection* fill_surfaces,
|
SurfaceCollection* fill_surfaces,
|
||||||
//BBS
|
//BBS
|
||||||
ExPolygons* fill_no_overlap)
|
ExPolygons* fill_no_overlap)
|
||||||
: slices(slices), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height),
|
: slices(slices), compatible_regions(compatible_regions), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height),
|
||||||
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
|
layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow),
|
||||||
overhang_flow(flow), solid_infill_flow(flow),
|
overhang_flow(flow), solid_infill_flow(flow),
|
||||||
config(config), object_config(object_config), print_config(print_config),
|
config(config), object_config(object_config), print_config(print_config),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue