ENH: sync some code change from prusa
Sync some code change and fix from Prusa. Thanks Prusa. This also can fix github issue #317 Signed-off-by: salt.wei <salt.wei@bambulab.com> Change-Id: If6993e0149733ccf85ed85f82553caa03df7ac60
This commit is contained in:
parent
afd5e241e6
commit
0f1a001fd6
|
@ -21,7 +21,7 @@ DistributedBeadingStrategy::DistributedBeadingStrategy(const coord_t optimal_wid
|
|||
name = "DistributedBeadingStrategy";
|
||||
}
|
||||
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t thickness, coord_t bead_count) const
|
||||
DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(const coord_t thickness, const coord_t bead_count) const
|
||||
{
|
||||
Beading ret;
|
||||
|
||||
|
@ -41,17 +41,23 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
|
|||
weights[bead_idx] = getWeight(bead_idx);
|
||||
|
||||
const float total_weight = std::accumulate(weights.cbegin(), weights.cend(), 0.f);
|
||||
coord_t accumulated_width = 0;
|
||||
for (coord_t bead_idx = 0; bead_idx < bead_count; bead_idx++) {
|
||||
const float weight_fraction = weights[bead_idx] / total_weight;
|
||||
const coord_t splitup_left_over_weight = to_be_divided * weight_fraction;
|
||||
const coord_t width = optimal_width + splitup_left_over_weight;
|
||||
const coord_t width = (bead_idx == bead_count - 1) ? thickness - accumulated_width : optimal_width + splitup_left_over_weight;
|
||||
|
||||
// Be aware that toolpath_locations is computed by dividing the width by 2, so toolpath_locations
|
||||
// could be off by 1 because of rounding errors.
|
||||
if (bead_idx == 0)
|
||||
ret.toolpath_locations.emplace_back(width / 2);
|
||||
else
|
||||
ret.toolpath_locations.emplace_back(ret.toolpath_locations.back() + (ret.bead_widths.back() + width) / 2);
|
||||
ret.bead_widths.emplace_back(width);
|
||||
accumulated_width += width;
|
||||
}
|
||||
ret.left_over = 0;
|
||||
assert((accumulated_width + ret.left_over) == thickness);
|
||||
} else if (bead_count == 2) {
|
||||
const coord_t outer_width = thickness / 2;
|
||||
ret.bead_widths.emplace_back(outer_width);
|
||||
|
@ -68,6 +74,13 @@ DistributedBeadingStrategy::Beading DistributedBeadingStrategy::compute(coord_t
|
|||
ret.left_over = thickness;
|
||||
}
|
||||
|
||||
assert(([&ret = std::as_const(ret), thickness]() -> bool {
|
||||
coord_t total_bead_width = 0;
|
||||
for (const coord_t &bead_width : ret.bead_widths)
|
||||
total_bead_width += bead_width;
|
||||
return (total_bead_width + ret.left_over) == thickness;
|
||||
}()));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "Utils.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Geometry/VoronoiVisualUtils.hpp"
|
||||
#include "Geometry/VoronoiUtilsCgal.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
|
||||
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
|
||||
|
@ -43,6 +44,71 @@ template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
|
|||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static void export_graph_to_svg(const std::string &path,
|
||||
SkeletalTrapezoidationGraph &graph,
|
||||
const Polygons &polys,
|
||||
const std::vector<std::shared_ptr<LineJunctions>> &edge_junctions = {},
|
||||
const bool beat_count = true,
|
||||
const bool transition_middles = true,
|
||||
const bool transition_ends = true)
|
||||
{
|
||||
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
||||
coordf_t stroke_width = scale_(0.03);
|
||||
BoundingBox bbox = get_extents(polys);
|
||||
for (const auto &node : graph.nodes)
|
||||
bbox.merge(node.p);
|
||||
|
||||
bbox.offset(scale_(1.));
|
||||
|
||||
::Slic3r::SVG svg(path.c_str(), bbox);
|
||||
for (const auto &line : to_lines(polys))
|
||||
svg.draw(line, "gray", stroke_width);
|
||||
|
||||
for (const auto &edge : graph.edges)
|
||||
svg.draw(Line(edge.from->p, edge.to->p), (edge.data.centralIsSet() && edge.data.isCentral()) ? "blue" : "cyan", stroke_width);
|
||||
|
||||
for (const auto &line_junction : edge_junctions)
|
||||
for (const auto &extrusion_junction : *line_junction)
|
||||
svg.draw(extrusion_junction.p, "orange", coord_t(stroke_width * 2.));
|
||||
|
||||
if (beat_count) {
|
||||
for (const auto &node : graph.nodes) {
|
||||
svg.draw(node.p, "red", coord_t(stroke_width * 1.6));
|
||||
svg.draw_text(node.p, std::to_string(node.data.bead_count).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (transition_middles) {
|
||||
for (auto &edge : graph.edges) {
|
||||
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionMiddle>> transitions = edge.data.getTransitions(); transitions) {
|
||||
for (auto &transition : *transitions) {
|
||||
Line edge_line = Line(edge.to->p, edge.from->p);
|
||||
double edge_length = edge_line.length();
|
||||
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
|
||||
svg.draw(pt, "magenta", coord_t(stroke_width * 1.5));
|
||||
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (transition_ends) {
|
||||
for (auto &edge : graph.edges) {
|
||||
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionEnd>> transitions = edge.data.getTransitionEnds(); transitions) {
|
||||
for (auto &transition : *transitions) {
|
||||
Line edge_line = Line(edge.to->p, edge.from->p);
|
||||
double edge_length = edge_line.length();
|
||||
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
|
||||
svg.draw(pt, transition.is_lower_end ? "green" : "lime", coord_t(stroke_width * 1.5));
|
||||
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p)
|
||||
{
|
||||
auto he_node_it = vd_node_to_he_node.find(&vd_node);
|
||||
|
@ -285,7 +351,6 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
if (cell.incident_edge()->is_infinite())
|
||||
|
@ -386,7 +451,23 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
|
|||
constructFromPolygons(polys);
|
||||
}
|
||||
|
||||
bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
|
||||
static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.is_finite()) {
|
||||
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) ||
|
||||
!VoronoiUtils::is_finite(*edge.vertex1()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
|
||||
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram))
|
||||
return true;
|
||||
|
||||
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // There is no spoon
|
||||
|
@ -405,7 +486,8 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
|
|||
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr;
|
||||
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge();
|
||||
do {
|
||||
if (edge->is_infinite()) continue;
|
||||
if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
continue;
|
||||
|
||||
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
|
||||
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
|
||||
|
@ -432,8 +514,71 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph)
|
||||
{
|
||||
for (const auto &edge : graph.edges)
|
||||
if (edge.twin == nullptr)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inline static std::unordered_map<Point, Point, PointHash> try_to_fix_degenerated_voronoi_diagram_by_rotation(
|
||||
Geometry::VoronoiDiagram &voronoi_diagram,
|
||||
const Polygons &polys,
|
||||
Polygons &polys_rotated,
|
||||
std::vector<SkeletalTrapezoidation::Segment> &segments,
|
||||
const double fix_angle)
|
||||
{
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
for (Polygon &poly : polys_rotated)
|
||||
poly.rotate(fix_angle);
|
||||
|
||||
assert(polys_rotated.size() == polys.size());
|
||||
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
|
||||
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
|
||||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
|
||||
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
|
||||
}
|
||||
|
||||
segments.clear();
|
||||
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
|
||||
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
|
||||
|
||||
voronoi_diagram.clear();
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
return vertex_mapping;
|
||||
}
|
||||
|
||||
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
|
||||
const double fix_angle,
|
||||
const std::unordered_map<Point, Point, PointHash> &vertex_mapping)
|
||||
{
|
||||
for (STHalfEdgeNode &node : graph.nodes) {
|
||||
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
|
||||
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
|
||||
node.p = node_it->second;
|
||||
else
|
||||
node.p.rotate(-fix_angle);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
{
|
||||
#ifdef ARACHNE_DEBUG
|
||||
this->outline = polys;
|
||||
#endif
|
||||
|
||||
// Check self intersections.
|
||||
assert([&polys]() -> bool {
|
||||
EdgeGrid::Grid grid;
|
||||
|
@ -450,39 +595,57 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
|||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys, poly_idx, point_idx);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
{
|
||||
static int iRun = 0;
|
||||
BoundingBox bbox = get_extents(polys);
|
||||
SVG svg(debug_out_path("arachne_voronoi-input-%d.svg", iRun++).c_str(), bbox);
|
||||
svg.draw_outline(polys, "black", scaled<coordf_t>(0.03f));
|
||||
}
|
||||
#endif
|
||||
|
||||
Geometry::VoronoiDiagram voronoi_diagram;
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing.
|
||||
// When any Voronoi vertex is missing, rotate input polygon and try again.
|
||||
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
|
||||
const double fix_angle = PI / 6;
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
Polygons polys_copy = polys;
|
||||
if (has_missing_voronoi_vertex) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
for (Polygon &poly : polys_copy)
|
||||
poly.rotate(fix_angle);
|
||||
|
||||
assert(polys_copy.size() == polys.size());
|
||||
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
|
||||
assert(polys_copy[poly_idx].size() == polys[poly_idx].size());
|
||||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
|
||||
vertex_mapping.insert({polys[poly_idx][point_idx], polys_copy[poly_idx][point_idx]});
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||||
}
|
||||
#endif
|
||||
|
||||
segments.clear();
|
||||
for (size_t poly_idx = 0; poly_idx < polys_copy.size(); poly_idx++)
|
||||
for (size_t point_idx = 0; point_idx < polys_copy[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys_copy, poly_idx, point_idx);
|
||||
// Try to detect cases when some Voronoi vertex is missing and when
|
||||
// the Voronoi diagram is not planar.
|
||||
// When any Voronoi vertex is missing, or the Voronoi diagram is not
|
||||
// planar, rotate the input polygon and try again.
|
||||
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
|
||||
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
|
||||
const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram);
|
||||
const double fix_angle = PI / 6;
|
||||
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
// polys_copy is referenced through items stored in the std::vector segments.
|
||||
Polygons polys_copy = polys;
|
||||
if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) {
|
||||
if (has_missing_voronoi_vertex)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
else if (!is_voronoi_diagram_planar)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
|
||||
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
|
||||
|
||||
voronoi_diagram.clear();
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
|
||||
else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar;
|
||||
|
||||
process_voronoi_diagram:
|
||||
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
|
||||
for (vd_t::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (!cell.incident_edge())
|
||||
continue; // There is no spoon
|
||||
|
@ -538,16 +701,43 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
|||
prev_edge->to->data.distance_to_boundary = 0;
|
||||
}
|
||||
|
||||
if (has_missing_voronoi_vertex) {
|
||||
for (node_t &node : graph.nodes) {
|
||||
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
|
||||
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
|
||||
node.p = node_it->second;
|
||||
else
|
||||
node.p.rotate(-fix_angle);
|
||||
// For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar.
|
||||
// When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have
|
||||
// a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi
|
||||
// diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram.
|
||||
if (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
degenerated_voronoi_diagram = true;
|
||||
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
|
||||
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input.";
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
this->graph.edges.clear();
|
||||
this->graph.nodes.clear();
|
||||
this->vd_edge_to_he_edge.clear();
|
||||
this->vd_node_to_he_node.clear();
|
||||
|
||||
goto process_voronoi_diagram;
|
||||
}
|
||||
|
||||
if (degenerated_voronoi_diagram) {
|
||||
assert(!has_missing_twin_edge(this->graph));
|
||||
|
||||
if (has_missing_twin_edge(this->graph))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
if (degenerated_voronoi_diagram)
|
||||
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
#endif
|
||||
|
||||
separatePointyQuadEndNodes();
|
||||
|
||||
graph.collapseSmallEdges();
|
||||
|
@ -594,45 +784,62 @@ void SkeletalTrapezoidation::separatePointyQuadEndNodes()
|
|||
// vvvvvvvvvvvvvvvvvvvvv
|
||||
//
|
||||
|
||||
#if 0
|
||||
static void export_graph_to_svg(const std::string &path, const SkeletalTrapezoidationGraph &graph, const Polygons &polys)
|
||||
{
|
||||
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
||||
coordf_t stroke_width = scale_(0.05);
|
||||
BoundingBox bbox;
|
||||
for (const auto &node : graph.nodes)
|
||||
bbox.merge(node.p);
|
||||
|
||||
bbox.offset(scale_(1.));
|
||||
::Slic3r::SVG svg(path.c_str(), bbox);
|
||||
for (const auto &line : to_lines(polys))
|
||||
svg.draw(line, "red", stroke_width);
|
||||
|
||||
for (const auto &edge : graph.edges)
|
||||
svg.draw(Line(edge.from->p, edge.to->p), "cyan", scale_(0.01));
|
||||
}
|
||||
#endif
|
||||
|
||||
void SkeletalTrapezoidation::generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges)
|
||||
{
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static int iRun = 0;
|
||||
#endif
|
||||
|
||||
p_generated_toolpaths = &generated_toolpaths;
|
||||
|
||||
updateIsCentral();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-updateIsCentral-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
filterCentral(central_filter_dist);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-filterCentral-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
if (filter_outermost_central_edges)
|
||||
filterOuterCentral();
|
||||
|
||||
updateBeadCount();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-updateBeadCount-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
filterNoncentralRegions();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-filterNoncentralRegions-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
generateTransitioningRibs();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
generateExtraRibs();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateExtraRibs-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
generateSegments();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-final-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
++iRun;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::updateIsCentral()
|
||||
|
@ -844,11 +1051,24 @@ void SkeletalTrapezoidation::generateTransitioningRibs()
|
|||
|
||||
filterTransitionMids();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static int iRun = 0;
|
||||
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-mids-%d.svg", iRun++), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
ptr_vector_t<std::list<TransitionEnd>> edge_transition_ends; // We only map the half edge in the upward direction. mapped items are not sorted
|
||||
generateAllTransitionEnds(edge_transition_ends);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-ends-%d.svg", iRun++), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
applyTransitions(edge_transition_ends);
|
||||
// Note that the shared pointer lists will be out of scope and thus destroyed here, since the remaining refs are weak_ptr.
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
++iRun;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -1569,16 +1789,37 @@ void SkeletalTrapezoidation::generateSegments()
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
static int iRun = 0;
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-before-propagation-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
propagateBeadingsUpward(upward_quad_mids, node_beadings);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-upward-propagation-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
propagateBeadingsDownward(upward_quad_mids, node_beadings);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-downward-propagation-%d.svg", iRun), this->graph, this->outline);
|
||||
#endif
|
||||
|
||||
ptr_vector_t<LineJunctions> edge_junctions; // junctions ordered high R to low R
|
||||
generateJunctions(node_beadings, edge_junctions);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
export_graph_to_svg(debug_out_path("ST-generateSegments-junctions-%d.svg", iRun), this->graph, this->outline, edge_junctions);
|
||||
#endif
|
||||
|
||||
connectJunctions(edge_junctions);
|
||||
|
||||
generateLocalMaximaSingleBeads();
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
++iRun;
|
||||
#endif
|
||||
}
|
||||
|
||||
SkeletalTrapezoidation::edge_t* SkeletalTrapezoidation::getQuadMaxRedgeTo(edge_t* quad_start_edge)
|
||||
|
@ -1811,7 +2052,10 @@ void SkeletalTrapezoidation::generateJunctions(ptr_vector_t<BeadingPropagation>&
|
|||
for (junction_idx = (std::max(size_t(1), beading->toolpath_locations.size()) - 1) / 2; junction_idx < num_junctions; junction_idx--)
|
||||
{
|
||||
coord_t bead_R = beading->toolpath_locations[junction_idx];
|
||||
if (bead_R <= start_R)
|
||||
// toolpath_locations computed inside DistributedBeadingStrategy could be off by 1 because of rounding errors.
|
||||
// In GH issue #8472, these roundings errors caused missing the middle extrusion.
|
||||
// Adding small epsilon should help resolve those cases.
|
||||
if (bead_R <= start_R + 1)
|
||||
{ // Junction coinciding with start node is used in this function call
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
#include "SkeletalTrapezoidationJoint.hpp"
|
||||
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
|
||||
#include "SkeletalTrapezoidationGraph.hpp"
|
||||
#include "../Geometry/Voronoi.hpp"
|
||||
|
||||
//#define ARACHNE_DEBUG
|
||||
//#define ARACHNE_DEBUG_VORONOI
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
@ -122,6 +126,10 @@ public:
|
|||
*/
|
||||
void generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges = false);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
Polygons outline;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* Auxiliary for referencing one transition along an edge which may contain multiple transitions
|
||||
|
|
|
@ -24,18 +24,19 @@ namespace Slic3r::Arachne
|
|||
{
|
||||
|
||||
WallToolPaths::WallToolPaths(const Polygons& outline, const coord_t bead_width_0, const coord_t bead_width_x,
|
||||
const size_t inset_count, const coord_t wall_0_inset, const WallToolPathsParams ¶ms)
|
||||
const size_t inset_count, const coord_t wall_0_inset, const coordf_t layer_height, const WallToolPathsParams ¶ms)
|
||||
: outline(outline)
|
||||
, bead_width_0(bead_width_0)
|
||||
, bead_width_x(bead_width_x)
|
||||
, inset_count(inset_count)
|
||||
, wall_0_inset(wall_0_inset)
|
||||
, layer_height(layer_height)
|
||||
, print_thin_walls(Slic3r::Arachne::fill_outline_gaps)
|
||||
, min_feature_size(scaled<coord_t>(params.min_feature_size))
|
||||
, min_bead_width(scaled<coord_t>(params.min_bead_width))
|
||||
, small_area_length(static_cast<double>(bead_width_0) / 2.)
|
||||
, toolpaths_generated(false)
|
||||
, wall_transition_filter_deviation(scaled<coord_t>(params.wall_transition_filter_deviation))
|
||||
, toolpaths_generated(false)
|
||||
, m_params(params)
|
||||
{
|
||||
}
|
||||
|
@ -312,61 +313,47 @@ void removeSmallAreas(Polygons &thiss, const double min_area_size, const bool re
|
|||
};
|
||||
|
||||
auto new_end = thiss.end();
|
||||
if(remove_holes)
|
||||
{
|
||||
for(auto it = thiss.begin(); it < new_end; it++)
|
||||
{
|
||||
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector
|
||||
if(fabs(ClipperLib::Area(to_path(*it))) < min_area_size)
|
||||
{
|
||||
new_end--;
|
||||
if (remove_holes) {
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
// All polygons smaller than target are removed by replacing them with a polygon from the back of the vector.
|
||||
if (fabs(ClipperLib::Area(to_path(*it))) < min_area_size) {
|
||||
--new_end;
|
||||
*it = std::move(*new_end);
|
||||
it--; // wind back the iterator such that the polygon just swaped in is checked next
|
||||
continue; // Don't increment the iterator such that the polygon just swapped in is checked next.
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// For each polygon, computes the signed area, move small outlines at the end of the vector and keep pointer on small holes
|
||||
std::vector<Polygon> small_holes;
|
||||
for(auto it = thiss.begin(); it < new_end; it++) {
|
||||
double area = ClipperLib::Area(to_path(*it));
|
||||
if (fabs(area) < min_area_size)
|
||||
{
|
||||
if(area >= 0)
|
||||
{
|
||||
new_end--;
|
||||
for (auto it = thiss.begin(); it < new_end;) {
|
||||
if (double area = ClipperLib::Area(to_path(*it)); fabs(area) < min_area_size) {
|
||||
if (area >= 0) {
|
||||
--new_end;
|
||||
if (it < new_end) {
|
||||
std::swap(*new_end, *it);
|
||||
it--;
|
||||
}
|
||||
else
|
||||
{ // Don't self-swap the last Path
|
||||
continue;
|
||||
} else { // Don't self-swap the last Path
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
small_holes.push_back(*it);
|
||||
}
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
// Removes small holes that have their first point inside one of the removed outlines
|
||||
// Iterating in reverse ensures that unprocessed small holes won't be moved
|
||||
const auto removed_outlines_start = new_end;
|
||||
for (auto hole_it = small_holes.rbegin(); hole_it < small_holes.rend(); hole_it++)
|
||||
{
|
||||
for (auto outline_it = removed_outlines_start; outline_it < thiss.end(); outline_it++)
|
||||
{
|
||||
if (Polygon(*outline_it).contains(*hole_it->begin())) {
|
||||
new_end--;
|
||||
*hole_it = std::move(*new_end);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thiss.resize(new_end-thiss.begin());
|
||||
}
|
||||
|
||||
|
@ -471,7 +458,7 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
|||
// The functions above could produce intersecting polygons that could cause a crash inside Arachne.
|
||||
// Applying Clipper union should be enough to get rid of this issue.
|
||||
// Clipper union also fixed an issue in Arachne that in post-processing Voronoi diagram, some edges
|
||||
// didn't have twin edges (this probably isn't an issue in Boost Voronoi generator).
|
||||
// didn't have twin edges. (a non-planar Voronoi diagram probably caused this).
|
||||
prepared_outline = union_(prepared_outline);
|
||||
|
||||
if (area(prepared_outline) <= 0) {
|
||||
|
@ -479,9 +466,14 @@ const std::vector<VariableWidthLines> &WallToolPaths::generate()
|
|||
return toolpaths;
|
||||
}
|
||||
|
||||
const float external_perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_0), float(this->layer_height));
|
||||
const float perimeter_extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(unscale<float>(bead_width_x), float(this->layer_height));
|
||||
|
||||
const coord_t wall_transition_length = scaled<coord_t>(this->m_params.wall_transition_length);
|
||||
const double wall_split_middle_threshold = this->m_params.wall_split_middle_threshold; // For an uneven nr. of lines: When to split the middle wall into two.
|
||||
const double wall_add_middle_threshold = this->m_params.wall_add_middle_threshold; // For an even nr. of lines: When to add a new middle in between the innermost two walls.
|
||||
|
||||
const double wall_split_middle_threshold = std::clamp(2. * unscaled<double>(this->min_bead_width) / external_perimeter_extrusion_width - 1., 0.01, 0.99); // For an uneven nr. of lines: When to split the middle wall into two.
|
||||
const double wall_add_middle_threshold = std::clamp(unscaled<double>(this->min_bead_width) / perimeter_extrusion_width, 0.01, 0.99); // For an even nr. of lines: When to add a new middle in between the innermost two walls.
|
||||
|
||||
const int wall_distribution_count = this->m_params.wall_distribution_count;
|
||||
const size_t max_bead_count = (inset_count < std::numeric_limits<coord_t>::max() / 2) ? 2 * inset_count : std::numeric_limits<coord_t>::max();
|
||||
const auto beading_strat = BeadingStrategyFactory::makeStrategy
|
||||
|
@ -609,6 +601,14 @@ void WallToolPaths::stitchToolPaths(std::vector<VariableWidthLines> &toolpaths,
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// PolylineStitcher, in some cases, produced closed extrusion (polygons),
|
||||
// but the endpoints differ by a small distance. So we reconnect them.
|
||||
// FIXME Lukas H.: Investigate more deeply why it is happening.
|
||||
if (wall_polygon.junctions.front().p != wall_polygon.junctions.back().p &&
|
||||
(wall_polygon.junctions.back().p - wall_polygon.junctions.front().p).cast<double>().norm() < stitch_distance) {
|
||||
wall_polygon.junctions.emplace_back(wall_polygon.junctions.front());
|
||||
}
|
||||
wall_polygon.is_closed = true;
|
||||
wall_lines.emplace_back(std::move(wall_polygon)); // add stitched polygons to result
|
||||
}
|
||||
|
|
|
@ -29,8 +29,6 @@ public:
|
|||
float wall_transition_angle;
|
||||
float wall_transition_filter_deviation;
|
||||
int wall_distribution_count;
|
||||
float wall_add_middle_threshold;
|
||||
float wall_split_middle_threshold;
|
||||
};
|
||||
|
||||
class WallToolPaths
|
||||
|
@ -44,7 +42,7 @@ public:
|
|||
* \param inset_count The maximum number of parallel extrusion lines that make up the wall
|
||||
* \param wall_0_inset How far to inset the outer wall, to make it adhere better to other walls.
|
||||
*/
|
||||
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, const WallToolPathsParams ¶ms);
|
||||
WallToolPaths(const Polygons& outline, coord_t bead_width_0, coord_t bead_width_x, size_t inset_count, coord_t wall_0_inset, coordf_t layer_height, const WallToolPathsParams ¶ms);
|
||||
|
||||
/*!
|
||||
* Generates the Toolpaths
|
||||
|
@ -123,14 +121,15 @@ private:
|
|||
coord_t bead_width_x; //<! The subsequently extrusion line width with which libArachne generates its walls if WallToolPaths was called with the nominal_bead_width Constructor this is the same as bead_width_0
|
||||
size_t inset_count; //<! The maximum number of walls to generate
|
||||
coord_t wall_0_inset; //<! How far to inset the outer wall. Should only be applied when printing the actual walls, not extra infill/skin/support walls.
|
||||
coordf_t layer_height;
|
||||
bool print_thin_walls; //<! Whether to enable the widening beading meta-strategy for thin features
|
||||
coord_t min_feature_size; //<! The minimum size of the features that can be widened by the widening beading meta-strategy. Features thinner than that will not be printed
|
||||
coord_t min_bead_width; //<! The minimum bead size to use when widening thin model features with the widening beading meta-strategy
|
||||
double small_area_length; //<! The length of the small features which are to be filtered out, this is squared into a surface
|
||||
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
bool toolpaths_generated; //<! Are the toolpaths generated
|
||||
std::vector<VariableWidthLines> toolpaths; //<! The generated toolpaths
|
||||
Polygons inner_contour; //<! The inner contour of the generated toolpaths
|
||||
coord_t wall_transition_filter_deviation; //!< The allowed line width deviation induced by filtering
|
||||
const WallToolPathsParams m_params;
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
|
|||
{
|
||||
const double x = node->x();
|
||||
const double y = node->y();
|
||||
assert(std::isfinite(x) && std::isfinite(y));
|
||||
assert(x <= double(std::numeric_limits<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
|
||||
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::lowest());
|
||||
return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates.
|
||||
|
|
|
@ -35,6 +35,11 @@ public:
|
|||
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
|
||||
*/
|
||||
static std::vector<Point> discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle);
|
||||
|
||||
static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex)
|
||||
{
|
||||
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
|
|
@ -151,6 +151,8 @@ set(lisbslic3r_sources
|
|||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiUtilsCgal.cpp
|
||||
Geometry/VoronoiUtilsCgal.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
Int128.hpp
|
||||
InternalBridgeDetector.cpp
|
||||
|
|
|
@ -441,6 +441,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
|||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
params.use_arachne = surface_fill.params.pattern == ipConcentric;
|
||||
params.layer_height = m_regions[surface_fill.region_id]->layer()->height;
|
||||
|
||||
// BBS
|
||||
params.flow = surface_fill.params.flow;
|
||||
|
|
|
@ -65,6 +65,8 @@ struct FillParams
|
|||
|
||||
// For Concentric infill, to switch between Classic and Arachne.
|
||||
bool use_arachne{ false };
|
||||
// Layer height for Concentric infill with Arachne.
|
||||
coordf_t layer_height { 0.f };
|
||||
|
||||
// BBS
|
||||
Flow flow;
|
||||
|
|
|
@ -83,15 +83,13 @@ void FillConcentric::_fill_surface_single(const FillParams& params,
|
|||
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
|
||||
Arachne::WallToolPathsParams input_params;
|
||||
input_params.min_bead_width = 0.85 * min_nozzle_diameter;
|
||||
input_params.min_feature_size = 0.1;
|
||||
input_params.min_feature_size = 0.25 * min_nozzle_diameter;
|
||||
input_params.wall_transition_length = 1.0 * min_nozzle_diameter;
|
||||
input_params.wall_transition_angle = 10;
|
||||
input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter;
|
||||
input_params.wall_distribution_count = 1;
|
||||
input_params.wall_add_middle_threshold = 0.75;
|
||||
input_params.wall_split_middle_threshold = 0.5;
|
||||
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, input_params);
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params);
|
||||
|
||||
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
|
||||
std::vector<const Arachne::ExtrusionLine*> all_extrusions;
|
||||
|
|
|
@ -27,15 +27,13 @@ void FillConcentricInternal::fill_surface_extrusion(const Surface* surface, cons
|
|||
double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end());
|
||||
Arachne::WallToolPathsParams input_params;
|
||||
input_params.min_bead_width = 0.85 * min_nozzle_diameter;
|
||||
input_params.min_feature_size = 0.1;
|
||||
input_params.min_feature_size = 0.25 * min_nozzle_diameter;
|
||||
input_params.wall_transition_length = 0.4;
|
||||
input_params.wall_transition_angle = 10;
|
||||
input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter;
|
||||
input_params.wall_distribution_count = 1;
|
||||
input_params.wall_add_middle_threshold = 0.75;
|
||||
input_params.wall_split_middle_threshold = 0.5;
|
||||
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, input_params);
|
||||
Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params);
|
||||
|
||||
std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
|
||||
std::vector<const Arachne::ExtrusionLine*> all_extrusions;
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
|
||||
#include <CGAL/Arr_segment_traits_2.h>
|
||||
#include <CGAL/Surface_sweep_2_algorithms.h>
|
||||
|
||||
#include "libslic3r/Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp"
|
||||
|
||||
#include "VoronoiUtilsCgal.hpp"
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
|
||||
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
|
||||
|
||||
inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; }
|
||||
|
||||
// FIXME Lukas H.: Also includes parabolic segments.
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
|
||||
{
|
||||
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
|
||||
[](const VD::edge_type &edge) { return edge.color() == 0; }));
|
||||
|
||||
std::vector<CGAL_Segment> segments;
|
||||
segments.reserve(voronoi_diagram.num_edges());
|
||||
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.color() != 0)
|
||||
continue;
|
||||
|
||||
if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr &&
|
||||
Arachne::VoronoiUtils::is_finite(*edge.vertex0()) && Arachne::VoronoiUtils::is_finite(*edge.vertex1())) {
|
||||
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
|
||||
edge.color(1);
|
||||
assert(edge.twin() != nullptr);
|
||||
edge.twin()->color(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges())
|
||||
edge.color(0);
|
||||
|
||||
std::vector<CGAL_Point> intersections_pt;
|
||||
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
|
||||
return intersections_pt.empty();
|
||||
}
|
||||
|
||||
static bool check_if_three_vectors_are_ccw(const CGAL_Point &common_pt, const CGAL_Point &pt_1, const CGAL_Point &pt_2, const CGAL_Point &test_pt) {
|
||||
CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2);
|
||||
if (orientation == CGAL::Orientation::COLLINEAR) {
|
||||
// The first two edges are collinear, so the third edge must be on the right side on the first of them.
|
||||
return CGAL::orientation(common_pt, pt_1, test_pt) == CGAL::Orientation::RIGHT_TURN;
|
||||
} else if (orientation == CGAL::Orientation::LEFT_TURN) {
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI.
|
||||
// So we need to check if test_pt isn't between them.
|
||||
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt);
|
||||
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
|
||||
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
|
||||
} else {
|
||||
assert(orientation == CGAL::Orientation::RIGHT_TURN);
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI.
|
||||
// So we need to check if test_pt is between them.
|
||||
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt);
|
||||
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
|
||||
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
|
||||
std::vector<const VD::edge_type *> edges;
|
||||
const VD::edge_type *edge = vertex.incident_edge();
|
||||
|
||||
do {
|
||||
// FIXME Lukas H.: Also process parabolic segments.
|
||||
if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
|
||||
Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
edges.emplace_back(edge);
|
||||
|
||||
edge = edge->rot_next();
|
||||
} while (edge != vertex.incident_edge());
|
||||
|
||||
// Checking for CCW make sense for three and more edges.
|
||||
if (edges.size() > 2) {
|
||||
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) {
|
||||
const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
|
||||
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it;
|
||||
const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
|
||||
|
||||
if (!check_if_three_vectors_are_ccw(to_cgal_point(*prev_edge->vertex0()), to_cgal_point(*prev_edge->vertex1()),
|
||||
to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1())))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r::Geometry
|
|
@ -0,0 +1,21 @@
|
|||
#ifndef slic3r_VoronoiUtilsCgal_hpp_
|
||||
#define slic3r_VoronoiUtilsCgal_hpp_
|
||||
|
||||
#include "Voronoi.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
class VoronoiDiagram;
|
||||
|
||||
class VoronoiUtilsCgal
|
||||
{
|
||||
public:
|
||||
// Check if the Voronoi diagram is planar using CGAL sweeping edge algorithm for enumerating all intersections between lines.
|
||||
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
// Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex.
|
||||
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
};
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
#endif // slic3r_VoronoiUtilsCgal_hpp_
|
|
@ -753,10 +753,19 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||
|| opt_key == "bottom_surface_pattern"
|
||||
|| opt_key == "external_fill_link_max_length"
|
||||
|| opt_key == "infill_direction"
|
||||
|| opt_key == "sparse_infill_pattern"
|
||||
|| opt_key == "top_surface_line_width"
|
||||
|| opt_key == "initial_layer_line_width") {
|
||||
steps.emplace_back(posInfill);
|
||||
} else if (opt_key == "sparse_infill_pattern") {
|
||||
steps.emplace_back(posInfill);
|
||||
|
||||
const auto *old_fill_pattern = old_config.option<ConfigOptionEnum<InfillPattern>>(opt_key);
|
||||
const auto *new_fill_pattern = new_config.option<ConfigOptionEnum<InfillPattern>>(opt_key);
|
||||
assert(old_infill && new_infill);
|
||||
// We need to recalculate infill surfaces when infill_only_where_needed is enabled, and we are switching from
|
||||
// the Lightning infill to another infill or vice versa.
|
||||
if (PrintObject::infill_only_where_needed && (new_fill_pattern->value == ipLightning || old_fill_pattern->value == ipLightning))
|
||||
steps.emplace_back(posPrepareInfill);
|
||||
} else if (opt_key == "sparse_infill_density") {
|
||||
// One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes),
|
||||
// normal infill and 100% (solid) infill.
|
||||
|
|
Loading…
Reference in New Issue