Merge branch 'lh_250_bug_fix'

This commit is contained in:
Lukáš Hejl 2023-01-11 22:19:52 +01:00
commit 4620b11ab2
6 changed files with 543 additions and 59 deletions

View File

@ -573,6 +573,59 @@ inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalT
} }
} }
bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
{
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // Degenerated cell, there is no spoon
if (!cell.contains_segment())
continue; // Skip cells that don't contain segments.
const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
const Vec2d source_segment_from = source_segment.from().cast<double>();
const Vec2d source_segment_vec = source_segment.to().cast<double>() - source_segment_from;
Point start_source_point, end_source_point;
VoronoiUtils::vd_t::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr;
SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments);
// All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid.
// FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases.
// It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf).
if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr)
for (VoronoiUtils::vd_t::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next())
if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0)
return true;
}
return false;
}
enum class VoronoiDiagramStatus {
NO_ISSUE_DETECTED,
MISSING_VORONOI_VERTEX,
NON_PLANAR_VORONOI_DIAGRAM,
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION
};
// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram
// is not planar or some Voronoi edge is intersecting input segment.
VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::VoronoiDiagram &voronoi_diagram,
const std::vector<SkeletalTrapezoidation::Segment> &segments)
{
if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) {
return VoronoiDiagramStatus::MISSING_VORONOI_VERTEX;
} else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) {
// Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446.
return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
} else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); !is_voronoi_diagram_planar) {
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM;
}
return VoronoiDiagramStatus::NO_ISSUE_DETECTED;
}
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{ {
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
@ -614,36 +667,35 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #endif
// Try to detect cases when some Voronoi vertex is missing and when // When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is
// the Voronoi diagram is not planar. // intersecting input segment, rotate the input polygon and try again.
// When any Voronoi vertex is missing, or the Voronoi diagram is not VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
// 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; const double fix_angle = PI / 6;
std::unordered_map<Point, Point, PointHash> vertex_mapping; std::unordered_map<Point, Point, PointHash> vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments. // polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys; Polygons polys_copy = polys;
if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) { if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
if (has_missing_voronoi_vertex) if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth."; BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
else if (!is_voronoi_diagram_planar) else if (status == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM)
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth."; BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
else if (status == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle); 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)); assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram)); assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments));
assert(!detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments));
if (detect_missing_voronoi_vertex(voronoi_diagram, segments)) if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input."; 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)) else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input."; BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
else if (detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
} }
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar;
process_voronoi_diagram: 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()); 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()) { for (vd_t::cell_type cell : voronoi_diagram.cells()) {
@ -652,35 +704,35 @@ process_voronoi_diagram:
Point start_source_point; Point start_source_point;
Point end_source_point; Point end_source_point;
vd_t::edge_type* starting_vonoroi_edge = nullptr; vd_t::edge_type* starting_voronoi_edge = nullptr;
vd_t::edge_type* ending_vonoroi_edge = nullptr; vd_t::edge_type* ending_voronoi_edge = nullptr;
// Compute and store result in above variables // Compute and store result in above variables
if (cell.contains_point()) { if (cell.contains_point()) {
const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments);
if (!keep_going) if (!keep_going)
continue; continue;
} else { } else {
assert(cell.contains_segment()); assert(cell.contains_segment());
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments);
} }
if (!starting_vonoroi_edge || !ending_vonoroi_edge) { if (!starting_voronoi_edge || !ending_voronoi_edge) {
assert(false && "Each cell should start / end in a polygon vertex"); assert(false && "Each cell should start / end in a polygon vertex");
continue; continue;
} }
// Copy start to end edge to graph // Copy start to end edge to graph
edge_t* prev_edge = nullptr; edge_t* prev_edge = nullptr;
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest()); assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest()); assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()).cast<coord_t>(), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()]; node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
starting_node->data.distance_to_boundary = 0; starting_node->data.distance_to_boundary = 0;
constexpr bool is_next_to_start_or_end = true; constexpr bool is_next_to_start_or_end = true;
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
for (vd_t::edge_type* vd_edge = starting_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) { for (vd_t::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
assert(vd_edge->is_finite()); assert(vd_edge->is_finite());
assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
@ -692,12 +744,12 @@ process_voronoi_diagram:
Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast<coord_t>(); Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast<coord_t>();
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_vonoroi_edge); graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
} }
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments); transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
prev_edge->to->data.distance_to_boundary = 0; prev_edge->to->data.distance_to_boundary = 0;
} }
@ -705,9 +757,9 @@ process_voronoi_diagram:
// When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have // 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 // 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. // 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)) { if (status == VoronoiDiagramStatus::NO_ISSUE_DETECTED && has_missing_twin_edge(this->graph)) {
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth."; BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
degenerated_voronoi_diagram = true; status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION;
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle); 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)); assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
@ -724,14 +776,14 @@ process_voronoi_diagram:
goto process_voronoi_diagram; goto process_voronoi_diagram;
} }
if (degenerated_voronoi_diagram) { if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
assert(!has_missing_twin_edge(this->graph)); assert(!has_missing_twin_edge(this->graph));
if (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."; BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
} }
if (degenerated_voronoi_diagram) if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED)
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping); rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping);
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
@ -742,7 +794,7 @@ process_voronoi_diagram:
graph.collapseSmallEdges(); graph.collapseSmallEdges();
// Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge, // Set [incident_edge] the first possible edge that way we can iterate over all reachable edges from node.incident_edge,
// without needing to iterate backward // without needing to iterate backward
for (edge_t& edge : graph.edges) for (edge_t& edge : graph.edges)
if (!edge.prev) if (!edge.prev)

View File

@ -9,6 +9,7 @@
#include <memory> // smart pointers #include <memory> // smart pointers
#include <unordered_map> #include <unordered_map>
#include <utility> // pair #include <utility> // pair
#include <Arachne/utils/VoronoiUtils.hpp>
#include "utils/HalfEdgeGraph.hpp" #include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp" #include "utils/PolygonsSegmentIndex.hpp"
@ -229,7 +230,7 @@ protected:
* /return Whether the cell is inside of the polygon. If it's outside of the * /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether. * polygon we should skip processing it altogether.
*/ */
bool 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); static bool 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);
/*! /*!
* Compute the range of line segments that surround a cell of the skeletal * Compute the range of line segments that surround a cell of the skeletal
@ -255,7 +256,7 @@ protected:
* /return Whether the cell is inside of the polygon. If it's outside of the * /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether. * polygon we should skip processing it altogether.
*/ */
void computeSegmentCellRange(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); static void computeSegmentCellRange(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);
/*! /*!
* For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two
@ -597,6 +598,8 @@ protected:
* Genrate small segments for local maxima where the beading would only result in a single bead * Genrate small segments for local maxima where the beading would only result in a single bead
*/ */
void generateLocalMaximaSingleBeads(); void generateLocalMaximaSingleBeads();
friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments);
}; };
} // namespace Slic3r::Arachne } // namespace Slic3r::Arachne

View File

@ -367,7 +367,16 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
case 'T': case 'T':
{ {
// Activate an extruder head. // Activate an extruder head.
int new_extruder = parse_int(line); int new_extruder = -1;
try {
new_extruder = parse_int(line);
} catch (Slic3r::InvalidArgument &) {
// Ignore invalid GCodes starting with T.
eatws(line);
break;
}
assert(new_extruder != -1);
if (new_extruder != int(m_current_extruder)) { if (new_extruder != int(m_current_extruder)) {
m_current_extruder = new_extruder; m_current_extruder = new_extruder;
m_retracted = true; m_retracted = true;

View File

@ -8,17 +8,135 @@
#include "VoronoiUtilsCgal.hpp" #include "VoronoiUtilsCgal.hpp"
using VD = Slic3r::Geometry::VoronoiDiagram; using VD = Slic3r::Geometry::VoronoiDiagram;
using namespace Slic3r::Arachne;
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; // The tangent vector of the parabola is computed based on the Proof of the reflective property.
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2; // https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property
// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663
namespace impl {
using K = CGAL::Simple_cartesian<double>;
using FK = CGAL::Simple_cartesian<CGAL::Interval_nt_advanced>;
using EK = CGAL::Simple_cartesian<CGAL::MP_Float>;
using C2E = CGAL::Cartesian_converter<K, EK>;
using C2F = CGAL::Cartesian_converter<K, FK>;
class Epick : public CGAL::Filtered_kernel_adaptor<CGAL::Type_equality_wrapper<K::Base<Epick>::Type, Epick>, true> {};
inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; } template<typename K>
inline typename K::Vector_2 calculate_parabolic_tangent_vector(
// Test point on the parabola, where the tangent will be calculated.
const typename K::Point_2 &p,
// Focus point of the parabola.
const typename K::Point_2 &f,
// Points of a directrix of the parabola.
const typename K::Point_2 &u,
const typename K::Point_2 &v,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const typename K::Orientation &tangent_orientation)
{
using RT = typename K::RT;
using Vector_2 = typename K::Vector_2;
const Vector_2 directrix_vec = v - u;
const RT directrix_vec_sqr_length = CGAL::scalar_product(directrix_vec, directrix_vec);
Vector_2 focus_vec = (f - u) * directrix_vec_sqr_length - directrix_vec * CGAL::scalar_product(directrix_vec, p - u);
Vector_2 tangent_vec = focus_vec.perpendicular(tangent_orientation);
return tangent_vec;
}
template<typename K> struct ParabolicTangentToSegmentOrientationPredicate
{
using Point_2 = typename K::Point_2;
using Vector_2 = typename K::Vector_2;
using Orientation = typename K::Orientation;
using result_type = typename K::Orientation;
result_type operator()(
// Test point on the parabola, where the tangent will be calculated.
const Point_2 &p,
// End of the linear segment (p, q), for which orientation towards the tangent to parabola will be evaluated.
const Point_2 &q,
// Focus point of the parabola.
const Point_2 &f,
// Points of a directrix of the parabola.
const Point_2 &u,
const Point_2 &v,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const Orientation &tangent_orientation) const
{
assert(tangent_orientation == CGAL::Orientation::LEFT_TURN || tangent_orientation == CGAL::Orientation::RIGHT_TURN);
Vector_2 tangent_vec = calculate_parabolic_tangent_vector<K>(p, f, u, v, tangent_orientation);
Vector_2 linear_vec = q - p;
return CGAL::sign(tangent_vec.x() * linear_vec.y() - tangent_vec.y() * linear_vec.x());
}
};
template<typename K> struct ParabolicTangentToParabolicTangentOrientationPredicate
{
using Point_2 = typename K::Point_2;
using Vector_2 = typename K::Vector_2;
using Orientation = typename K::Orientation;
using result_type = typename K::Orientation;
result_type operator()(
// Common point on both parabolas, where the tangent will be calculated.
const Point_2 &p,
// Focus point of the first parabola.
const Point_2 &f_0,
// Points of a directrix of the first parabola.
const Point_2 &u_0,
const Point_2 &v_0,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const Orientation &tangent_orientation_0,
// Focus point of the second parabola.
const Point_2 &f_1,
// Points of a directrix of the second parabola.
const Point_2 &u_1,
const Point_2 &v_1,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const Orientation &tangent_orientation_1) const
{
assert(tangent_orientation_0 == CGAL::Orientation::LEFT_TURN || tangent_orientation_0 == CGAL::Orientation::RIGHT_TURN);
assert(tangent_orientation_1 == CGAL::Orientation::LEFT_TURN || tangent_orientation_1 == CGAL::Orientation::RIGHT_TURN);
Vector_2 tangent_vec_0 = calculate_parabolic_tangent_vector<K>(p, f_0, u_0, v_0, tangent_orientation_0);
Vector_2 tangent_vec_1 = calculate_parabolic_tangent_vector<K>(p, f_1, u_1, v_1, tangent_orientation_1);
return CGAL::sign(tangent_vec_0.x() * tangent_vec_1.y() - tangent_vec_0.y() * tangent_vec_1.x());
}
};
using ParabolicTangentToSegmentOrientationPredicateFiltered = CGAL::Filtered_predicate<ParabolicTangentToSegmentOrientationPredicate<EK>, ParabolicTangentToSegmentOrientationPredicate<FK>, C2E, C2F>;
using ParabolicTangentToParabolicTangentOrientationPredicateFiltered = CGAL::Filtered_predicate<ParabolicTangentToParabolicTangentOrientationPredicate<EK>, ParabolicTangentToParabolicTangentOrientationPredicate<FK>, C2E, C2F>;
} // namespace impl
using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrientationPredicateFiltered;
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
using CGAL_Point = impl::K::Point_2;
inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
inline static Linef make_linef(const VD::edge_type &edge)
{
const VD::vertex_type *v0 = edge.vertex0();
const VD::vertex_type *v1 = edge.vertex1();
return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())};
}
inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); }
// FIXME Lukas H.: Also includes parabolic segments. // FIXME Lukas H.: Also includes parabolic segments.
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram) bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
{ {
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;
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; };
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(), assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
[](const VD::edge_type &edge) { return edge.color() == 0; })); [](const VD::edge_type &edge) { return edge.color() == 0; }));
@ -30,7 +148,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
continue; continue;
if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr && 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())) { VoronoiUtils::is_finite(*edge.vertex0()) && VoronoiUtils::is_finite(*edge.vertex1())) {
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1())); segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
edge.color(1); edge.color(1);
assert(edge.twin() != nullptr); assert(edge.twin() != nullptr);
@ -46,37 +164,101 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
return intersections_pt.empty(); 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) { struct ParabolicSegment
CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2); {
const Point focus;
const Line directrix;
// Two points on the parabola;
const Linef segment;
// Indicate if focus point is on the left side or right side relative to parabolic segment endpoints.
const CGAL::Orientation is_focus_on_left;
};
inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<VoronoiUtils::Segment> &segments)
{
assert(edge.is_curved());
const VD::cell_type *left_cell = edge.cell();
const VD::cell_type *right_cell = edge.twin()->cell();
const Point focus_pt = VoronoiUtils::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments);
const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments);
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt)));
assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN);
return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side};
}
inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector<VoronoiUtils::Segment> &segments) {
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
CGAL::Orientation orientation;
if (edge_a.is_linear() && edge_b.is_linear()) {
orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1()));
} else if (edge_a.is_curved() && edge_b.is_curved()) {
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments);
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments);
orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a),
to_cgal_point(parabolic_a.focus),
to_cgal_point(parabolic_a.directrix.a),
to_cgal_point(parabolic_a.directrix.b),
parabolic_a.is_focus_on_left,
to_cgal_point(parabolic_b.focus),
to_cgal_point(parabolic_b.directrix.a),
to_cgal_point(parabolic_b.directrix.b),
parabolic_b.is_focus_on_left);
return orientation;
} else {
assert(edge_a.is_curved() != edge_b.is_curved());
const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a;
const VD::edge_type &parabolic_edge = edge_a.is_curved() ? edge_a : edge_b;
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments);
orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()),
to_cgal_point(parabolic.focus),
to_cgal_point(parabolic.directrix.a),
to_cgal_point(parabolic.directrix.b),
parabolic.is_focus_on_left);
if (edge_b.is_curved())
orientation = CGAL::opposite(orientation);
}
return orientation;
}
static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector<VoronoiUtils::Segment> &segments)
{
assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0()));
CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments);
if (orientation == CGAL::Orientation::COLLINEAR) { 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. // 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; return orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN;
} else if (orientation == CGAL::Orientation::LEFT_TURN) { } else if (orientation == CGAL::Orientation::LEFT_TURN) {
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI. // 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. // 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 orientation1 = orientation_of_two_edges(first, third, segments);
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt); CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN); return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
} else { } else {
assert(orientation == CGAL::Orientation::RIGHT_TURN); assert(orientation == CGAL::Orientation::RIGHT_TURN);
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI. // 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. // So we need to check if test_pt is between them.
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt); CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt); CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN); return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
} }
} }
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram) bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
{ {
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
std::vector<const VD::edge_type *> edges; std::vector<const VD::edge_type *> edges;
const VD::edge_type *edge = vertex.incident_edge(); const VD::edge_type *edge = vertex.incident_edge();
do { do {
// FIXME Lukas H.: Also process parabolic segments. if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1()))
edges.emplace_back(edge); edges.emplace_back(edge);
edge = edge->rot_next(); edge = edge->rot_next();
@ -89,8 +271,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
const Geometry::VoronoiDiagram::edge_type *curr_edge = *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); 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()), if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments))
to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1())))
return false; return false;
} }
} }
@ -99,5 +280,4 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
return true; return true;
} }
} // namespace Slic3r::Geometry } // namespace Slic3r::Geometry

View File

@ -2,6 +2,7 @@
#define slic3r_VoronoiUtilsCgal_hpp_ #define slic3r_VoronoiUtilsCgal_hpp_
#include "Voronoi.hpp" #include "Voronoi.hpp"
#include "../Arachne/utils/VoronoiUtils.hpp"
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
class VoronoiDiagram; class VoronoiDiagram;
@ -13,7 +14,7 @@ public:
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); 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. // 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); static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<Arachne::VoronoiUtils::Segment> &segments);
}; };
} // namespace Slic3r::Geometry } // namespace Slic3r::Geometry

View File

@ -451,3 +451,242 @@ TEST_CASE("Arachne - Missing infill", "[ArachneMissingInfill]") {
// REQUIRE(wallToolPaths.getInnerContour().size() == 1); // REQUIRE(wallToolPaths.getInnerContour().size() == 1);
} }
// This test case was distilled from GitHub issue #8849.
// Missing part of the model after simplifying generated tool-paths by simplifyToolPaths.
TEST_CASE("Arachne - #8849 - Missing part of model", "[ArachneMissingPart8849]") {
const Polygon poly_0 = {
Point(-29700000, -10600000),
Point(-28200000, -10600000),
Point( 20000000, -10600000),
Point( 20000000, - 9900000),
Point(-28200000, - 9900000),
Point(-28200000, 0),
Point(-29700000, 0),
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 449999;
coord_t perimeter_spacing = 757079;
coord_t inset_count = 2;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.32, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-missing-part-8849.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 30mm when the part is missing and around 120 when everything is ok.
// REQUIRE(total_extrusion_length >= scaled<int64_t>(120.));
}
// This test case was distilled from GitHub issue #8446.
// Boost Voronoi generator produces non-planar Voronoi diagram with two intersecting linear Voronoi edges.
// Those intersecting edges are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #8446 - Degenerated Voronoi diagram - Linear edges", "[ArachneDegeneratedDiagram8446LinearEdges]") {
Polygon poly_0 = {
Point( 42240656, 9020315),
Point( 4474248, 42960681),
Point( -4474248, 42960681),
Point( -4474248, 23193537),
Point( -6677407, 22661038),
Point( -8830542, 21906307),
Point( -9702935, 21539826),
Point(-13110431, 19607811),
Point(-18105334, 15167780),
Point(-20675743, 11422461),
Point(-39475413, 17530840),
Point(-42240653, 9020315)
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-8446-linear-edges.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 211.2mm when the part is ok and 212.1mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(211.5));
}
// This test case was distilled from GitHub issue #8846.
// Boost Voronoi generator produces degenerated Voronoi diagram with one parabolic edge intersecting linear Voronoi edge.
// Those intersecting edges are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #8846 - Degenerated Voronoi diagram - One Parabola", "[ArachneDegeneratedDiagram8846OneParabola]") {
const Polygon poly_0 = {
Point(101978540, -41304489), Point(101978540, 41304489),
Point(94709788, 42514051), Point(94709788, 48052315),
Point(93352716, 48052315), Point(93352716, 42514052),
Point(75903540, 42514051), Point(75903540, 48052315),
Point(74546460, 48052315), Point(74546460, 42514052),
Point(69634788, 42514051), Point(69634788, 48052315),
Point(68277708, 48052315), Point(68277708, 42514051),
Point(63366040, 42514051), Point(63366040, 48052315),
Point(62008960, 48052315), Point(62008960, 42514051),
Point(57097292, 42514051), Point(57097292, 48052315),
Point(55740212, 48052315), Point(55740212, 42514052),
Point(50828540, 42514052), Point(50828540, 48052315),
Point(49471460, 48052315), Point(49471460, 42514051),
Point(25753540, 42514051), Point(25753540, 48052315),
Point(24396460, 48052315), Point(24396460, 42514051),
Point(19484790, 42514052), Point(19484790, 48052315),
Point(18127710, 48052315), Point(18127710, 42514051),
Point(-5590210, 42514051), Point(-5590210, 48052315),
Point(-6947290, 48052315), Point(-6947290, 42514051),
Point(-11858960, 42514051), Point(-11858960, 48052315),
Point(-13216040, 48052315), Point(-13216040, 42514051),
Point(-18127710, 42514051), Point(-18127710, 48052315),
Point(-19484790, 48052315), Point(-19484790, 42514052),
Point(-49471460, 42514051), Point(-49471460, 48052315),
Point(-50828540, 48052315), Point(-50828540, 42514052),
Point(-55740212, 42514052), Point(-55740212, 48052315),
Point(-57097292, 48052315), Point(-57097292, 42514051),
Point(-68277708, 42514051), Point(-68277708, 48052315),
Point(-69634788, 48052315), Point(-69634788, 42514051),
Point(-74546460, 42514052), Point(-74546460, 48052315),
Point(-75903540, 48052315), Point(-75903540, 42514051),
Point(-80815204, 42514051), Point(-80815204, 48052315),
Point(-82172292, 48052315), Point(-82172292, 42514051),
Point(-87083956, 42514051), Point(-87083956, 48052315),
Point(-88441044, 48052315), Point(-88441044, 42514051),
Point(-99621460, 42514051), Point(-99621460, 48052315),
Point(-100978540, 48052315), Point(-100978540, 42528248),
Point(-101978540, 41304489), Point(-101978540, -41304489),
Point(-100978540, -48052315), Point(-99621460, -48052315),
};
Polygon poly_1 = {
Point(-100671460, -40092775),
Point(-100671460, 40092775),
Point(100671460, 40092775),
Point(100671460, -40092775),
};
Polygons polygons = {poly_0, poly_1};
coord_t ext_perimeter_spacing = 607079;
coord_t perimeter_spacing = 607079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-8846-one-parabola.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 1335mm when the part is ok and 1347mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(1335.));
}
// This test case was distilled from GitHub issue #9357.
// Boost Voronoi generator produces degenerated Voronoi diagram with two intersecting parabolic Voronoi edges.
// Those intersecting edges are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #9357 - Degenerated Voronoi diagram - Two parabolas", "[ArachneDegeneratedDiagram9357TwoParabolas]") {
const Polygon poly_0 = {
Point(78998946, -11733905),
Point(40069507, -7401251),
Point(39983905, -6751055),
Point(39983905, 8251054),
Point(79750000, 10522762),
Point(79983905, 10756667),
Point(79983905, 12248946),
Point(79950248, 12504617),
Point(79709032, 12928156),
Point(79491729, 13102031),
Point(78998946, 13233905),
Point(38501054, 13233905),
Point(37258117, 12901005),
Point(36349000, 11991885),
Point(36100868, 11392844),
Point(36016095, 10748947),
Point(36016095, -6751054),
Point(35930493, -7401249),
Point(4685798, -11733905),
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-9357-two-parabolas.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 256mm when the part is ok and 293mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(256.));
}
// This test case was distilled from GitHub issue #8846.
// Boost Voronoi generator produces degenerated Voronoi diagram with some Voronoi edges intersecting input segments.
// Those Voronoi edges intersecting input segments are causing that perimeters are also generated in places where they shouldn't be.
TEST_CASE("Arachne - #8846 - Degenerated Voronoi diagram - Voronoi edges intersecting input segment", "[ArachneDegeneratedDiagram8846IntersectingInputSegment]") {
const Polygon poly_0 = {
Point( 60000000, 58000000),
Point(-20000000, 53229451),
Point( 49312250, 53229452),
Point( 49443687, 53666225),
Point( 55358348, 50908580),
Point( 53666223, 49443687),
Point( 53229452, 49312250),
Point( 53229452, -49312250),
Point( 53666014, -49443623),
Point(-10000000, -58000000),
Point( 60000000, -58000000),
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 407079;
coord_t perimeter_spacing = 407079;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.32, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-8846-intersecting-input-segment.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
int64_t total_extrusion_length = 0;
for (Arachne::VariableWidthLines &perimeter : perimeters)
for (Arachne::ExtrusionLine &extrusion_line : perimeter)
total_extrusion_length += extrusion_line.getLength();
// Total extrusion length should be around 500mm when the part is ok and 680mm when it has perimeters in places where they shouldn't be.
REQUIRE(total_extrusion_length <= scaled<int64_t>(500.));
}