Merge branch 'lh_voronoi_refactoring' into master_27x

This commit is contained in:
Lukáš Hejl 2024-02-08 15:41:02 +01:00
commit 054e932d34
27 changed files with 1668 additions and 1724 deletions

View File

@ -10,8 +10,6 @@
#include <functional> #include <functional>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include "utils/VoronoiUtils.hpp"
#include "utils/linearAlg2D.hpp" #include "utils/linearAlg2D.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "SVG.hpp" #include "SVG.hpp"
@ -19,27 +17,10 @@
#include "Geometry/VoronoiUtilsCgal.hpp" #include "Geometry/VoronoiUtilsCgal.hpp"
#include "../EdgeGrid.hpp" #include "../EdgeGrid.hpp"
#include "Geometry/VoronoiUtils.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). #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).
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
{
return dir.to_int() ? CSegment.p() : CSegment.next().p();
}
};
} // namespace boost::polygon
namespace Slic3r::Arachne namespace Slic3r::Arachne
{ {
@ -108,8 +89,7 @@ static void export_graph_to_svg(const std::string
} }
#endif #endif
SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p) SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::vertex_type &vd_node, Point p) {
{
auto he_node_it = vd_node_to_he_node.find(&vd_node); auto he_node_it = vd_node_to_he_node.find(&vd_node);
if (he_node_it == vd_node_to_he_node.end()) if (he_node_it == vd_node_to_he_node.end())
{ {
@ -124,8 +104,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty
} }
} }
void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments) void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments) {
{
auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin());
if (he_edge_it != vd_edge_to_he_edge.end()) if (he_edge_it != vd_edge_to_he_edge.end())
{ // Twin segment(s) have already been made { // Twin segment(s) have already been made
@ -235,22 +214,19 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
} }
} }
Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector<Segment>& segments) Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
{ {
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(vd_edge));
/*Terminology in this function assumes that the edge moves horizontally from /*Terminology in this function assumes that the edge moves horizontally from
left to right. This is not necessarily the case; the edge can go in any left to right. This is not necessarily the case; the edge can go in any
direction, but it helps to picture it in a certain direction in your head.*/ direction, but it helps to picture it in a certain direction in your head.*/
const vd_t::cell_type* left_cell = vd_edge.cell(); const VD::cell_type *left_cell = vd_edge.cell();
const vd_t::cell_type* right_cell = vd_edge.twin()->cell(); const VD::cell_type *right_cell = vd_edge.twin()->cell();
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()); Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
Point start = VoronoiUtils::p(vd_edge.vertex0()).cast<coord_t>();
Point end = VoronoiUtils::p(vd_edge.vertex1()).cast<coord_t>();
bool point_left = left_cell->contains_point(); bool point_left = left_cell->contains_point();
bool point_right = right_cell->contains_point(); bool point_right = right_cell->contains_point();
@ -260,17 +236,17 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const
} }
else if (point_left != point_right) //This is a parabolic edge between a point and a line. else if (point_left != point_right) //This is a parabolic edge between a point and a line.
{ {
Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments); Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
} }
else //This is a straight edge between two points. else //This is a straight edge between two points.
{ {
/*While the edge is straight, it is still discretized since the part /*While the edge is straight, it is still discretized since the part
becomes narrower between the two points. As such it may need different becomes narrower between the two points. As such it may need different
beadings along the way.*/ beadings along the way.*/
Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end());
coord_t d = (right_point - left_point).cast<int64_t>().norm(); coord_t d = (right_point - left_point).cast<int64_t>().norm();
Point middle = (left_point + right_point) / 2; Point middle = (left_point + right_point) / 2;
Point x_axis_dir = perp(Point(right_point - left_point)); Point x_axis_dir = perp(Point(right_point - left_point));
@ -350,8 +326,7 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const
} }
} }
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) bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments) {
{
if (cell.incident_edge()->is_infinite()) if (cell.incident_edge()->is_infinite())
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell.
@ -359,16 +334,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
// Copy whole cell into graph or not at all // Copy whole cell into graph or not at all
// If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon. // If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon.
if (const vd_t::vertex_type &vert = *cell.incident_edge()->vertex0(); if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0();
vert.x() >= double(std::numeric_limits<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) || vert.x() >= double(std::numeric_limits<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) ||
vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest())) vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest()))
return false; // Don't copy any part of this cell return false; // Don't copy any part of this cell
const Point source_point = VoronoiUtils::getSourcePoint(cell, segments); const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
if (some_point == source_point.cast<int64_t>()) if (some_point == source_point.cast<int64_t>())
some_point = VoronoiUtils::p(cell.incident_edge()->vertex1()); some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1());
//Test if the some_point is even inside the polygon. //Test if the some_point is even inside the polygon.
//The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex.
@ -377,16 +352,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point)) if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point))
return false; // Don't copy any part of this cell return false; // Don't copy any part of this cell
vd_t::edge_type* vd_edge = cell.incident_edge(); const VD::edge_type* vd_edge = cell.incident_edge();
do { do {
assert(vd_edge->is_finite()); assert(vd_edge->is_finite());
if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) { if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
start_source_point = source_point; start_source_point = source_point;
end_source_point = source_point; end_source_point = source_point;
starting_vd_edge = vd_edge->next(); starting_vd_edge = vd_edge->next();
ending_vd_edge = vd_edge; ending_vd_edge = vd_edge;
} else { } else {
assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input.");
} }
} }
while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge());
@ -395,47 +370,6 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
return true; return true;
} }
void SkeletalTrapezoidation::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)
{
const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
const Point from = source_segment.from();
const Point to = source_segment.to();
// Find starting edge
// Find end edge
bool seen_possible_start = false;
bool after_start = false;
bool ending_edge_is_set_before_start = false;
vd_t::edge_type* edge = cell.incident_edge();
do {
if (edge->is_infinite())
continue;
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>() ));
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
starting_vd_edge = edge;
seen_possible_start = true;
}
else if (seen_possible_start) {
after_start = true;
}
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
ending_vd_edge = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
assert(starting_vd_edge && ending_vd_edge);
assert(starting_vd_edge != ending_vd_edge);
start_source_point = source_segment.to();
end_source_point = source_segment.from();
}
SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy,
double transitioning_angle, coord_t discretization_step_size, double transitioning_angle, coord_t discretization_step_size,
coord_t transition_filter_dist, coord_t allowed_filter_deviation, coord_t transition_filter_dist, coord_t allowed_filter_deviation,
@ -450,195 +384,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
constructFromPolygons(polys); constructFromPolygons(polys);
} }
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
if (cell.contains_segment()) {
const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
const Point from = source_segment.from();
const Point to = source_segment.to();
// Find starting edge
// Find end edge
bool seen_possible_start = false;
bool after_start = false;
bool ending_edge_is_set_before_start = false;
VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge();
do {
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());
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>()));
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
starting_vd_edge = edge;
seen_possible_start = true;
} else if (seen_possible_start) {
after_start = true;
}
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
ending_vd_edge = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
if (!starting_vd_edge || !ending_vd_edge || starting_vd_edge == ending_vd_edge)
return true;
}
}
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;
}
using PointMap = SkeletalTrapezoidation::PointMap;
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
const double fix_angle,
const PointMap &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);
}
}
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;
}
inline static std::pair<PointMap, double> try_to_fix_degenerated_voronoi_diagram_by_rotation(
Geometry::VoronoiDiagram &voronoi_diagram,
const Polygons &polys,
Polygons &polys_rotated,
std::vector<SkeletalTrapezoidation::Segment> &segments,
const std::vector<double> &fix_angles)
{
const Polygons polys_rotated_original = polys_rotated;
double fixed_by_angle = fix_angles.front();
PointMap vertex_mapping;
for (const double &fix_angle : fix_angles) {
vertex_mapping.clear();
polys_rotated = polys_rotated_original;
fixed_by_angle = fix_angle;
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
if (detect_voronoi_diagram_known_issues(voronoi_diagram, segments) == VoronoiDiagramStatus::NO_ISSUE_DETECTED)
break;
}
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
return {vertex_mapping, fixed_by_angle};
}
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{ {
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
@ -670,8 +415,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #endif
Geometry::VoronoiDiagram voronoi_diagram; VD voronoi_diagram;
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
#ifdef ARACHNE_DEBUG_VORONOI #ifdef ARACHNE_DEBUG_VORONOI
{ {
@ -680,45 +425,15 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #endif
// When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is
// intersecting input segment, rotate the input polygon and try again.
VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
double fixed_by_angle = fix_angles.front();
PointMap vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys;
if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
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.";
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.";
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
VoronoiDiagramStatus status_after_fix = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
assert(status_after_fix == VoronoiDiagramStatus::NO_ISSUE_DETECTED);
if (status_after_fix == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
else if (status_after_fix == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM)
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
else if (status_after_fix == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
}
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 (const VD::cell_type &cell : voronoi_diagram.cells()) {
if (!cell.incident_edge()) if (!cell.incident_edge())
continue; // There is no spoon continue; // There is no spoon
Point start_source_point; Point start_source_point;
Point end_source_point; Point end_source_point;
vd_t::edge_type* starting_voronoi_edge = nullptr; const VD::edge_type *starting_voronoi_edge = nullptr;
vd_t::edge_type* ending_voronoi_edge = nullptr; const VD::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()) {
@ -727,7 +442,12 @@ process_voronoi_diagram:
continue; continue;
} else { } else {
assert(cell.contains_segment()); assert(cell.contains_segment());
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend());
assert(cell_range.is_valid());
start_source_point = cell_range.segment_start_point;
end_source_point = cell_range.segment_end_point;
starting_voronoi_edge = cell_range.edge_begin;
ending_voronoi_edge = cell_range.edge_end;
} }
if (!starting_voronoi_edge || !ending_voronoi_edge) { if (!starting_voronoi_edge || !ending_voronoi_edge) {
@ -736,69 +456,28 @@ process_voronoi_diagram:
} }
// Copy start to end edge to graph // Copy start to end edge to graph
edge_t* prev_edge = nullptr; assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
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()); edge_t *prev_edge = nullptr;
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, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_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_voronoi_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_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { for (const VD::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(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
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()); Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
Point v1 = VoronoiUtils::p(vd_edge->vertex0()).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_voronoi_edge); graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
} }
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()); transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
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_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;
} }
// 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 (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.";
status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION;
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
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 (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
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 (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED)
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping);
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
#endif #endif

View File

@ -11,8 +11,6 @@
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
#include <Arachne/utils/VoronoiUtils.hpp>
#include "utils/HalfEdgeGraph.hpp" #include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp" #include "utils/PolygonsSegmentIndex.hpp"
#include "utils/ExtrusionJunction.hpp" #include "utils/ExtrusionJunction.hpp"
@ -26,8 +24,9 @@
//#define ARACHNE_DEBUG //#define ARACHNE_DEBUG
//#define ARACHNE_DEBUG_VORONOI //#define ARACHNE_DEBUG_VORONOI
namespace Slic3r::Arachne namespace Slic3r::Arachne {
{
using VD = Slic3r::Geometry::VoronoiDiagram;
/*! /*!
* Main class of the dynamic beading strategies. * Main class of the dynamic beading strategies.
@ -50,8 +49,6 @@ deposition modeling" by Kuipers et al.
*/ */
class SkeletalTrapezoidation class SkeletalTrapezoidation
{ {
using pos_t = double;
using vd_t = boost::polygon::voronoi_diagram<pos_t>;
using graph_t = SkeletalTrapezoidationGraph; using graph_t = SkeletalTrapezoidationGraph;
using edge_t = STHalfEdge; using edge_t = STHalfEdge;
using node_t = STHalfEdgeNode; using node_t = STHalfEdgeNode;
@ -83,7 +80,6 @@ class SkeletalTrapezoidation
public: public:
using Segment = PolygonsSegmentIndex; using Segment = PolygonsSegmentIndex;
using PointMap = ankerl::unordered_dense::map<Point, Point, PointHash>;
using NodeSet = ankerl::unordered_dense::set<node_t*>; using NodeSet = ankerl::unordered_dense::set<node_t*>;
/*! /*!
@ -168,9 +164,9 @@ protected:
* mapping each voronoi VD edge to the corresponding halfedge HE edge * mapping each voronoi VD edge to the corresponding halfedge HE edge
* In case the result segment is discretized, we map the VD edge to the *last* HE edge * In case the result segment is discretized, we map the VD edge to the *last* HE edge
*/ */
ankerl::unordered_dense::map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge; ankerl::unordered_dense::map<const VD::edge_type *, edge_t *> vd_edge_to_he_edge;
ankerl::unordered_dense::map<vd_t::vertex_type*, node_t*> vd_node_to_he_node; ankerl::unordered_dense::map<const VD::vertex_type *, node_t *> vd_node_to_he_node;
node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. node_t &makeNode(const VD::vertex_type &vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
/*! /*!
* (Eventual) returned 'polylines per index' result (from generateToolpaths): * (Eventual) returned 'polylines per index' result (from generateToolpaths):
@ -181,7 +177,7 @@ protected:
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
* \p prev_edge serves as input and output. May be null as input. * \p prev_edge serves as input and output. May be null as input.
*/ */
void transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments); void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments);
/*! /*!
* Discretize a Voronoi edge that represents the medial axis of a vertex- * Discretize a Voronoi edge that represents the medial axis of a vertex-
@ -208,7 +204,7 @@ protected:
* \return A number of coordinates along the edge where the edge is broken * \return A number of coordinates along the edge where the edge is broken
* up into discrete pieces. * up into discrete pieces.
*/ */
Points discretize(const vd_t::edge_type& segment, const std::vector<Segment>& segments); Points discretize(const VD::edge_type& segment, 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
@ -234,33 +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.
*/ */
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); static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments);
/*!
* Compute the range of line segments that surround a cell of the skeletal
* graph that belongs to a line segment of the medial axis.
*
* This should only be used on cells that belong to a central line segment
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
*
* The resulting line segments is just the first and the last segment. They
* are linked to the neighboring segments, so you can iterate over the
* segments until you reach the last segment.
* \param cell The cell to compute the range of line segments for.
* \param[out] start_source_point The start point of the source segment of
* this cell.
* \param[out] end_source_point The end point of the source segment of this
* cell.
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
* loop around the cell starts.
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
* around the cell ends.
* \param points All vertices of the input Polygons.
* \param segments All edges of the input Polygons.
* /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether.
*/
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
@ -603,7 +573,7 @@ protected:
*/ */
void generateLocalMaximaSingleBeads(); void generateLocalMaximaSingleBeads();
friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments); friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
}; };
} // namespace Slic3r::Arachne } // namespace Slic3r::Arachne

View File

@ -156,8 +156,6 @@ struct PathsPointIndexLocator
} }
}; };
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
}//namespace Slic3r::Arachne }//namespace Slic3r::Arachne
namespace std namespace std

View File

@ -27,5 +27,24 @@ public:
} // namespace Slic3r::Arachne } // namespace Slic3r::Arachne
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
{
return dir.to_int() ? CSegment.to() : CSegment.from();
}
};
} // namespace boost::polygon
#endif//UTILS_POLYGONS_SEGMENT_INDEX_H #endif//UTILS_POLYGONS_SEGMENT_INDEX_H

View File

@ -1,251 +0,0 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include <stack>
#include <optional>
#include <boost/log/trivial.hpp>
#include "linearAlg2D.hpp"
#include "VoronoiUtils.hpp"
namespace Slic3r::Arachne
{
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.
}
Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
{
assert(cell.contains_point());
if(!cell.contains_point())
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
switch (cell.source_category()) {
case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT:
assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n");
BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!";
break;
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT:
assert(cell.source_index() < segments.size());
return segments[cell.source_index()].to();
break;
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT:
assert(cell.source_index() < segments.size());
return segments[cell.source_index()].from();
break;
default:
assert(false && "getSourcePoint should only be called on point cells!\n");
break;
}
assert(false && "cell.source_category() is equal to an invalid value!\n");
BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!";
return {};
}
PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
{
assert(cell.contains_point());
if(!cell.contains_point())
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
switch (cell.source_category()) {
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: {
assert(cell.source_index() < segments.size());
PolygonsPointIndex ret = segments[cell.source_index()];
++ret;
return ret;
break;
}
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: {
assert(cell.source_index() < segments.size());
return segments[cell.source_index()];
break;
}
default:
assert(false && "getSourcePoint should only be called on point cells!\n");
break;
}
PolygonsPointIndex ret = segments[cell.source_index()];
return ++ret;
}
const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments)
{
assert(cell.contains_segment());
if (!cell.contains_segment())
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!";
return segments[cell.source_index()];
}
class PointMatrix
{
public:
double matrix[4];
PointMatrix()
{
matrix[0] = 1;
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 1;
}
PointMatrix(double rotation)
{
rotation = rotation / 180 * M_PI;
matrix[0] = cos(rotation);
matrix[1] = -sin(rotation);
matrix[2] = -matrix[1];
matrix[3] = matrix[0];
}
PointMatrix(const Point p)
{
matrix[0] = p.x();
matrix[1] = p.y();
double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1]));
matrix[0] /= f;
matrix[1] /= f;
matrix[2] = -matrix[1];
matrix[3] = matrix[0];
}
static PointMatrix scale(double s)
{
PointMatrix ret;
ret.matrix[0] = s;
ret.matrix[3] = s;
return ret;
}
Point apply(const Point p) const
{
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3]));
}
Point unapply(const Point p) const
{
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3]));
}
};
Points VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle)
{
Points discretized;
// x is distance of point projected on the segment ab
// xx is point projected on the segment ab
const Point a = segment.from();
const Point b = segment.to();
const Point ab = b - a;
const Point as = s - a;
const Point ae = e - a;
const coord_t ab_size = ab.cast<int64_t>().norm();
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t sxex = ex - sx;
assert((as.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
assert((ae.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
const Point ap = p - a;
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
assert((ap.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
Point pxx;
Line(a, b).distance_to_infinite_squared(p, &pxx);
const Point ppxx = pxx - p;
const coord_t d = ppxx.cast<int64_t>().norm();
const PointMatrix rot = PointMatrix(perp(ppxx));
if (d == 0)
{
discretized.emplace_back(s);
discretized.emplace_back(e);
return discretized;
}
const float marking_bound = atan(transitioning_angle * 0.5);
int64_t msx = - marking_bound * int64_t(d); // projected marking_start
int64_t mex = marking_bound * int64_t(d); // projected marking_end
assert(msx <= std::numeric_limits<coord_t>::max());
assert(double(msx) * double(msx) <= double(std::numeric_limits<int64_t>::max()));
assert(mex <= std::numeric_limits<coord_t>::max());
assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits<coord_t>::max());
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx;
Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx;
const int dir = (sx > ex) ? -1 : 1;
if (dir < 0)
{
std::swap(marking_start, marking_end);
std::swap(msx, mex);
}
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
const Point apex = rot.unapply(Point(0, d / 2)) + pxx;
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
assert(!(add_marking_start && add_marking_end) || add_apex);
if(add_marking_start && add_marking_end && !add_apex)
{
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
}
const coord_t step_count = static_cast<coord_t>(static_cast<float>(std::abs(ex - sx)) / approximate_step_size + 0.5);
discretized.emplace_back(s);
for (coord_t step = 1; step < step_count; step++)
{
assert(double(sxex) * double(step) <= double(std::numeric_limits<int64_t>::max()));
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
assert(double(x) * double(x) <= double(std::numeric_limits<int64_t>::max()));
assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits<int64_t>::max()));
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir))
{
discretized.emplace_back(marking_start);
add_marking_start = false;
}
if (add_apex && int64_t(x) * int64_t(dir) > 0)
{
discretized.emplace_back(apex);
add_apex = false; // only add the apex just before the
}
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir))
{
discretized.emplace_back(marking_end);
add_marking_end = false;
}
assert(x <= std::numeric_limits<coord_t>::max() && x >= std::numeric_limits<coord_t>::lowest());
assert(y <= std::numeric_limits<coord_t>::max() && y >= std::numeric_limits<coord_t>::lowest());
const Point result = rot.unapply(Point(x, y)) + pxx;
discretized.emplace_back(result);
}
if (add_apex)
{
discretized.emplace_back(apex);
}
if (add_marking_end)
{
discretized.emplace_back(marking_end);
}
discretized.emplace_back(e);
return discretized;
}
}//namespace Slic3r::Arachne

View File

@ -1,47 +0,0 @@
//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef UTILS_VORONOI_UTILS_H
#define UTILS_VORONOI_UTILS_H
#include <vector>
#include <boost/polygon/voronoi.hpp>
#include "PolygonsSegmentIndex.hpp"
namespace Slic3r::Arachne
{
/*!
*/
class VoronoiUtils
{
public:
using Segment = PolygonsSegmentIndex;
using voronoi_data_t = double;
using vd_t = boost::polygon::voronoi_diagram<voronoi_data_t>;
static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
static Vec2i64 p(const vd_t::vertex_type *node);
/*!
* Discretize a parabola based on (approximate) step size.
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
*/
static Points 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
#endif // UTILS_VORONOI_UTILS_H

View File

@ -214,6 +214,8 @@ set(SLIC3R_SOURCES
Geometry/Voronoi.hpp Geometry/Voronoi.hpp
Geometry/VoronoiOffset.cpp Geometry/VoronoiOffset.cpp
Geometry/VoronoiOffset.hpp Geometry/VoronoiOffset.hpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Geometry/VoronoiVisualUtils.hpp Geometry/VoronoiVisualUtils.hpp
Int128.hpp Int128.hpp
JumpPointSearch.cpp JumpPointSearch.cpp
@ -464,7 +466,6 @@ set(SLIC3R_SOURCES
BranchingTree/BranchingTree.hpp BranchingTree/BranchingTree.hpp
BranchingTree/PointCloud.cpp BranchingTree/PointCloud.cpp
BranchingTree/PointCloud.hpp BranchingTree/PointCloud.hpp
Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.hpp
Arachne/BeadingStrategy/BeadingStrategy.cpp Arachne/BeadingStrategy/BeadingStrategy.cpp
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
@ -495,8 +496,9 @@ set(SLIC3R_SOURCES
Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolygonsSegmentIndex.hpp
Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.hpp
Arachne/utils/PolylineStitcher.cpp Arachne/utils/PolylineStitcher.cpp
Arachne/utils/VoronoiUtils.hpp Geometry/Voronoi.cpp
Arachne/utils/VoronoiUtils.cpp Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.hpp
Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidation.cpp
Arachne/SkeletalTrapezoidationEdge.hpp Arachne/SkeletalTrapezoidationEdge.hpp

View File

@ -124,7 +124,7 @@ static constexpr const char* PRINTABLE_ATTR = "printable";
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count"; static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam"; static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation"; static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
static constexpr const char* KEY_ATTR = "key"; static constexpr const char* KEY_ATTR = "key";
static constexpr const char* VALUE_ATTR = "value"; static constexpr const char* VALUE_ATTR = "value";
@ -362,7 +362,7 @@ namespace Slic3r {
std::vector<Vec3i> triangles; std::vector<Vec3i> triangles;
std::vector<std::string> custom_supports; std::vector<std::string> custom_supports;
std::vector<std::string> custom_seam; std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation; std::vector<std::string> mm_segmentation;
bool empty() { return vertices.empty() || triangles.empty(); } bool empty() { return vertices.empty() || triangles.empty(); }
@ -371,7 +371,7 @@ namespace Slic3r {
triangles.clear(); triangles.clear();
custom_supports.clear(); custom_supports.clear();
custom_seam.clear(); custom_seam.clear();
mmu_segmentation.clear(); mm_segmentation.clear();
} }
}; };
@ -1830,7 +1830,7 @@ namespace Slic3r {
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR)); m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR)); m_curr_object.geometry.mm_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MM_SEGMENTATION_ATTR));
return true; return true;
} }
@ -2320,25 +2320,25 @@ namespace Slic3r {
if (has_transform) if (has_transform)
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
// recreate custom supports, seam and mmu segmentation from previously loaded attribute // recreate custom supports, seam and mm segmentation from previously loaded attribute
volume->supported_facets.reserve(triangles_count); volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count); volume->seam_facets.reserve(triangles_count);
volume->mmu_segmentation_facets.reserve(triangles_count); volume->mm_segmentation_facets.reserve(triangles_count);
for (size_t i=0; i<triangles_count; ++i) { for (size_t i=0; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i; size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size()); assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size()); assert(index < geometry.custom_seam.size());
assert(index < geometry.mmu_segmentation.size()); assert(index < geometry.mm_segmentation.size());
if (! geometry.custom_supports[index].empty()) if (! geometry.custom_supports[index].empty())
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]); volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
if (! geometry.custom_seam[index].empty()) if (! geometry.custom_seam[index].empty())
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]); volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
if (! geometry.mmu_segmentation[index].empty()) if (! geometry.mm_segmentation[index].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]); volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]);
} }
volume->supported_facets.shrink_to_fit(); volume->supported_facets.shrink_to_fit();
volume->seam_facets.shrink_to_fit(); volume->seam_facets.shrink_to_fit();
volume->mmu_segmentation_facets.shrink_to_fit(); volume->mm_segmentation_facets.shrink_to_fit();
if (auto &es = volume_data.shape_configuration; es.has_value()) if (auto &es = volume_data.shape_configuration; es.has_value())
volume->emboss_shape = std::move(es); volume->emboss_shape = std::move(es);
@ -3002,12 +3002,12 @@ namespace Slic3r {
output_buffer += "\""; output_buffer += "\"";
} }
std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i); std::string mm_painting_data_string = volume->mm_segmentation_facets.get_triangle_as_string(i);
if (! mmu_painting_data_string.empty()) { if (! mm_painting_data_string.empty()) {
output_buffer += " "; output_buffer += " ";
output_buffer += MMU_SEGMENTATION_ATTR; output_buffer += MM_SEGMENTATION_ATTR;
output_buffer += "=\""; output_buffer += "=\"";
output_buffer += mmu_painting_data_string; output_buffer += mm_painting_data_string;
output_buffer += "\""; output_buffer += "\"";
} }

View File

@ -6,6 +6,9 @@
#include "clipper.hpp" #include "clipper.hpp"
#include "VoronoiOffset.hpp" #include "VoronoiOffset.hpp"
#include "ClipperUtils.hpp"
#include <boost/log/trivial.hpp>
#ifdef SLIC3R_DEBUG #ifdef SLIC3R_DEBUG
namespace boost { namespace polygon { namespace boost { namespace polygon {
@ -467,7 +470,20 @@ void MedialAxis::build(ThickPolylines* polylines)
test(l.b.y()); test(l.b.y());
} }
#endif // NDEBUG #endif // NDEBUG
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd); m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
// For several ExPolygons in SPE-1729, an invalid Voronoi diagram was produced that wasn't fixable by rotating input data.
// Those ExPolygons contain very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
// So we filter out such thin lines and holes and try to compute the Voronoi diagram again.
if (!m_vd.is_valid()) {
m_lines = to_lines(closing_ex({m_expolygon}, float(2. * SCALED_EPSILON)));
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
if (!m_vd.is_valid())
BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing.";
}
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees // static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); // std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);

View File

@ -0,0 +1,354 @@
#include "Voronoi.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
#include "libslic3r/Geometry/VoronoiUtils.hpp"
#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp"
#include "libslic3r/MultiMaterialSegmentation.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r::Geometry {
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
// Explicit template instantiation.
template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool);
template void VoronoiDiagram::construct_voronoi(ColoredLinesConstIt, ColoredLinesConstIt, bool);
template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
void>::type
VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const SegmentIterator segment_end, const bool try_to_repair_if_needed) {
boost::polygon::construct_voronoi(segment_begin, segment_end, &m_voronoi_diagram);
if (try_to_repair_if_needed) {
if (m_issue_type = detect_known_issues(*this, segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth.";
} else {
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth.";
}
if (m_issue_type = try_to_repair_degenerated_voronoi_diagram(segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input.";
} else {
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input.";
}
m_state = State::REPAIR_UNSUCCESSFUL;
} else {
m_state = State::REPAIR_SUCCESSFUL;
}
} else {
m_state = State::REPAIR_NOT_NEEDED;
m_issue_type = IssueType::NO_ISSUE_DETECTED;
}
} else {
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
}
void VoronoiDiagram::clear()
{
if (m_is_modified) {
m_vertices.clear();
m_edges.clear();
m_cells.clear();
m_is_modified = false;
} else {
m_voronoi_diagram.clear();
}
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
void VoronoiDiagram::copy_to_local(voronoi_diagram_type &voronoi_diagram) {
m_edges.clear();
m_cells.clear();
m_vertices.clear();
// Copy Voronoi edges.
m_edges.reserve(voronoi_diagram.num_edges());
for (const edge_type &edge : voronoi_diagram.edges()) {
m_edges.emplace_back(edge.is_linear(), edge.is_primary());
m_edges.back().color(edge.color());
}
// Copy Voronoi cells.
m_cells.reserve(voronoi_diagram.num_cells());
for (const cell_type &cell : voronoi_diagram.cells()) {
m_cells.emplace_back(cell.source_index(), cell.source_category());
m_cells.back().color(cell.color());
if (cell.incident_edge()) {
size_t incident_edge_idx = cell.incident_edge() - voronoi_diagram.edges().data();
m_cells.back().incident_edge(&m_edges[incident_edge_idx]);
}
}
// Copy Voronoi vertices.
m_vertices.reserve(voronoi_diagram.num_vertices());
for (const vertex_type &vertex : voronoi_diagram.vertices()) {
m_vertices.emplace_back(vertex.x(), vertex.y());
m_vertices.back().color(vertex.color());
if (vertex.incident_edge()) {
size_t incident_edge_idx = vertex.incident_edge() - voronoi_diagram.edges().data();
m_vertices.back().incident_edge(&m_edges[incident_edge_idx]);
}
}
// Assign all pointers for each Voronoi edge.
for (const edge_type &old_edge : voronoi_diagram.edges()) {
size_t edge_idx = &old_edge - voronoi_diagram.edges().data();
edge_type &new_edge = m_edges[edge_idx];
if (old_edge.cell()) {
size_t cell_idx = old_edge.cell() - voronoi_diagram.cells().data();
new_edge.cell(&m_cells[cell_idx]);
}
if (old_edge.vertex0()) {
size_t vertex0_idx = old_edge.vertex0() - voronoi_diagram.vertices().data();
new_edge.vertex0(&m_vertices[vertex0_idx]);
}
if (old_edge.twin()) {
size_t twin_edge_idx = old_edge.twin() - voronoi_diagram.edges().data();
new_edge.twin(&m_edges[twin_edge_idx]);
}
if (old_edge.next()) {
size_t next_edge_idx = old_edge.next() - voronoi_diagram.edges().data();
new_edge.next(&m_edges[next_edge_idx]);
}
if (old_edge.prev()) {
size_t prev_edge_idx = old_edge.prev() - voronoi_diagram.edges().data();
new_edge.prev(&m_edges[prev_edge_idx]);
}
}
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end)
{
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) {
return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX;
} else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) {
return cell_issue_type;
} else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) {
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
return IssueType::NON_PLANAR_VORONOI_DIAGRAM;
}
return IssueType::NO_ISSUE_DETECTED;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram,
const SegmentIterator segment_begin,
const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
using Point = typename boost::polygon::segment_point_type<Segment>::type;
using SegmentCellRange = SegmentCellRange<Point>;
for (VD::cell_type cell : voronoi_diagram.cells()) {
if (cell.is_degenerate() || !cell.contains_segment())
continue; // Skip degenerated cell that has no spoon. Also, skip a cell that doesn't contain a segment.
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segment_begin, segment_end); cell_range.is_valid()) {
// Detection if Voronoi edge is intersecting input segment.
// It detects this type of issue at least in GH issues #8446, #8474 and #8514.
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
const Vec2d source_segment_from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW).template cast<double>();
const Vec2d source_segment_to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH).template cast<double>();
const Vec2d source_segment_vec = source_segment_to - source_segment_from;
// All Voronoi vertices must be on the left side of the source segment, otherwise the Voronoi diagram is invalid.
for (const VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) {
if (edge->is_infinite()) {
// When there is a missing Voronoi vertex, we may encounter an infinite Voronoi edge.
// This happens, for example, in GH issue #8846.
return IssueType::MISSING_VORONOI_VERTEX;
} else if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) {
return IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
}
}
} else {
// When there is a missing Voronoi vertex (especially at one of the endpoints of the input segment),
// the returned cell_range is marked as invalid.
// It detects this type of issue at least in GH issue #8846.
return IssueType::MISSING_VORONOI_VERTEX;
}
}
return IssueType::NO_ISSUE_DETECTED;
}
bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram)
{
for (const voronoi_diagram_type::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;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
IssueType issue_type = m_issue_type;
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
for (const double fix_angle : fix_angles) {
issue_type = try_to_repair_degenerated_voronoi_diagram_by_rotation(segment_begin, segment_end, fix_angle);
if (issue_type == IssueType::NO_ISSUE_DETECTED) {
return issue_type;
}
}
return issue_type;
}
inline VD::vertex_type::color_type encode_input_segment_endpoint(const VD::cell_type::source_index_type cell_source_index, const boost::polygon::direction_1d dir)
{
return (cell_source_index + 1) << 1 | (dir.to_int() ? 1 : 0);
}
template<typename SegmentIterator>
inline typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
decode_input_segment_endpoint(const VD::vertex_type::color_type color, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
const size_t segment_idx = (color >> 1) - 1;
const SegmentIterator segment_it = segment_begin + segment_idx;
const PointType source_point = boost::polygon::segment_traits<SegmentType>::get(*segment_it, ((color & 1) ? boost::polygon::HIGH :
boost::polygon::LOW));
return source_point;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const SegmentIterator segment_begin,
const SegmentIterator segment_end,
const double fix_angle)
{
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
// Copy all segments and rotate their vertices.
std::vector<VoronoiDiagram::Segment> segments_rotated;
segments_rotated.reserve(std::distance(segment_begin, segment_end));
for (auto segment_it = segment_begin; segment_it != segment_end; ++segment_it) {
PointType from = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::LOW);
PointType to = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::HIGH);
segments_rotated.emplace_back(from.rotated(fix_angle), to.rotated(fix_angle));
}
VoronoiDiagram::voronoi_diagram_type voronoi_diagram_rotated;
boost::polygon::construct_voronoi(segments_rotated.begin(), segments_rotated.end(), &voronoi_diagram_rotated);
this->copy_to_local(voronoi_diagram_rotated);
const IssueType issue_type = detect_known_issues(*this, segments_rotated.begin(), segments_rotated.end());
// We want to remap all Voronoi vertices at the endpoints of input segments
// to ensure that Voronoi vertices at endpoints will be preserved after rotation.
// So we assign every Voronoi vertices color to map this Vertex into input segments.
for (cell_type cell : m_cells) {
if (cell.is_degenerate())
continue;
if (cell.contains_segment()) {
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segments_rotated.begin(), segments_rotated.end()); cell_range.is_valid()) {
if (cell_range.edge_end->vertex1()->color() == 0) {
// Vertex 1 of edge_end points to the starting endpoint of the input segment (from() or line.a).
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::LOW);
cell_range.edge_end->vertex1()->color(color);
}
if (cell_range.edge_begin->vertex0()->color() == 0) {
// Vertex 0 of edge_end points to the ending endpoint of the input segment (to() or line.b).
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::HIGH);
cell_range.edge_begin->vertex0()->color(color);
}
} else {
// This could happen when there is a missing Voronoi vertex even after rotation.
assert(cell_range.is_valid());
}
}
// FIXME @hejllukas: Implement mapping also for source points and not just for source segments.
}
// Rotate all Voronoi vertices back.
// When a Voronoi vertex can be mapped to the input segment endpoint, then we don't need to do rotation back.
for (vertex_type &vertex : m_vertices) {
if (vertex.color() == 0) {
// This vertex isn't mapped to any vertex, so we rotate it back.
vertex = VoronoiUtils::make_rotated_vertex(vertex, -fix_angle);
} else {
// This vertex can be mapped to the input segment endpoint.
PointType endpoint = decode_input_segment_endpoint(vertex.color(), segment_begin, segment_end);
vertex_type endpoint_vertex{double(endpoint.x()), double(endpoint.y())};
endpoint_vertex.incident_edge(vertex.incident_edge());
endpoint_vertex.color(vertex.color());
vertex = endpoint_vertex;
}
}
// We have to clear all marked vertices because some algorithms expect that all vertices have a color equal to 0.
for (vertex_type &vertex : m_vertices)
vertex.color(0);
m_voronoi_diagram.clear();
m_is_modified = true;
return issue_type;
}
} // namespace Slic3r::Geometry

View File

@ -8,8 +8,6 @@
#include "../Line.hpp" #include "../Line.hpp"
#include "../Polyline.hpp" #include "../Polyline.hpp"
#define BOOST_VORONOI_USE_GMP 1
#ifdef _MSC_VER #ifdef _MSC_VER
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned // Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
#pragma warning(push) #pragma warning(push)
@ -20,18 +18,182 @@
#pragma warning(pop) #pragma warning(pop)
#endif // _MSC_VER #endif // _MSC_VER
namespace Slic3r { namespace Slic3r::Geometry {
namespace Geometry { class VoronoiDiagram
{
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
public: public:
typedef double coord_type; using coord_type = double;
typedef boost::polygon::point_data<coordinate_type> point_type; using voronoi_diagram_type = boost::polygon::voronoi_diagram<coord_type>;
typedef boost::polygon::segment_data<coordinate_type> segment_type; using point_type = boost::polygon::point_data<voronoi_diagram_type::coordinate_type>;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type; using segment_type = boost::polygon::segment_data<voronoi_diagram_type::coordinate_type>;
using rect_type = boost::polygon::rectangle_data<voronoi_diagram_type::coordinate_type>;
using coordinate_type = voronoi_diagram_type::coordinate_type;
using vertex_type = voronoi_diagram_type::vertex_type;
using edge_type = voronoi_diagram_type::edge_type;
using cell_type = voronoi_diagram_type::cell_type;
using const_vertex_iterator = voronoi_diagram_type::const_vertex_iterator;
using const_edge_iterator = voronoi_diagram_type::const_edge_iterator;
using const_cell_iterator = voronoi_diagram_type::const_cell_iterator;
using vertex_container_type = voronoi_diagram_type::vertex_container_type;
using edge_container_type = voronoi_diagram_type::edge_container_type;
using cell_container_type = voronoi_diagram_type::cell_container_type;
enum class IssueType {
NO_ISSUE_DETECTED,
FINITE_EDGE_WITH_NON_FINITE_VERTEX,
MISSING_VORONOI_VERTEX,
NON_PLANAR_VORONOI_DIAGRAM,
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
UNKNOWN // Repairs are disabled in the constructor.
};
enum class State {
REPAIR_NOT_NEEDED, // The original Voronoi diagram doesn't have any issue.
REPAIR_SUCCESSFUL, // The original Voronoi diagram has some issues, but it was repaired.
REPAIR_UNSUCCESSFUL, // The original Voronoi diagram has some issues, but it wasn't repaired.
UNKNOWN // Repairs are disabled in the constructor.
};
VoronoiDiagram() = default;
virtual ~VoronoiDiagram() = default;
IssueType get_issue_type() const { return m_issue_type; }
State get_state() const { return m_state; }
bool is_valid() const { return m_state != State::REPAIR_UNSUCCESSFUL; }
void clear();
const vertex_container_type &vertices() const { return m_is_modified ? m_vertices : m_voronoi_diagram.vertices(); }
const edge_container_type &edges() const { return m_is_modified ? m_edges : m_voronoi_diagram.edges(); }
const cell_container_type &cells() const { return m_is_modified ? m_cells : m_voronoi_diagram.cells(); }
std::size_t num_vertices() const { return m_is_modified ? m_vertices.size() : m_voronoi_diagram.num_vertices(); }
std::size_t num_edges() const { return m_is_modified ? m_edges.size() : m_voronoi_diagram.num_edges(); }
std::size_t num_cells() const { return m_is_modified ? m_cells.size() : m_voronoi_diagram.num_cells(); }
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
void>::type
construct_voronoi(SegmentIterator segment_begin, SegmentIterator segment_end, bool try_to_repair_if_needed = true);
template<typename PointIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
void>::type
construct_voronoi(const PointIterator first, const PointIterator last)
{
boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram);
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
template<typename PointIterator, typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_and<
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<typename boost::polygon::geometry_concept<
typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type>::type,
void>::type
construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last)
{
boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram);
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
// 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.
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
IssueType>::type
detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
try_to_repair_degenerated_voronoi_diagram_by_rotation(SegmentIterator segment_begin, SegmentIterator segment_end, double fix_angle);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
try_to_repair_degenerated_voronoi_diagram(SegmentIterator segment_begin, SegmentIterator segment_end);
private:
struct Segment
{
Point from;
Point to;
Segment() = delete;
explicit Segment(const Point &from, const Point &to) : from(from), to(to) {}
};
void copy_to_local(voronoi_diagram_type &voronoi_diagram);
// Detect issues related to Voronoi cells, or that can be detected by iterating over Voronoi cells.
// The first type of issue that can be detected is a missing Voronoi vertex, especially when it is
// missing at one of the endpoints of the input segment.
// The second type of issue that can be detected is a Voronoi edge that intersects the input segment.
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
IssueType>::type
detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram);
voronoi_diagram_type m_voronoi_diagram;
vertex_container_type m_vertices;
edge_container_type m_edges;
cell_container_type m_cells;
bool m_is_modified = false;
State m_state = State::UNKNOWN;
IssueType m_issue_type = IssueType::UNKNOWN;
public:
using SegmentIt = std::vector<Slic3r::Geometry::VoronoiDiagram::Segment>::iterator;
friend struct boost::polygon::segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>;
}; };
} } // namespace Slicer::Geometry } // namespace Slic3r::Geometry
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Geometry::VoronoiDiagram::Segment>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>
{
using coordinate_type = coord_t;
using point_type = Slic3r::Point;
using segment_type = Slic3r::Geometry::VoronoiDiagram::Segment;
static inline point_type get(const segment_type &segment, direction_1d dir) { return dir.to_int() ? segment.to : segment.from; }
};
} // namespace boost::polygon
#endif // slic3r_Geometry_Voronoi_hpp_ #endif // slic3r_Geometry_Voronoi_hpp_

View File

@ -0,0 +1,281 @@
#include <Arachne/utils/PolygonsSegmentIndex.hpp>
#include <MultiMaterialSegmentation.hpp>
#include "VoronoiUtils.hpp"
namespace Slic3r::Geometry {
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesIt = ColoredLines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
// Explicit template instantiation.
template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
template ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float);
template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename std::iterator_traits<SegmentIterator>::reference>::type
VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
if (!cell.contains_segment())
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source segment!");
if (cell.source_index() >= size_t(std::distance(segment_begin, segment_end)))
throw Slic3r::OutOfRange("Voronoi cell source index is out of range!");
return *(segment_begin + cell.source_index());
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
if (!cell.contains_point())
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::LOW);
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::HIGH);
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
} else {
throw Slic3r::InvalidArgument("Function get_source_point() should only be called on point cells!");
}
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Arachne::PolygonsPointIndex>::type
VoronoiUtils::get_source_point_index(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
if (!cell.contains_point())
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return (*segment_it);
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return (*segment_it).next();
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
} else {
throw Slic3r::InvalidArgument("Function get_source_point_index() should only be called on point cells!");
}
}
template<typename Segment>
typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
Points>::type
VoronoiUtils::discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle)
{
Points discretized;
// x is distance of point projected on the segment ab
// xx is point projected on the segment ab
const Point a = source_segment.from();
const Point b = source_segment.to();
const Point ab = b - a;
const Point as = start - a;
const Point ae = end - a;
const coord_t ab_size = ab.cast<int64_t>().norm();
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t sxex = ex - sx;
const Point ap = source_point - a;
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
Point pxx;
Line(a, b).distance_to_infinite_squared(source_point, &pxx);
const Point ppxx = pxx - source_point;
const coord_t d = ppxx.cast<int64_t>().norm();
const Vec2d rot = perp(ppxx).cast<double>().normalized();
const double rot_cos_theta = rot.x();
const double rot_sin_theta = rot.y();
if (d == 0) {
discretized.emplace_back(start);
discretized.emplace_back(end);
return discretized;
}
const double marking_bound = atan(transitioning_angle * 0.5);
int64_t msx = -marking_bound * int64_t(d); // projected marking_start
int64_t mex = marking_bound * int64_t(d); // projected marking_end
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
const int dir = (sx > ex) ? -1 : 1;
if (dir < 0) {
std::swap(marking_start, marking_end);
std::swap(msx, mex);
}
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx;
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
assert(!add_marking_start || !add_marking_end || add_apex);
if (add_marking_start && add_marking_end && !add_apex)
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
const coord_t step_count = lround(static_cast<double>(std::abs(ex - sx)) / approximate_step_size);
discretized.emplace_back(start);
for (coord_t step = 1; step < step_count; ++step) {
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) {
discretized.emplace_back(marking_start);
add_marking_start = false;
}
if (add_apex && int64_t(x) * int64_t(dir) > 0) {
discretized.emplace_back(apex);
add_apex = false; // only add the apex just before the
}
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) {
discretized.emplace_back(marking_end);
add_marking_end = false;
}
assert(is_in_range<coord_t>(x) && is_in_range<coord_t>(y));
const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx;
discretized.emplace_back(result);
}
if (add_apex)
discretized.emplace_back(apex);
if (add_marking_end)
discretized.emplace_back(marking_end);
discretized.emplace_back(end);
return discretized;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Geometry::SegmentCellRange<
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
using Point = typename boost::polygon::segment_point_type<Segment>::type;
using SegmentCellRange = SegmentCellRange<Point>;
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
const Point from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW);
const Point to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH);
const Vec2i64 from_i64 = from.template cast<int64_t>();
const Vec2i64 to_i64 = to.template cast<int64_t>();
// FIXME @hejllukas: Ensure that there is no infinite edge during iteration between edge_begin and edge_end.
SegmentCellRange cell_range(to, from);
// Find starting edge and end edge
bool seen_possible_start = false;
bool after_start = false;
bool ending_edge_is_set_before_start = false;
const VD::edge_type *edge = cell.incident_edge();
do {
if (edge->is_infinite())
continue;
Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0());
Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1());
assert(v0 != to_i64 || v1 != from_i64);
if (v0 == to_i64 && !after_start) { // Use the last edge which starts in source_segment.to
cell_range.edge_begin = edge;
seen_possible_start = true;
} else if (seen_possible_start) {
after_start = true;
}
if (v1 == from_i64 && (!cell_range.edge_end || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
cell_range.edge_end = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
return cell_range;
}
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex)
{
assert(vertex != nullptr);
return VoronoiUtils::to_point(*vertex);
}
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type &vertex)
{
const double x = vertex.x(), y = vertex.y();
assert(std::isfinite(x) && std::isfinite(y));
assert(is_in_range<int64_t>(x) && is_in_range<int64_t>(y));
return {std::llround(x), std::llround(y)};
}
bool VoronoiUtils::is_finite(const VD::vertex_type &vertex)
{
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
}
VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle)
{
const double cos_a = std::cos(angle);
const double sin_a = std::sin(angle);
const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y());
const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x());
VD::vertex_type rotated_vertex{rotated_x, rotated_y};
rotated_vertex.incident_edge(vertex.incident_edge());
rotated_vertex.color(vertex.color());
return rotated_vertex;
}
} // namespace Slic3r::Geometry

View File

@ -0,0 +1,120 @@
#ifndef slic3r_VoronoiUtils_hpp_
#define slic3r_VoronoiUtils_hpp_
#include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
using VD = Slic3r::Geometry::VoronoiDiagram;
namespace Slic3r::Geometry {
// Represent trapezoid Voronoi cell around segment.
template<typename PT> struct SegmentCellRange
{
const PT segment_start_point; // The start point of the source segment of this cell.
const PT segment_end_point; // The end point of the source segment of this cell.
const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
SegmentCellRange() = delete;
explicit SegmentCellRange(const PT &segment_start_point, const PT &segment_end_point)
: segment_start_point(segment_start_point), segment_end_point(segment_end_point)
{}
bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; }
};
class VoronoiUtils
{
public:
static Vec2i64 to_point(const VD::vertex_type *vertex);
static Vec2i64 to_point(const VD::vertex_type &vertex);
static bool is_finite(const VD::vertex_type &vertex);
static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename std::iterator_traits<SegmentIterator>::reference>::type
get_source_segment(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
get_source_point(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Arachne::PolygonsPointIndex>::type
get_source_point_index(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
/**
* Discretize a parabola based on (approximate) step size.
*
* Adapted from CuraEngine VoronoiUtils::discretizeParabola by Tim Kuipers @BagelOrb and @Ghostkeeper.
*
* @param approximate_step_size is measured parallel to the source_segment, not along the parabola.
*/
template<typename Segment>
static typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
Points>::type
discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, coord_t approximate_step_size, float transitioning_angle);
/**
* Compute the range of line segments that surround a cell of the skeletal
* graph that belongs to a line segment of the medial axis.
*
* This should only be used on cells that belong to a central line segment
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
*
* The resulting line segments is just the first and the last segment. They
* are linked to the neighboring segments, so you can iterate over the
* segments until you reach the last segment.
*
* Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb,
* Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper.
*
* @param cell The cell to compute the range of line segments for.
* @param segment_begin Begin iterator for all edges of the input Polygons.
* @param segment_end End iterator for all edges of the input Polygons.
* @return Range of line segments that surround the cell.
*/
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Geometry::SegmentCellRange<
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename T> static bool is_in_range(double value)
{
return double(std::numeric_limits<T>::lowest()) <= value && value <= double(std::numeric_limits<T>::max());
}
template<typename T> static bool is_in_range(const VD::vertex_type &vertex)
{
return VoronoiUtils::is_finite(vertex) && is_in_range<T>(vertex.x()) && is_in_range<T>(vertex.y());
}
template<typename T> static bool is_in_range(const VD::edge_type &edge)
{
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr)
return false;
return is_in_range<T>(*edge.vertex0()) && is_in_range<T>(*edge.vertex1());
}
};
} // namespace Slic3r::Geometry
#endif // slic3r_VoronoiUtils_hpp_

View File

@ -7,15 +7,26 @@
#include <CGAL/Surface_sweep_2_algorithms.h> #include <CGAL/Surface_sweep_2_algorithms.h>
#include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp" #include "libslic3r/Geometry/VoronoiUtils.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
#include "libslic3r/MultiMaterialSegmentation.hpp"
#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 PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
// Explicit template instantiation.
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
// The tangent vector of the parabola is computed based on the Proof of the reflective property. // The tangent vector of the parabola is computed based on the Proof of the reflective property.
// https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property // https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property
// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663 // https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663
@ -121,30 +132,30 @@ using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrie
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered; using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
using CGAL_Point = impl::K::Point_2; 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 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 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 CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
inline static Linef make_linef(const VD::edge_type &edge) inline Linef make_linef(const VD::edge_type &edge)
{ {
const VD::vertex_type *v0 = edge.vertex0(); const VD::vertex_type *v0 = edge.vertex0();
const VD::vertex_type *v1 = edge.vertex1(); const VD::vertex_type *v1 = edge.vertex1();
return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())}; return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())};
} }
[[maybe_unused]] inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); } [[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_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_E_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; using CGAL_E_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()}; }; auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_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; }));
std::vector<CGAL_Segment> segments; std::vector<CGAL_E_Segment> segments;
segments.reserve(voronoi_diagram.num_edges()); segments.reserve(voronoi_diagram.num_edges());
for (const VD::edge_type &edge : voronoi_diagram.edges()) { for (const VD::edge_type &edge : voronoi_diagram.edges()) {
@ -163,7 +174,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
for (const VD::edge_type &edge : voronoi_diagram.edges()) for (const VD::edge_type &edge : voronoi_diagram.edges())
edge.color(0); edge.color(0);
std::vector<CGAL_Point> intersections_pt; std::vector<CGAL_E_Point> intersections_pt;
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt)); CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
return intersections_pt.empty(); return intersections_pt.empty();
} }
@ -178,29 +189,44 @@ struct ParabolicSegment
const CGAL::Orientation is_focus_on_left; const CGAL::Orientation is_focus_on_left;
}; };
inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<VoronoiUtils::Segment> &segments) template<typename SegmentIterator>
inline static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
ParabolicSegment>::type
get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{ {
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
assert(edge.is_curved()); assert(edge.is_curved());
const VD::cell_type *left_cell = edge.cell(); const VD::cell_type *left_cell = edge.cell();
const VD::cell_type *right_cell = edge.twin()->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 Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end);
const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments); const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end);
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); 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); 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};
const Point directrix_from = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::LOW);
const Point directrix_to = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::HIGH);
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) { template<typename SegmentIterator>
inline static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
CGAL::Orientation>::type
orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0())); assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
CGAL::Orientation orientation; CGAL::Orientation orientation;
if (edge_a.is_linear() && edge_b.is_linear()) { 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())); 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()) { } else if (edge_a.is_curved() && edge_b.is_curved()) {
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments); const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end);
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments); const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end);
orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a), orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a),
to_cgal_point(parabolic_a.focus), to_cgal_point(parabolic_a.focus),
to_cgal_point(parabolic_a.directrix.a), to_cgal_point(parabolic_a.directrix.a),
@ -216,7 +242,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a; 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 VD::edge_type &parabolic_edge = edge_a.is_curved() ? edge_a : edge_b;
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments); const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end);
orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()), orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()),
to_cgal_point(parabolic.focus), to_cgal_point(parabolic.focus),
to_cgal_point(parabolic.directrix.a), to_cgal_point(parabolic.directrix.a),
@ -230,39 +256,54 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
return 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) template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
bool>::type
check_if_three_edges_are_ccw(const VD::edge_type &edge_first,
const VD::edge_type &edge_second,
const VD::edge_type &edge_third,
const SegmentIterator segment_begin,
const SegmentIterator segment_end)
{ {
assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0())); assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0()));
CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments); CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end);
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 orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN; return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == 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 = orientation_of_two_edges(first, third, segments); CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
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 = orientation_of_two_edges(first, third, segments); CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments); CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
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, const std::vector<VoronoiUtils::Segment> &segments) template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
bool>::type
VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram,
const SegmentIterator segment_begin,
const SegmentIterator segment_end)
{ {
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 {
if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
edges.emplace_back(edge); edges.emplace_back(edge);
edge = edge->rot_next(); edge = edge->rot_next();
@ -271,11 +312,11 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
// Checking for CCW make sense for three and more edges. // Checking for CCW make sense for three and more edges.
if (edges.size() > 2) { if (edges.size() > 2) {
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) { 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 VD::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it; const VD::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 VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments)) if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end))
return false; return false;
} }
} }

View File

@ -6,7 +6,7 @@
#define slic3r_VoronoiUtilsCgal_hpp_ #define slic3r_VoronoiUtilsCgal_hpp_
#include "Voronoi.hpp" #include "Voronoi.hpp"
#include "../Arachne/utils/VoronoiUtils.hpp" #include "../Arachne/utils/PolygonsSegmentIndex.hpp"
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
class VoronoiDiagram; class VoronoiDiagram;
@ -18,8 +18,12 @@ 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, const std::vector<Arachne::VoronoiUtils::Segment> &segments); template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
bool>::type
is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
}; };
} // namespace Slic3r::Geometry } // namespace Slic3r::Geometry

View File

@ -1237,7 +1237,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
vol->supported_facets.assign(volume->supported_facets); vol->supported_facets.assign(volume->supported_facets);
vol->seam_facets.assign(volume->seam_facets); vol->seam_facets.assign(volume->seam_facets);
vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets); vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets);
// Perform conversion only if the target "imperial" state is different from the current one. // Perform conversion only if the target "imperial" state is different from the current one.
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state. // This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
@ -1349,7 +1349,7 @@ void ModelVolume::reset_extra_facets()
{ {
this->supported_facets.reset(); this->supported_facets.reset();
this->seam_facets.reset(); this->seam_facets.reset();
this->mmu_segmentation_facets.reset(); this->mm_segmentation_facets.reset();
} }
@ -1915,7 +1915,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
config.set_new_unique_id(); config.set_new_unique_id();
supported_facets.set_new_unique_id(); supported_facets.set_new_unique_id();
seam_facets.set_new_unique_id(); seam_facets.set_new_unique_id();
mmu_segmentation_facets.set_new_unique_id(); mm_segmentation_facets.set_new_unique_id();
} }
void ModelVolume::rotate(double angle, Axis axis) void ModelVolume::rotate(double angle, Axis axis)
@ -2224,7 +2224,7 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
{ {
return model_property_changed(mo, mo_new, return model_property_changed(mo, mo_new,
[](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; }, [](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; },
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); }); [](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); });
} }
bool model_has_parameter_modifiers_in_objects(const Model &model) bool model_has_parameter_modifiers_in_objects(const Model &model)

View File

@ -823,8 +823,8 @@ public:
// List of seam enforcers/blockers. // List of seam enforcers/blockers.
FacetsAnnotation seam_facets; FacetsAnnotation seam_facets;
// List of mesh facets painted for MMU segmentation. // List of mesh facets painted for MM segmentation.
FacetsAnnotation mmu_segmentation_facets; FacetsAnnotation mm_segmentation_facets;
// Is set only when volume is Embossed Text type // Is set only when volume is Embossed Text type
// Contain information how to re-create volume // Contain information how to re-create volume
@ -929,12 +929,12 @@ public:
this->config.set_new_unique_id(); this->config.set_new_unique_id();
this->supported_facets.set_new_unique_id(); this->supported_facets.set_new_unique_id();
this->seam_facets.set_new_unique_id(); this->seam_facets.set_new_unique_id();
this->mmu_segmentation_facets.set_new_unique_id(); this->mm_segmentation_facets.set_new_unique_id();
} }
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); } bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_seam_painted() const { return !this->seam_facets.empty(); }
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); } bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); }
protected: protected:
friend class Print; friend class Print;
@ -973,11 +973,11 @@ private:
assert(this->config.id().valid()); assert(this->config.id().valid());
assert(this->supported_facets.id().valid()); assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid()); assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid());
assert(this->id() != this->config.id()); assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id()); assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id()); assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id()); assert(this->id() != this->mm_segmentation_facets.id());
return true; return true;
} }
@ -1003,23 +1003,23 @@ private:
ObjectBase(other), ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull), name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets), supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets),
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape) cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{ {
assert(this->id().valid()); assert(this->id().valid());
assert(this->config.id().valid()); assert(this->config.id().valid());
assert(this->supported_facets.id().valid()); assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid()); assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid());
assert(this->id() != this->config.id()); assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id()); assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id()); assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id()); assert(this->id() != this->mm_segmentation_facets.id());
assert(this->id() == other.id()); assert(this->id() == other.id());
assert(this->config.id() == other.config.id()); assert(this->config.id() == other.config.id());
assert(this->supported_facets.id() == other.supported_facets.id()); assert(this->supported_facets.id() == other.supported_facets.id());
assert(this->seam_facets.id() == other.seam_facets.id()); assert(this->seam_facets.id() == other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id()); assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id());
this->set_material_id(other.material_id()); this->set_material_id(other.material_id());
} }
// Providing a new mesh, therefore this volume will get a new unique ID assigned. // Providing a new mesh, therefore this volume will get a new unique ID assigned.
@ -1031,11 +1031,11 @@ private:
assert(this->config.id().valid()); assert(this->config.id().valid());
assert(this->supported_facets.id().valid()); assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid()); assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid()); assert(this->mm_segmentation_facets.id().valid());
assert(this->id() != this->config.id()); assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id()); assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id()); assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id()); assert(this->id() != this->mm_segmentation_facets.id());
assert(this->id() != other.id()); assert(this->id() != other.id());
assert(this->config.id() == other.config.id()); assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id()); this->set_material_id(other.material_id());
@ -1046,11 +1046,11 @@ private:
assert(this->config.id() != other.config.id()); assert(this->config.id() != other.config.id());
assert(this->supported_facets.id() != other.supported_facets.id()); assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id()); assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id()); assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id());
assert(this->id() != this->config.id()); assert(this->id() != this->config.id());
assert(this->supported_facets.empty()); assert(this->supported_facets.empty());
assert(this->seam_facets.empty()); assert(this->seam_facets.empty());
assert(this->mmu_segmentation_facets.empty()); assert(this->mm_segmentation_facets.empty());
} }
ModelVolume& operator=(ModelVolume &rhs) = delete; ModelVolume& operator=(ModelVolume &rhs) = delete;
@ -1058,19 +1058,19 @@ private:
friend class cereal::access; friend class cereal::access;
friend class UndoRedo::StackImpl; friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated. // Used for deserialization, therefore no IDs are allocated.
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) { ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) {
assert(this->id().invalid()); assert(this->id().invalid());
assert(this->config.id().invalid()); assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid()); assert(this->supported_facets.id().invalid());
assert(this->seam_facets.id().invalid()); assert(this->seam_facets.id().invalid());
assert(this->mmu_segmentation_facets.id().invalid()); assert(this->mm_segmentation_facets.id().invalid());
} }
template<class Archive> void load(Archive &ar) { template<class Archive> void load(Archive &ar) {
bool has_convex_hull; bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::load_by_value(ar, supported_facets); cereal::load_by_value(ar, supported_facets);
cereal::load_by_value(ar, seam_facets); cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, mmu_segmentation_facets); cereal::load_by_value(ar, mm_segmentation_facets);
cereal::load_by_value(ar, config); cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration); cereal::load(ar, text_configuration);
cereal::load(ar, emboss_shape); cereal::load(ar, emboss_shape);
@ -1088,7 +1088,7 @@ private:
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info); ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::save_by_value(ar, supported_facets); cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets); cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets); cereal::save_by_value(ar, mm_segmentation_facets);
cereal::save_by_value(ar, config); cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration); cereal::save(ar, text_configuration);
cereal::save(ar, emboss_shape); cereal::save(ar, emboss_shape);

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,41 @@
namespace Slic3r { namespace Slic3r {
class PrintObject; class PrintObject;
class ExPolygon; class ExPolygon;
using ExPolygons = std::vector<ExPolygon>;
struct ColoredLine
{
Line line;
int color;
int poly_idx = -1;
int local_line_idx = -1;
};
using ColoredLines = std::vector<ColoredLine>;
// Returns MMU segmentation based on painting in MMU segmentation gizmo // Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback); std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
} // namespace Slic3r } // namespace Slic3r
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::ColoredLine>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::ColoredLine>
{
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::ColoredLine &line, const direction_1d &dir)
{
return dir.to_int() ? line.line.b : line.line.a;
}
};
} // namespace boost::polygon
#endif // slic3r_MultiMaterialSegmentation_hpp_ #endif // slic3r_MultiMaterialSegmentation_hpp_

View File

@ -78,8 +78,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.supported_facets.assign(mv_src.supported_facets); mv_dst.supported_facets.assign(mv_src.supported_facets);
assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id()); assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id());
mv_dst.seam_facets.assign(mv_src.seam_facets); mv_dst.seam_facets.assign(mv_src.seam_facets);
assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id()); assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id());
mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets); mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets);
//FIXME what to do with the materials? //FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id; // mv_dst.m_material_id = mv_src.m_material_id;
++ i_src; ++ i_src;
@ -1374,7 +1374,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
std::vector<unsigned int> painting_extruders; std::vector<unsigned int> painting_extruders;
if (const auto &volumes = print_object.model_object()->volumes; if (const auto &volumes = print_object.model_object()->volumes;
num_extruders > 1 && num_extruders > 1 &&
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) { std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) {
//FIXME be more specific! Don't enumerate extruders that are not used for painting! //FIXME be more specific! Don't enumerate extruders that are not used for painting!
painting_extruders.assign(num_extruders, 0); painting_extruders.assign(num_extruders, 0);
std::iota(painting_extruders.begin(), painting_extruders.end(), 1); std::iota(painting_extruders.begin(), painting_extruders.end(), 1);

View File

@ -728,7 +728,7 @@ void PrintObject::slice_volumes()
// Is any ModelVolume MMU painted? // Is any ModelVolume MMU painted?
if (const auto& volumes = this->model_object()->volumes; if (const auto& volumes = this->model_object()->volumes;
m_print->config().nozzle_diameter.size() > 1 && m_print->config().nozzle_diameter.size() > 1 &&
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) { std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mm_segmentation_facets.empty(); }) != volumes.end()) {
// If XY Size compensation is also enabled, notify the user that XY Size compensation // If XY Size compensation is also enabled, notify the user that XY Size compensation
// would not be used because the object is multi-material painted. // would not be used because the object is multi-material painted.

View File

@ -4922,7 +4922,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
for (GLVolume *vol : visible_volumes) { for (GLVolume *vol : visible_volumes) {
const int obj_idx = vol->object_idx(); const int obj_idx = vol->object_idx();
const int vol_idx = vol->volume_idx(); const int vol_idx = vol->volume_idx();
const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mmu_segmentation_facets.empty(); const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty();
GLShaderProgram* shader = wxGetApp().get_shader(render_as_painted ? "mm_gouraud" : "gouraud_light"); GLShaderProgram* shader = wxGetApp().get_shader(render_as_painted ? "mm_gouraud" : "gouraud_light");
if (shader == nullptr) if (shader == nullptr)
continue; continue;
@ -4958,7 +4958,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx]; const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx];
const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count); const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count);
TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]); TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]);
ts.deserialize(model_volume.mmu_segmentation_facets.get_data(), true); ts.deserialize(model_volume.mm_segmentation_facets.get_data(), true);
ts.request_update_render_data(); ts.request_update_render_data();
ts.render(nullptr, model_matrix); ts.render(nullptr, model_matrix);

View File

@ -1905,7 +1905,7 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
cnv->get_gizmos_manager().reset_all_states(); cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting")); Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes) for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.reset(); mv->mm_segmentation_facets.reset();
break; break;
case InfoItemType::Sinking: case InfoItemType::Sinking:
@ -2897,7 +2897,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
[type](const ModelVolume *mv) { [type](const ModelVolume *mv) {
return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() : return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() :
type == InfoItemType::CustomSeam ? mv->seam_facets.empty() : type == InfoItemType::CustomSeam ? mv->seam_facets.empty() :
mv->mmu_segmentation_facets.empty()); mv->mm_segmentation_facets.empty());
}); });
break; break;

View File

@ -648,7 +648,7 @@ void Preview::update_layers_slider_mode()
if ((volume->config.has("extruder") && if ((volume->config.has("extruder") &&
volume->config.option("extruder")->getInt() != 0 && // extruder isn't default volume->config.option("extruder")->getInt() != 0 && // extruder isn't default
volume->config.option("extruder")->getInt() != extruder) || volume->config.option("extruder")->getInt() != extruder) ||
!volume->mmu_segmentation_facets.empty()) !volume->mm_segmentation_facets.empty())
return false; return false;
for (const auto& range : object->layer_config_ranges) for (const auto& range : object->layer_config_ranges)

View File

@ -517,7 +517,7 @@ void GLGizmoMmuSegmentation::update_model_object() const
if (! mv->is_model_part()) if (! mv->is_model_part())
continue; continue;
++idx; ++idx;
updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get()); updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get());
} }
if (updated) { if (updated) {
@ -547,7 +547,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count); size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count);
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx])); m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx]));
// Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize(). // Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data(), false); m_triangle_selectors.back()->deserialize(mv->mm_segmentation_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data(); m_triangle_selectors.back()->request_update_render_data();
} }
m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo); m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo);

View File

@ -3702,7 +3702,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
// We need to make sure that the painted data point to existing triangles. // We need to make sure that the painted data point to existing triangles.
new_volume->supported_facets.assign(old_volume->supported_facets); new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets); new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets); new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_facets);
} }
std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back()); std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1); old_model_object->delete_volume(old_model_object->volumes.size() - 1);
@ -7918,10 +7918,10 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string &notificati
// may be different and they would make no sense. // may be different and they would make no sense.
bool paint_removed = false; bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) { for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty(); paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty();
mv->supported_facets.reset(); mv->supported_facets.reset();
mv->seam_facets.reset(); mv->seam_facets.reset();
mv->mmu_segmentation_facets.reset(); mv->mm_segmentation_facets.reset();
} }
if (paint_removed) { if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to. // snapshot_time is captured by copy so the lambda knows where to undo/redo to.

View File

@ -62,7 +62,7 @@ TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]")
// Construction of the Voronoi Diagram. // Construction of the Voronoi Diagram.
VD vd; VD vd;
construct_voronoi(pts.begin(), pts.end(), &vd); vd.construct_voronoi(pts.begin(), pts.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-pts.svg").c_str(), dump_voronoi_to_svg(debug_out_path("voronoi-pts.svg").c_str(),
@ -190,7 +190,7 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]")
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
VD vd; VD vd;
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-lines.svg").c_str(), dump_voronoi_to_svg(debug_out_path("voronoi-lines.svg").c_str(),
@ -298,7 +298,7 @@ TEST_CASE("Voronoi weirdness", "[Voronoi]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-weirdness.svg").c_str(), dump_voronoi_to_svg(debug_out_path("voronoi-weirdness.svg").c_str(),
@ -322,7 +322,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]")
} }
VD vd; VD vd;
construct_voronoi(pts.begin(), pts.end(), &vd); vd.construct_voronoi(pts.begin(), pts.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
// Scale the voronoi vertices and input points, so that the dump_voronoi_to_svg will display them correctly. // Scale the voronoi vertices and input points, so that the dump_voronoi_to_svg will display them correctly.
@ -1319,7 +1319,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
#endif #endif
VD vd; VD vd;
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
for (const auto& edge : vd.edges()) for (const auto& edge : vd.edges())
if (edge.is_finite()) { if (edge.is_finite()) {
@ -1360,7 +1360,7 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
VD vd; VD vd;
Lines lines = to_lines(poly_with_hole); Lines lines = to_lines(poly_with_hole);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : { for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 1, 1 }, OffsetTest { scale_(0.2), 1, 1 },
@ -1426,7 +1426,7 @@ TEST_CASE("Voronoi offset 2", "[VoronoiOffset]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : { for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 }, OffsetTest { scale_(0.2), 2, 2 },
@ -1496,7 +1496,7 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : { for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 }, OffsetTest { scale_(0.2), 2, 2 },
@ -1747,7 +1747,7 @@ TEST_CASE("Voronoi offset with edge collapse", "[VoronoiOffset4]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : { for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 }, OffsetTest { scale_(0.2), 2, 2 },
@ -1858,7 +1858,7 @@ TEST_CASE("Voronoi offset 5", "[VoronoiOffset5]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : { for (const OffsetTest &ot : {
OffsetTest { scale_(2.8), 1, 1 }, OffsetTest { scale_(2.8), 1, 1 },
@ -1916,7 +1916,7 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
Slic3r::Voronoi::annotate_inside_outside(vd, lines); Slic3r::Voronoi::annotate_inside_outside(vd, lines);
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
@ -1966,7 +1966,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines);
#endif #endif
@ -2006,7 +2006,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines);
#endif #endif
@ -2047,7 +2047,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines);
#endif #endif
@ -2091,8 +2091,8 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]")
Geometry::VoronoiDiagram vd_2; Geometry::VoronoiDiagram vd_2;
Lines lines_1 = to_lines(polygon_1); Lines lines_1 = to_lines(polygon_1);
Lines lines_2 = to_lines(polygon_2); Lines lines_2 = to_lines(polygon_2);
construct_voronoi(lines_1.begin(), lines_1.end(), &vd_1); vd_1.construct_voronoi(lines_1.begin(), lines_1.end());
construct_voronoi(lines_2.begin(), lines_2.end(), &vd_2); vd_2.construct_voronoi(lines_2.begin(), lines_2.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1);
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2); dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2);
@ -2124,7 +2124,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines); dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines);
#endif #endif
@ -2164,7 +2164,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines); dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines);
#endif #endif
@ -2226,10 +2226,66 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]")
VD vd; VD vd;
Lines lines = to_lines(poly); Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd); vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT #ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines); dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines);
#endif #endif
// REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd)); // REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd));
} }
// This case is extracted from SPE-1729, where several ExPolygon with very thin lines
// and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDiagramThinLinesSPE1729]")
{
Polygon contour = {
{32247689, -2405501},
{32247733, -2308514},
{32247692, -2405496},
{50484384, 332941},
{50374839, 1052546},
{32938040, -1637993},
{32938024, -1673788},
{32942107, 7220481},
{32252205, 7447599},
{32252476, 8037808},
{32555965, 8277599},
{17729260, 8904718},
{17729236, 8853233},
{17729259, 8904722},
{17039259, 8935481},
{17033440, -3880421},
{17204385, -3852156},
{17723645, -3441873},
{17723762, -3187210},
{17728957, 8240730},
{17728945, 8213866},
{31716233, 7614090},
{20801623, -1009882},
{21253963, -1580792},
{32252082, 7157187},
{32248022, -1673787},
{24245653, -2925506},
{18449246, -3809095},
{18728385, -4449246}
};
Polygon hole = {
{32247789, -2181284},
{32247870, -2003865},
{32247872, -2003866},
{32247752, -2267007}
};
Polygons polygons = {contour, hole};
VD vd;
Lines lines = to_lines(polygons);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("invalid-voronoi-diagram-thin-lines.svg").c_str(), vd, Points(), lines);
#endif
// REQUIRE(vd.is_valid());
}