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:
Noisyfox 2024-09-23 00:41:17 +08:00 committed by GitHub
parent 3d3633f110
commit 9d3d242453
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 707 additions and 92 deletions

View 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

View 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_ */

View file

@ -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)

View file

@ -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;

View file

@ -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

View file

@ -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)
{ {

View file

@ -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)
{ {

View file

@ -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()) {

View file

@ -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.

View file

@ -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),
&region_config, &region_config,

View file

@ -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 = [&current_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

View file

@ -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),