ENH: sync some code change from prusa

Sync some code change and fix from Prusa.
Thanks Prusa.

This also can fix github issue #317

Signed-off-by: salt.wei <salt.wei@bambulab.com>
Change-Id: If6993e0149733ccf85ed85f82553caa03df7ac60
This commit is contained in:
salt.wei 2022-10-12 20:20:27 +08:00 committed by Lane.Wei
parent afd5e241e6
commit 0f1a001fd6
15 changed files with 514 additions and 110 deletions

View File

@ -21,7 +21,7 @@ DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_wid
name = "DistributedBeadingStrategy";
}
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, const coord_t bead_count) const
{
Beading ret;
@ -40,18 +40,24 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++)
weights[bead_idx] = getWeight(bead_idx);
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
coord_t accumulated_width = 0;
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
const float weight_fraction = weights[bead_idx] / total_weight;
const float weight_fraction = weights[bead_idx] / total_weight;
const coord_t splitup_left_over_weight = to_be_divided * weight_fraction;
const coord_t width = optimal_width + splitup_left_over_weight;
const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width : optimal_width + splitup_left_over_weight;
// Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations
// could be off by 1 because of rounding errors.
if (bead_idx == 0)
ret.toolpath_locations.emplace_back(width / 2);
else
ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
ret.bead_widths.emplace_back(width);
accumulated_width += width;
}
ret.left_over = 0;
assert((accumulated_width + ret.left_over) == thickness);
} else if (bead_count == 2) {
const coord_t outer_width = thickness / 2;
ret.bead_widths.emplace_back(outer_width);
@ -68,6 +74,13 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
ret.left_over = thickness;
}
assert(([&ret = std::as_const(ret), thickness]() -> bool {
coord_t total_bead_width = 0;
for (const coord_t &bead_width : ret.bead_widths)
total_bead_width += bead_width;
return (total_bead_width + ret.left_over) == thickness;
}()));
return ret;
}

View File

@ -17,6 +17,7 @@
#include "Utils.hpp"
#include "SVG.hpp"
#include "Geometry/VoronoiVisualUtils.hpp"
#include "Geometry/VoronoiUtilsCgal.hpp"
#include "../EdgeGrid.hpp"
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
@ -43,6 +44,71 @@ template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
namespace Slic3r::Arachne
{
#ifdef ARACHNE_DEBUG
static void export_graph_to_svg(const std::string &path,
SkeletalTrapezoidationGraph &graph,
const Polygons &polys,
const std::vector<std::shared_ptr<LineJunctions>> &edge_junctions = {},
const bool beat_count = true,
const bool transition_middles = true,
const bool transition_ends = true)
{
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
coordf_t stroke_width = scale_(0.03);
BoundingBox bbox = get_extents(polys);
for (const auto &node : graph.nodes)
bbox.merge(node.p);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(path.c_str(), bbox);
for (const auto &line : to_lines(polys))
svg.draw(line, "gray", stroke_width);
for (const auto &edge : graph.edges)
svg.draw(Line(edge.from->p, edge.to->p), (edge.data.centralIsSet() && edge.data.isCentral()) ? "blue" : "cyan", stroke_width);
for (const auto &line_junction : edge_junctions)
for (const auto &extrusion_junction : *line_junction)
svg.draw(extrusion_junction.p, "orange", coord_t(stroke_width * 2.));
if (beat_count) {
for (const auto &node : graph.nodes) {
svg.draw(node.p, "red", coord_t(stroke_width * 1.6));
svg.draw_text(node.p, std::to_string(node.data.bead_count).c_str(), "black", 1);
}
}
if (transition_middles) {
for (auto &edge : graph.edges) {
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionMiddle>> transitions = edge.data.getTransitions(); transitions) {
for (auto &transition : *transitions) {
Line edge_line = Line(edge.to->p, edge.from->p);
double edge_length = edge_line.length();
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
svg.draw(pt, "magenta", coord_t(stroke_width * 1.5));
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
}
}
}
}
if (transition_ends) {
for (auto &edge : graph.edges) {
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionEnd>> transitions = edge.data.getTransitionEnds(); transitions) {
for (auto &transition : *transitions) {
Line edge_line = Line(edge.to->p, edge.from->p);
double edge_length = edge_line.length();
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
svg.draw(pt, transition.is_lower_end ? "green" : "lime", coord_t(stroke_width * 1.5));
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
}
}
}
}
}
#endif
SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p)
{
auto he_node_it = vd_node_to_he_node.find(&vd_node);
@ -285,7 +351,6 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
}
}
bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments)
{
if (cell.incident_edge()->is_infinite())
@ -386,7 +451,23 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
constructFromPolygons(polys);
}
bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram)
{
for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) {
if (edge.is_finite()) {
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) ||
!VoronoiUtils::is_finite(*edge.vertex1()))
return true;
}
}
return false;
}
static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram))
return true;
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // There is no spoon
@ -405,7 +486,8 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge();
do {
if (edge->is_infinite()) continue;
if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1()))
continue;
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
@ -432,8 +514,71 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
return false;
}
static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph)
{
for (const auto &edge : graph.edges)
if (edge.twin == nullptr)
return true;
return false;
}
inline static std::unordered_map<Point, Point, PointHash> try_to_fix_degenerated_voronoi_diagram_by_rotation(
Geometry::VoronoiDiagram &voronoi_diagram,
const Polygons &polys,
Polygons &polys_rotated,
std::vector<SkeletalTrapezoidation::Segment> &segments,
const double fix_angle)
{
std::unordered_map<Point, Point, PointHash> vertex_mapping;
for (Polygon &poly : polys_rotated)
poly.rotate(fix_angle);
assert(polys_rotated.size() == polys.size());
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
}
segments.clear();
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
voronoi_diagram.clear();
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
#ifdef ARACHNE_DEBUG_VORONOI
{
static int iRun = 0;
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
}
#endif
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
return vertex_mapping;
}
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
const double fix_angle,
const std::unordered_map<Point, Point, PointHash> &vertex_mapping)
{
for (STHalfEdgeNode &node : graph.nodes) {
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
node.p = node_it->second;
else
node.p.rotate(-fix_angle);
}
}
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{
#ifdef ARACHNE_DEBUG
this->outline = polys;
#endif
// Check self intersections.
assert([&polys]() -> bool {
EdgeGrid::Grid grid;
@ -450,39 +595,57 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); point_idx++)
segments.emplace_back(&polys, poly_idx, point_idx);
#ifdef ARACHNE_DEBUG
{
static int iRun = 0;
BoundingBox bbox = get_extents(polys);
SVG svg(debug_out_path("arachne_voronoi-input-%d.svg", iRun++).c_str(), bbox);
svg.draw_outline(polys, "black", scaled<coordf_t>(0.03f));
}
#endif
Geometry::VoronoiDiagram voronoi_diagram;
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
// Try to detect cases when some Voronoi vertex is missing.
// When any Voronoi vertex is missing, rotate input polygon and try again.
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
const double fix_angle = PI / 6;
#ifdef ARACHNE_DEBUG_VORONOI
{
static int iRun = 0;
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
}
#endif
// Try to detect cases when some Voronoi vertex is missing and when
// the Voronoi diagram is not planar.
// When any Voronoi vertex is missing, or the Voronoi diagram is not
// planar, rotate the input polygon and try again.
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram);
const double fix_angle = PI / 6;
std::unordered_map<Point, Point, PointHash> vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys;
if (has_missing_voronoi_vertex) {
BOOST_LOG_TRIVIAL(debug) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
for (Polygon &poly : polys_copy)
poly.rotate(fix_angle);
if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) {
if (has_missing_voronoi_vertex)
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
else if (!is_voronoi_diagram_planar)
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
assert(polys_copy.size() == polys.size());
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
assert(polys_copy[poly_idx].size() == polys[poly_idx].size());
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
vertex_mapping.insert({polys[poly_idx][point_idx], polys_copy[poly_idx][point_idx]});
}
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
segments.clear();
for (size_t poly_idx = 0; poly_idx < polys_copy.size(); poly_idx++)
for (size_t point_idx = 0; point_idx < polys_copy[poly_idx].size(); point_idx++)
segments.emplace_back(&polys_copy, poly_idx, point_idx);
voronoi_diagram.clear();
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram));
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram))
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
}
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar;
process_voronoi_diagram:
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
for (vd_t::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // There is no spoon
@ -538,16 +701,43 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
prev_edge->to->data.distance_to_boundary = 0;
}
if (has_missing_voronoi_vertex) {
for (node_t &node : graph.nodes) {
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
node.p = node_it->second;
else
node.p.rotate(-fix_angle);
}
// For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar.
// When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have
// a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi
// diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram.
if (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) {
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
degenerated_voronoi_diagram = true;
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input.";
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
this->graph.edges.clear();
this->graph.nodes.clear();
this->vd_edge_to_he_edge.clear();
this->vd_node_to_he_node.clear();
goto process_voronoi_diagram;
}
if (degenerated_voronoi_diagram) {
assert(!has_missing_twin_edge(this->graph));
if (has_missing_twin_edge(this->graph))
BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
}
if (degenerated_voronoi_diagram)
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping);
#ifdef ARACHNE_DEBUG
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
#endif
separatePointyQuadEndNodes();
graph.collapseSmallEdges();
@ -594,45 +784,62 @@ void SkeletalTrapezoidation::separatePointyQuadEndNodes()
// vvvvvvvvvvvvvvvvvvvvv
//
#if 0
static void export_graph_to_svg(const std::string &path, const SkeletalTrapezoidationGraph &graph, const Polygons &polys)
{
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
coordf_t stroke_width = scale_(0.05);
BoundingBox bbox;
for (const auto &node : graph.nodes)
bbox.merge(node.p);
bbox.offset(scale_(1.));
::Slic3r::SVG svg(path.c_str(), bbox);
for (const auto &line : to_lines(polys))
svg.draw(line, "red", stroke_width);
for (const auto &edge : graph.edges)
svg.draw(Line(edge.from->p, edge.to->p), "cyan", scale_(0.01));
}
#endif
void SkeletalTrapezoidation::generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges)
{
#ifdef ARACHNE_DEBUG
static int iRun = 0;
#endif
p_generated_toolpaths = &generated_toolpaths;
updateIsCentral();
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-updateIsCentral-final-%d.svg", iRun), this->graph, this->outline);
#endif
filterCentral(central_filter_dist);
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-filterCentral-final-%d.svg", iRun), this->graph, this->outline);
#endif
if (filter_outermost_central_edges)
filterOuterCentral();
updateBeadCount();
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-updateBeadCount-final-%d.svg", iRun), this->graph, this->outline);
#endif
filterNoncentralRegions();
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-filterNoncentralRegions-final-%d.svg", iRun), this->graph, this->outline);
#endif
generateTransitioningRibs();
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-final-%d.svg", iRun), this->graph, this->outline);
#endif
generateExtraRibs();
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateExtraRibs-final-%d.svg", iRun), this->graph, this->outline);
#endif
generateSegments();
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateSegments-final-%d.svg", iRun), this->graph, this->outline);
#endif
#ifdef ARACHNE_DEBUG
++iRun;
#endif
}
void SkeletalTrapezoidation::updateIsCentral()
@ -844,11 +1051,24 @@ void SkeletalTrapezoidation::generateTransitioningRibs()
filterTransitionMids();
#ifdef ARACHNE_DEBUG
static int iRun = 0;
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-mids-%d.svg", iRun++), this->graph, this->outline);
#endif
ptr_vector_t<std::list<TransitionEnd>> edge_transition_ends; // We only map the half edge in the upward direction. mapped items are not sorted
generateAllTransitionEnds(edge_transition_ends);
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-ends-%d.svg", iRun++), this->graph, this->outline);
#endif
applyTransitions(edge_transition_ends);
// Note that the shared pointer lists will be out of scope and thus destroyed here, since the remaining refs are weak_ptr.
#ifdef ARACHNE_DEBUG
++iRun;
#endif
}
@ -1569,16 +1789,37 @@ void SkeletalTrapezoidation::generateSegments()
}
}
#ifdef ARACHNE_DEBUG
static int iRun = 0;
export_graph_to_svg(debug_out_path("ST-generateSegments-before-propagation-%d.svg", iRun), this->graph, this->outline);
#endif
propagateBeadingsUpward(upward_quad_mids, node_beadings);
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateSegments-upward-propagation-%d.svg", iRun), this->graph, this->outline);
#endif
propagateBeadingsDownward(upward_quad_mids, node_beadings);
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateSegments-downward-propagation-%d.svg", iRun), this->graph, this->outline);
#endif
ptr_vector_t<LineJunctions> edge_junctions; // junctions ordered high R to low R
generateJunctions(node_beadings, edge_junctions);
#ifdef ARACHNE_DEBUG
export_graph_to_svg(debug_out_path("ST-generateSegments-junctions-%d.svg", iRun), this->graph, this->outline, edge_junctions);
#endif
connectJunctions(edge_junctions);
generateLocalMaximaSingleBeads();
#ifdef ARACHNE_DEBUG
++iRun;
#endif
}
SkeletalTrapezoidation::edge_t* SkeletalTrapezoidation::getQuadMaxRedgeTo(edge_t* quad_start_edge)
@ -1811,7 +2052,10 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t<BeadingPropagation>&
for (junction_idx = (std::max(size_t(1), beading->toolpath_locations.size()) - 1) / 2; junction_idx < num_junctions; junction_idx--)
{
coord_t bead_R = beading->toolpath_locations[junction_idx];
if (bead_R <= start_R)
// toolpath_locations computed inside DistributedBeadingStrategy could be off by 1 because of rounding errors.
// In GH issue #8472, these roundings errors caused missing the middle extrusion.
// Adding small epsilon should help resolve those cases.
if (bead_R <= start_R + 1)
{ // Junction coinciding with start node is used in this function call
break;
}

View File

@ -18,6 +18,10 @@
#include "SkeletalTrapezoidationJoint.hpp"
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
#include "SkeletalTrapezoidationGraph.hpp"
#include "../Geometry/Voronoi.hpp"
//#define ARACHNE_DEBUG
//#define ARACHNE_DEBUG_VORONOI
namespace Slic3r::Arachne
{
@ -122,6 +126,10 @@ public:
*/
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
#ifdef ARACHNE_DEBUG
Polygons outline;
#endif
protected:
/*!
* Auxiliary for referencing one transition along an edge which may contain multiple transitions

View File

@ -24,18 +24,19 @@ namespace Slic3r::Arachne
{
WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x,
const size_t inset_count, const coord_t wall_0_inset, const WallToolPathsParams &params)
const size_t inset_count, const coord_t wall_0_inset, const coordf_t layer_height, const WallToolPathsParams &params)
: outline(outline)
, bead_width_0(bead_width_0)
, bead_width_x(bead_width_x)
, inset_count(inset_count)
, wall_0_inset(wall_0_inset)
, layer_height(layer_height)
, print_thin_walls(Slic3r::Arachne::fill_outline_gaps)
, min_feature_size(scaled<coord_t>(params.min_feature_size))
, min_bead_width(scaled<coord_t>(params.min_bead_width))
, small_area_length(static_cast<double>(bead_width_0) / 2.)
, toolpaths_generated(false)
, wall_transition_filter_deviation(scaled<coord_t>(params.wall_transition_filter_deviation))
, toolpaths_generated(false)
, m_params(params)
{
}
@ -312,60 +313,46 @@ void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool re
};
auto new_end = thiss.end();
if(remove_holes)
{
for(auto it = thiss.begin(); it < new_end; it++)
{
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector
if(fabs(ClipperLib::Area(to_path(*it))) < min_area_size)
{
new_end--;
if (remove_holes) {
for (auto it = thiss.begin(); it < new_end;) {
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector.
if (fabs(ClipperLib::Area(to_path(*it))) < min_area_size) {
--new_end;
*it = std::move(*new_end);
it--; // wind back the iterator such that the polygon just swaped in is checked next
continue; // Don't increment the iterator such that the polygon just swapped in is checked next.
}
++it;
}
}
else
{
} else {
// For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes
std::vector<Polygon> small_holes;
for(auto it = thiss.begin(); it < new_end; it++) {
double area = ClipperLib::Area(to_path(*it));
if (fabs(area) < min_area_size)
{
if(area >= 0)
{
new_end--;
if(it < new_end) {
for (auto it = thiss.begin(); it < new_end;) {
if (double area = ClipperLib::Area(to_path(*it)); fabs(area) < min_area_size) {
if (area >= 0) {
--new_end;
if (it < new_end) {
std::swap(*new_end, *it);
it--;
}
else
{ // Don't self-swap the last Path
continue;
} else { // Don't self-swap the last Path
break;
}
}
else
{
} else {
small_holes.push_back(*it);
}
}
++it;
}
// Removes small holes that have their first point inside one of the removed outlines
// Iterating in reverse ensures that unprocessed small holes won't be moved
const auto removed_outlines_start = new_end;
for(auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++)
{
for(auto outline_it = removed_outlines_start; outline_it < thiss.end() ; outline_it++)
{
if(Polygon(*outline_it).contains(*hole_it->begin())) {
for (auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++)
for (auto outline_it = removed_outlines_start; outline_it < thiss.end(); outline_it++)
if (Polygon(*outline_it).contains(*hole_it->begin())) {
new_end--;
*hole_it = std::move(*new_end);
break;
}
}
}
}
thiss.resize(new_end-thiss.begin());
}
@ -471,7 +458,7 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
// The functions above could produce intersecting polygons that could cause a crash inside Arachne.
// Applying Clipper union should be enough to get rid of this issue.
// Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges
// didn't have twin edges (this probably isn't an issue in Boost Voronoi generator).
// didn't have twin edges. (a non-planar Voronoi diagram probably caused this).
prepared_outline = union_(prepared_outline);
if (area(prepared_outline) <= 0) {
@ -479,9 +466,14 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
return toolpaths;
}
const float external_perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_0), float(this->layer_height));
const float perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_x), float(this->layer_height));
const coord_t wall_transition_length = scaled<coord_t>(this->m_params.wall_transition_length);
const double wall_split_middle_threshold = this->m_params.wall_split_middle_threshold; // For an uneven nr. of lines: When to split the middle wall into two.
const double wall_add_middle_threshold = this->m_params.wall_add_middle_threshold; // For an even nr. of lines: When to add a new middle in between the innermost two walls.
const double wall_split_middle_threshold = std::clamp(2. * unscaled<double>(this->min_bead_width) / external_perimeter_extrusion_width - 1., 0.01, 0.99); // For an uneven nr. of lines: When to split the middle wall into two.
const double wall_add_middle_threshold = std::clamp(unscaled<double>(this->min_bead_width) / perimeter_extrusion_width, 0.01, 0.99); // For an even nr. of lines: When to add a new middle in between the innermost two walls.
const int wall_distribution_count = this->m_params.wall_distribution_count;
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
const auto beading_strat = BeadingStrategyFactory::makeStrategy
@ -609,6 +601,14 @@ void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths,
{
continue;
}
// PolylineStitcher, in some cases, produced closed extrusion (polygons),
// but the endpoints differ by a small distance. So we reconnect them.
// FIXME Lukas H.: Investigate more deeply why it is happening.
if (wall_polygon.junctions.front().p != wall_polygon.junctions.back().p &&
(wall_polygon.junctions.back().p - wall_polygon.junctions.front().p).cast<double>().norm() < stitch_distance) {
wall_polygon.junctions.emplace_back(wall_polygon.junctions.front());
}
wall_polygon.is_closed = true;
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
}

View File

@ -29,8 +29,6 @@ public:
float wall_transition_angle;
float wall_transition_filter_deviation;
int wall_distribution_count;
float wall_add_middle_threshold;
float wall_split_middle_threshold;
};
class WallToolPaths
@ -44,7 +42,7 @@ public:
* \param inset_count The maximum number of parallel extrusion lines that make up the wall
* \param wall_0_inset How far to inset the outer wall, to make it adhere better to other walls.
*/
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, const WallToolPathsParams &params);
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, coordf_t layer_height, const WallToolPathsParams &params);
/*!
* Generates the Toolpaths
@ -123,14 +121,15 @@ private:
coord_t bead_width_x; //<! The subsequently extrusion line width with which libArachne generates its walls if WallToolPaths was called with the nominal_bead_width Constructor this is the same as bead_width_0
size_t inset_count; //<! The maximum number of walls to generate
coord_t wall_0_inset; //<! How far to inset the outer wall. Should only be applied when printing the actual walls, not extra infill/skin/support walls.
coordf_t layer_height;
bool print_thin_walls; //<! Whether to enable the widening beading meta-strategy for thin features
coord_t min_feature_size; //<! The minimum size of the features that can be widened by the widening beading meta-strategy. Features thinner than that will not be printed
coord_t min_bead_width; //<! The minimum bead size to use when widening thin model features with the widening beading meta-strategy
double small_area_length; //<! The length of the small features which are to be filtered out, this is squared into a surface
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
bool toolpaths_generated; //<! Are the toolpaths generated
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
Polygons inner_contour; //<! The inner contour of the generated toolpaths
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
const WallToolPathsParams m_params;
};

View File

@ -15,6 +15,7 @@ Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
{
const double x = node->x();
const double y = node->y();
assert(std::isfinite(x) && std::isfinite(y));
assert(x <= double(std::numeric_limits<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::lowest());
return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates.

View File

@ -35,6 +35,11 @@ public:
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
*/
static std::vector<Point> discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle);
static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex)
{
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
}
};
} // namespace Slic3r::Arachne

View File

@ -151,6 +151,8 @@ set(lisbslic3r_sources
Geometry/Voronoi.hpp
Geometry/VoronoiOffset.cpp
Geometry/VoronoiOffset.hpp
Geometry/VoronoiUtilsCgal.cpp
Geometry/VoronoiUtilsCgal.hpp
Geometry/VoronoiVisualUtils.hpp
Int128.hpp
InternalBridgeDetector.cpp

View File

@ -441,6 +441,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = surface_fill.params.pattern == ipConcentric;
params.layer_height = m_regions[surface_fill.region_id]->layer()->height;
// BBS
params.flow = surface_fill.params.flow;

View File

@ -65,6 +65,8 @@ struct FillParams
// For Concentric infill, to switch between Classic and Arachne.
bool use_arachne{ false };
// Layer height for Concentric infill with Arachne.
coordf_t layer_height { 0.f };
// BBS
Flow flow;

View File

@ -83,15 +83,13 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
Arachne::WallToolPathsParams input_params;
input_params.min_bead_width = 0.85 * min_nozzle_diameter;
input_params.min_feature_size = 0.1;
input_params.min_feature_size = 0.25 * min_nozzle_diameter;
input_params.wall_transition_length = 1.0 * min_nozzle_diameter;
input_params.wall_transition_angle = 10;
input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter;
input_params.wall_distribution_count = 1;
input_params.wall_add_middle_threshold = 0.75;
input_params.wall_split_middle_threshold = 0.5;
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, input_params);
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params);
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
std::vector<const Arachne::ExtrusionLine*> all_extrusions;

View File

@ -27,15 +27,13 @@ void FillConcentricInternal::fill_surface_extrusion(const Surface* surface, cons
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
Arachne::WallToolPathsParams input_params;
input_params.min_bead_width = 0.85 * min_nozzle_diameter;
input_params.min_feature_size = 0.1;
input_params.min_feature_size = 0.25 * min_nozzle_diameter;
input_params.wall_transition_length = 0.4;
input_params.wall_transition_angle = 10;
input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter;
input_params.wall_distribution_count = 1;
input_params.wall_add_middle_threshold = 0.75;
input_params.wall_split_middle_threshold = 0.5;
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, input_params);
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params);
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
std::vector<const Arachne::ExtrusionLine*> all_extrusions;

View File

@ -0,0 +1,103 @@
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Surface_sweep_2_algorithms.h>
#include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp"
#include "VoronoiUtilsCgal.hpp"
using VD = Slic3r::Geometry::VoronoiDiagram;
namespace Slic3r::Geometry {
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; }
// FIXME Lukas H.: Also includes parabolic segments.
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
{
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
[](const VD::edge_type &edge) { return edge.color() == 0; }));
std::vector<CGAL_Segment> segments;
segments.reserve(voronoi_diagram.num_edges());
for (const VD::edge_type &edge : voronoi_diagram.edges()) {
if (edge.color() != 0)
continue;
if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr &&
Arachne::VoronoiUtils::is_finite(*edge.vertex0()) && Arachne::VoronoiUtils::is_finite(*edge.vertex1())) {
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
edge.color(1);
assert(edge.twin() != nullptr);
edge.twin()->color(1);
}
}
for (const VD::edge_type &edge : voronoi_diagram.edges())
edge.color(0);
std::vector<CGAL_Point> intersections_pt;
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
return intersections_pt.empty();
}
static bool check_if_three_vectors_are_ccw(const CGAL_Point &common_pt, const CGAL_Point &pt_1, const CGAL_Point &pt_2, const CGAL_Point &test_pt) {
CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2);
if (orientation == CGAL::Orientation::COLLINEAR) {
// The first two edges are collinear, so the third edge must be on the right side on the first of them.
return CGAL::orientation(common_pt, pt_1, test_pt) == CGAL::Orientation::RIGHT_TURN;
} else if (orientation == CGAL::Orientation::LEFT_TURN) {
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI.
// So we need to check if test_pt isn't between them.
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt);
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
} else {
assert(orientation == CGAL::Orientation::RIGHT_TURN);
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI.
// So we need to check if test_pt is between them.
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt);
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
}
}
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram)
{
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
std::vector<const VD::edge_type *> edges;
const VD::edge_type *edge = vertex.incident_edge();
do {
// FIXME Lukas H.: Also process parabolic segments.
if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1()))
edges.emplace_back(edge);
edge = edge->rot_next();
} while (edge != vertex.incident_edge());
// Checking for CCW make sense for three and more edges.
if (edges.size() > 2) {
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) {
const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it;
const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
if (!check_if_three_vectors_are_ccw(to_cgal_point(*prev_edge->vertex0()), to_cgal_point(*prev_edge->vertex1()),
to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1())))
return false;
}
}
}
return true;
}
} // namespace Slic3r::Geometry

View File

@ -0,0 +1,21 @@
#ifndef slic3r_VoronoiUtilsCgal_hpp_
#define slic3r_VoronoiUtilsCgal_hpp_
#include "Voronoi.hpp"
namespace Slic3r::Geometry {
class VoronoiDiagram;
class VoronoiUtilsCgal
{
public:
// Check if the Voronoi diagram is planar using CGAL sweeping edge algorithm for enumerating all intersections between lines.
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram);
// Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex.
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram);
};
} // namespace Slic3r::Geometry
#endif // slic3r_VoronoiUtilsCgal_hpp_

View File

@ -753,10 +753,19 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "bottom_surface_pattern"
|| opt_key == "external_fill_link_max_length"
|| opt_key == "infill_direction"
|| opt_key == "sparse_infill_pattern"
|| opt_key == "top_surface_line_width"
|| opt_key == "initial_layer_line_width") {
steps.emplace_back(posInfill);
} else if (opt_key == "sparse_infill_pattern") {
steps.emplace_back(posInfill);
const auto *old_fill_pattern = old_config.option<ConfigOptionEnum<InfillPattern>>(opt_key);
const auto *new_fill_pattern = new_config.option<ConfigOptionEnum<InfillPattern>>(opt_key);
assert(old_infill && new_infill);
// We need to recalculate infill surfaces when infill_only_where_needed is enabled, and we are switching from
// the Lightning infill to another infill or vice versa.
if (PrintObject::infill_only_where_needed && (new_fill_pattern->value == ipLightning || old_fill_pattern->value == ipLightning))
steps.emplace_back(posPrepareInfill);
} else if (opt_key == "sparse_infill_density") {
// One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes),
// normal infill and 100% (solid) infill.