Rest of the path chaining has been replaced with the new algorithm.

PolylineCollection.cpp/hpp was removed, use Polylines instead.
Various first_point() / last_point() now return references, not copies.
This commit is contained in:
bubnikv 2019-09-27 18:17:21 +02:00
parent 4b35ebe6e5
commit 331c187b39
29 changed files with 266 additions and 364 deletions

View file

@ -131,8 +131,6 @@ add_library(libslic3r STATIC
PolygonTrimmer.hpp
Polyline.cpp
Polyline.hpp
PolylineCollection.cpp
PolylineCollection.hpp
Print.cpp
Print.hpp
PrintBase.cpp

View file

@ -81,8 +81,8 @@ public:
virtual ExtrusionEntity* clone_move() = 0;
virtual ~ExtrusionEntity() {}
virtual void reverse() = 0;
virtual Point first_point() const = 0;
virtual Point last_point() const = 0;
virtual const Point& first_point() const = 0;
virtual const Point& last_point() const = 0;
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
virtual void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const = 0;
@ -139,8 +139,8 @@ public:
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
void reverse() override { this->polyline.reverse(); }
Point first_point() const override { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back(); }
const Point& first_point() const override { return this->polyline.points.front(); }
const Point& last_point() const override { return this->polyline.points.back(); }
size_t size() const { return this->polyline.size(); }
bool empty() const { return this->polyline.empty(); }
bool is_closed() const { return ! this->empty() && this->polyline.points.front() == this->polyline.points.back(); }
@ -200,8 +200,8 @@ public:
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { return this->paths.back().polyline.points.back(); }
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { return this->paths.back().polyline.points.back(); }
double length() const override;
ExtrusionRole role() const override { return this->paths.empty() ? erNone : this->paths.front().role(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
@ -243,8 +243,8 @@ public:
bool make_clockwise();
bool make_counter_clockwise();
void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { assert(first_point() == this->paths.back().polyline.points.back()); return first_point(); }
const Point& first_point() const override { return this->paths.front().polyline.points.front(); }
const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); }
Polygon polygon() const;
double length() const override;
bool split_at_vertex(const Point &point);

View file

@ -1,4 +1,5 @@
#include "ExtrusionEntityCollection.hpp"
#include "ShortestPath.hpp"
#include <algorithm>
#include <cmath>
#include <map>
@ -73,78 +74,31 @@ void ExtrusionEntityCollection::remove(size_t i)
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path(&coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role) const
{
if (this->entities.empty()) return;
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role);
}
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path_from(start_near, &coll, no_reverse, role);
return coll;
}
void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role) const
{
if (this->no_sort) {
*retval = *this;
return;
}
retval->entities.reserve(this->entities.size());
// if we're asked to return the original indices, build a map
std::map<ExtrusionEntity*,size_t> indices_map;
ExtrusionEntitiesPtr my_paths;
for (ExtrusionEntity * const &entity_src : this->entities) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = entity_src->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
continue;
}
}
ExtrusionEntity *entity = entity_src->clone();
my_paths.push_back(entity);
// if (orig_indices != nullptr)
// indices_map[entity] = &entity_src - &this->entities.front();
}
Points endpoints;
for (const ExtrusionEntity *entity : my_paths) {
endpoints.push_back(entity->first_point());
endpoints.push_back((no_reverse || ! entity->can_reverse()) ?
entity->first_point() : entity->last_point());
}
while (! my_paths.empty()) {
// find nearest point
int start_index = start_near.nearest_point_index(endpoints);
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
// never reverse loops, since it's pointless for chained path and callers might depend on orientation
if (start_index % 2 && !no_reverse && entity->can_reverse())
entity->reverse();
retval->entities.push_back(my_paths.at(path_index));
// if (orig_indices != nullptr)
// orig_indices->push_back(indices_map[entity]);
my_paths.erase(my_paths.begin() + path_index);
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
start_near = retval->entities.back()->last_point();
}
ExtrusionEntityCollection out;
if (this->no_sort) {
out = *this;
} else {
if (role == erMixed)
out = *this;
else {
for (const ExtrusionEntity *ee : this->entities) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = ee->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
continue;
}
}
out.entities.emplace_back(ee->clone());
}
}
chain_and_reorder_extrusion_entities(out.entities, &start_near);
}
return out;
}
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const

View file

@ -65,13 +65,10 @@ public:
}
void replace(size_t i, const ExtrusionEntity &entity);
void remove(size_t i);
ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed) const;
ExtrusionEntityCollection chained_path_from(Point start_near, bool no_reverse = false, ExtrusionRole role = erMixed) const;
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed) const;
ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const;
void reverse();
Point first_point() const { return this->entities.front()->first_point(); }
Point last_point() const { return this->entities.back()->last_point(); }
const Point& first_point() const { return this->entities.front()->first_point(); }
const Point& last_point() const { return this->entities.back()->last_point(); }
// Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width.
// Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps.
void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override;

View file

@ -176,7 +176,7 @@ void Fill3DHoneycomb::_fill_surface_single(
}
}
bool first = true;
for (Polyline &polyline : chain_infill_polylines(std::move(polylines))) {
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;

View file

@ -167,7 +167,7 @@ void FillGyroid::_fill_surface_single(
}
}
bool first = true;
for (Polyline &polyline : chain_infill_polylines(std::move(polylines))) {
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;

View file

@ -93,7 +93,7 @@ void FillHoneycomb::_fill_surface_single(
// connect paths
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
Polylines chained = chain_infill_polylines(std::move(paths));
Polylines chained = chain_polylines(std::move(paths));
assert(paths.empty());
paths.clear();
for (Polyline &path : chained) {

View file

@ -1,5 +1,4 @@
#include "../ClipperUtils.hpp"
#include "../PolylineCollection.hpp"
#include "../Surface.hpp"
#include "FillPlanePath.hpp"

View file

@ -93,7 +93,7 @@ void FillRectilinear::_fill_surface_single(
}
}
bool first = true;
for (Polyline &polyline : chain_infill_polylines(std::move(polylines))) {
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
if (! first) {
// Try to connect the lines.
Points &pts_end = polylines_out.back().points;

View file

@ -1969,7 +1969,7 @@ void GCode::process_layer(
m_layer = layers[instance_to_print.layer_id].support_layer;
gcode += this->extrude_support(
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, false, instance_to_print.object_by_extruder.support_extrusion_role));
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, instance_to_print.object_by_extruder.support_extrusion_role));
m_layer = layers[instance_to_print.layer_id].layer();
}
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
@ -2588,10 +2588,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
std::string gcode;
for (const ObjectByExtruder::Island::Region &region : by_region) {
m_config.apply(print.regions()[&region - &by_region.front()]->config());
for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos, false).entities) {
for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) {
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
if (eec) {
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos, false).entities)
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities)
gcode += this->extrude_entity(*ee, "infill");
} else
gcode += this->extrude_entity(*fill, "infill");

View file

@ -3,7 +3,6 @@
#include "ClipperUtils.hpp"
#include "ExPolygon.hpp"
#include "Line.hpp"
#include "PolylineCollection.hpp"
#include "clipper.hpp"
#include <algorithm>
#include <cassert>

View file

@ -61,8 +61,10 @@ public:
{
CoordType dist = point_coord - this->coordinate(idx, dimension);
return (dist * dist < search_radius + CoordType(EPSILON)) ?
// The plane intersects a hypersphere centered at point_coord of search_radius.
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
(dist < CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
// The plane does not intersect the hypersphere.
(dist > CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
}
// Visitor is supposed to return a bit mask of VisitorReturnMask.

View file

@ -6,8 +6,6 @@
#include "SurfaceCollection.hpp"
#include "ExtrusionEntityCollection.hpp"
#include "ExPolygonCollection.hpp"
#include "PolylineCollection.hpp"
namespace Slic3r {
@ -48,7 +46,7 @@ public:
Polygons bridged;
// collection of polylines representing the unsupported bridge edges
PolylineCollection unsupported_bridge_edges;
Polylines unsupported_bridge_edges;
// ordered collection of extrusion paths/loops to build all perimeters
// (this collection contains only ExtrusionEntityCollection objects)

View file

@ -272,7 +272,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
bridges[idx_last].bridge_angle = bd.angle;
if (this->layer()->object()->config().support_material) {
polygons_append(this->bridged, bd.coverage());
this->unsupported_bridge_edges.append(bd.unsupported_edges());
append(this->unsupported_bridge_edges, bd.unsupported_edges());
}
} else if (custom_angle > 0) {
// Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in

View file

@ -34,7 +34,7 @@ public:
void rotate(double angle, const Point &center);
void reverse();
Point first_point() const;
virtual Point last_point() const = 0;
virtual const Point& last_point() const = 0;
virtual Lines lines() const = 0;
size_t size() const { return points.size(); }
bool empty() const { return points.empty(); }

View file

@ -175,10 +175,9 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
perimeter_generator.overhang_flow.width,
perimeter_generator.overhang_flow.height);
// reapply the nearest point search for starting point
// We allow polyline reversal because Clipper may have randomly
// reversed polylines during clipping.
paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
chain_and_reorder_extrusion_paths(paths, &paths.front().first_point());
} else {
ExtrusionPath path(role);
path.polyline = loop.polygon.split_at_first_point();

View file

@ -5,43 +5,12 @@
namespace Slic3r {
Polygon::operator Polygons() const
{
Polygons pp;
pp.push_back(*this);
return pp;
}
Polygon::operator Polyline() const
{
return this->split_at_first_point();
}
Point&
Polygon::operator[](Points::size_type idx)
{
return this->points[idx];
}
const Point&
Polygon::operator[](Points::size_type idx) const
{
return this->points[idx];
}
Point
Polygon::last_point() const
{
return this->points.front(); // last point == first point for polygons
}
Lines Polygon::lines() const
{
return to_lines(*this);
}
Polyline
Polygon::split_at_vertex(const Point &point) const
Polyline Polygon::split_at_vertex(const Point &point) const
{
// find index of point
for (const Point &pt : this->points)
@ -52,8 +21,7 @@ Polygon::split_at_vertex(const Point &point) const
}
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline
Polygon::split_at_index(int index) const
Polyline Polygon::split_at_index(int index) const
{
Polyline polyline;
polyline.points.reserve(this->points.size() + 1);
@ -64,19 +32,6 @@ Polygon::split_at_index(int index) const
return polyline;
}
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline
Polygon::split_at_first_point() const
{
return this->split_at_index(0);
}
Points
Polygon::equally_spaced_points(double distance) const
{
return this->split_at_first_point().equally_spaced_points(distance);
}
/*
int64_t Polygon::area2x() const
{
@ -107,20 +62,17 @@ double Polygon::area() const
return 0.5 * a;
}
bool
Polygon::is_counter_clockwise() const
bool Polygon::is_counter_clockwise() const
{
return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this));
}
bool
Polygon::is_clockwise() const
bool Polygon::is_clockwise() const
{
return !this->is_counter_clockwise();
}
bool
Polygon::make_counter_clockwise()
bool Polygon::make_counter_clockwise()
{
if (!this->is_counter_clockwise()) {
this->reverse();
@ -129,8 +81,7 @@ Polygon::make_counter_clockwise()
return false;
}
bool
Polygon::make_clockwise()
bool Polygon::make_clockwise()
{
if (this->is_counter_clockwise()) {
this->reverse();
@ -139,16 +90,9 @@ Polygon::make_clockwise()
return false;
}
bool
Polygon::is_valid() const
{
return this->points.size() >= 3;
}
// Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line.
bool
Polygon::contains(const Point &point) const
bool Polygon::contains(const Point &point) const
{
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
bool result = false;
@ -174,8 +118,7 @@ Polygon::contains(const Point &point) const
}
// this only works on CCW polygons as CW will be ripped out by Clipper's simplify_polygons()
Polygons
Polygon::simplify(double tolerance) const
Polygons Polygon::simplify(double tolerance) const
{
// repeat first point at the end in order to apply Douglas-Peucker
// on the whole polygon
@ -189,8 +132,7 @@ Polygon::simplify(double tolerance) const
return simplify_polygons(pp);
}
void
Polygon::simplify(double tolerance, Polygons &polygons) const
void Polygon::simplify(double tolerance, Polygons &polygons) const
{
Polygons pp = this->simplify(tolerance);
polygons.reserve(polygons.size() + pp.size());
@ -198,8 +140,7 @@ Polygon::simplify(double tolerance, Polygons &polygons) const
}
// Only call this on convex polygons or it will return invalid results
void
Polygon::triangulate_convex(Polygons* polygons) const
void Polygon::triangulate_convex(Polygons* polygons) const
{
for (Points::const_iterator it = this->points.begin() + 2; it != this->points.end(); ++it) {
Polygon p;
@ -214,8 +155,7 @@ Polygon::triangulate_convex(Polygons* polygons) const
}
// center of mass
Point
Polygon::centroid() const
Point Polygon::centroid() const
{
double area_temp = this->area();
double x_temp = 0;
@ -232,8 +172,7 @@ Polygon::centroid() const
// find all concave vertices (i.e. having an internal angle greater than the supplied angle)
// (external = right side, thus we consider ccw orientation)
Points
Polygon::concave_points(double angle) const
Points Polygon::concave_points(double angle) const
{
Points points;
angle = 2*PI - angle;
@ -256,8 +195,7 @@ Polygon::concave_points(double angle) const
// find all convex vertices (i.e. having an internal angle smaller than the supplied angle)
// (external = right side, thus we consider ccw orientation)
Points
Polygon::convex_points(double angle) const
Points Polygon::convex_points(double angle) const
{
Points points;
angle = 2*PI - angle;

View file

@ -13,13 +13,14 @@ namespace Slic3r {
class Polygon;
typedef std::vector<Polygon> Polygons;
class Polygon : public MultiPoint {
class Polygon : public MultiPoint
{
public:
operator Polygons() const;
operator Polyline() const;
Point& operator[](Points::size_type idx);
const Point& operator[](Points::size_type idx) const;
operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; }
operator Polyline() const { return this->split_at_first_point(); }
Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
Polygon() {}
explicit Polygon(const Points &points): MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.points) {}
@ -34,20 +35,24 @@ public:
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
Point last_point() const;
// last point == first point for polygons
const Point& last_point() const override { return this->points.front(); }
virtual Lines lines() const;
Polyline split_at_vertex(const Point &point) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_index(int index) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_first_point() const;
Points equally_spaced_points(double distance) const;
Polyline split_at_first_point() const { return this->split_at_index(0); }
Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); }
double area() const;
bool is_counter_clockwise() const;
bool is_clockwise() const;
bool make_counter_clockwise();
bool make_clockwise();
bool is_valid() const;
bool is_valid() const { return this->points.size() >= 3; }
// Does an unoriented polygon contain a point?
// Tested by counting intersections along a horizontal line.
bool contains(const Point &point) const;

View file

@ -23,18 +23,17 @@ Polyline::operator Line() const
return Line(this->points.front(), this->points.back());
}
Point
Polyline::leftmost_point() const
const Point& Polyline::leftmost_point() const
{
Point p = this->points.front();
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++it) {
if ((*it)(0) < p(0)) p = *it;
const Point *p = &this->points.front();
for (Points::const_iterator it = this->points.begin() + 1; it != this->points.end(); ++ it) {
if (it->x() < p->x())
p = &(*it);
}
return p;
return *p;
}
Lines
Polyline::lines() const
Lines Polyline::lines() const
{
Lines lines;
if (this->points.size() >= 2) {
@ -205,6 +204,20 @@ BoundingBox get_extents(const Polylines &polylines)
return bb;
}
const Point& leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
const Point *p = &it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {
const Point *p2 = &it->leftmost_point();
if (p2->x() < p->x())
p = p2;
}
return *p;
}
bool remove_degenerate(Polylines &polylines)
{
bool modified = false;

View file

@ -62,9 +62,9 @@ public:
operator Polylines() const;
operator Line() const;
Point last_point() const override { return this->points.back(); }
const Point& last_point() const override { return this->points.back(); }
Point leftmost_point() const;
const Point& leftmost_point() const;
virtual Lines lines() const;
void clip_end(double distance);
void clip_start(double distance);
@ -77,6 +77,15 @@ public:
bool is_straight() const;
};
// Don't use this class in production code, it is used exclusively by the Perl binding for unit tests!
#ifdef PERL_UCHAR_MIN
class PolylineCollection
{
public:
Polylines polylines;
};
#endif /* PERL_UCHAR_MIN */
extern BoundingBox get_extents(const Polyline &polyline);
extern BoundingBox get_extents(const Polylines &polylines);
@ -129,6 +138,8 @@ inline void polylines_append(Polylines &dst, Polylines &&src)
}
}
const Point& leftmost_point(const Polylines &polylines);
bool remove_degenerate(Polylines &polylines);
class ThickPolyline : public Polyline {

View file

@ -1,92 +0,0 @@
#include "PolylineCollection.hpp"
namespace Slic3r {
struct Chaining
{
Point first;
Point last;
size_t idx;
};
template<typename T>
inline int nearest_point_index(const std::vector<Chaining> &pairs, const Point &start_near, bool no_reverse)
{
T dmin = std::numeric_limits<T>::max();
int idx = 0;
for (std::vector<Chaining>::const_iterator it = pairs.begin(); it != pairs.end(); ++it) {
T d = sqr(T(start_near(0) - it->first(0)));
if (d <= dmin) {
d += sqr(T(start_near(1) - it->first(1)));
if (d < dmin) {
idx = (it - pairs.begin()) * 2;
dmin = d;
if (dmin < EPSILON)
break;
}
}
if (! no_reverse) {
d = sqr(T(start_near(0) - it->last(0)));
if (d <= dmin) {
d += sqr(T(start_near(1) - it->last(1)));
if (d < dmin) {
idx = (it - pairs.begin()) * 2 + 1;
dmin = d;
if (dmin < EPSILON)
break;
}
}
}
}
return idx;
}
Polylines PolylineCollection::_chained_path_from(
const Polylines &src,
Point start_near,
bool no_reverse,
bool move_from_src)
{
std::vector<Chaining> endpoints;
endpoints.reserve(src.size());
for (size_t i = 0; i < src.size(); ++ i) {
Chaining c;
c.first = src[i].first_point();
if (! no_reverse)
c.last = src[i].last_point();
c.idx = i;
endpoints.push_back(c);
}
Polylines retval;
while (! endpoints.empty()) {
// find nearest point
int endpoint_index = nearest_point_index<double>(endpoints, start_near, no_reverse);
assert(endpoint_index >= 0 && size_t(endpoint_index) < endpoints.size() * 2);
if (move_from_src) {
retval.push_back(std::move(src[endpoints[endpoint_index/2].idx]));
} else {
retval.push_back(src[endpoints[endpoint_index/2].idx]);
}
if (endpoint_index & 1)
retval.back().reverse();
endpoints.erase(endpoints.begin() + endpoint_index/2);
start_near = retval.back().last_point();
}
return retval;
}
Point PolylineCollection::leftmost_point(const Polylines &polylines)
{
if (polylines.empty())
throw std::invalid_argument("leftmost_point() called on empty PolylineCollection");
Polylines::const_iterator it = polylines.begin();
Point p = it->leftmost_point();
for (++ it; it != polylines.end(); ++it) {
Point p2 = it->leftmost_point();
if (p2(0) < p(0))
p = p2;
}
return p;
}
} // namespace Slic3r

View file

@ -1,47 +0,0 @@
#ifndef slic3r_PolylineCollection_hpp_
#define slic3r_PolylineCollection_hpp_
#include "libslic3r.h"
#include "Polyline.hpp"
namespace Slic3r {
class PolylineCollection
{
static Polylines _chained_path_from(
const Polylines &src,
Point start_near,
bool no_reverse,
bool move_from_src);
public:
Polylines polylines;
void chained_path(PolylineCollection* retval, bool no_reverse = false) const
{ retval->polylines = chained_path(this->polylines, no_reverse); }
void chained_path_from(Point start_near, PolylineCollection* retval, bool no_reverse = false) const
{ retval->polylines = chained_path_from(this->polylines, start_near, no_reverse); }
Point leftmost_point() const
{ return leftmost_point(polylines); }
void append(const Polylines &polylines)
{ this->polylines.insert(this->polylines.end(), polylines.begin(), polylines.end()); }
static Point leftmost_point(const Polylines &polylines);
static Polylines chained_path(Polylines &&src, bool no_reverse = false) {
return (src.empty() || src.front().points.empty()) ?
Polylines() :
_chained_path_from(src, src.front().first_point(), no_reverse, true);
}
static Polylines chained_path_from(Polylines &&src, Point start_near, bool no_reverse = false)
{ return _chained_path_from(src, start_near, no_reverse, true); }
static Polylines chained_path(const Polylines &src, bool no_reverse = false) {
return (src.empty() || src.front().points.empty()) ?
Polylines() :
_chained_path_from(src, src.front().first_point(), no_reverse, false);
}
static Polylines chained_path_from(const Polylines &src, Point start_near, bool no_reverse = false)
{ return _chained_path_from(src, start_near, no_reverse, false); }
};
}
#endif

View file

@ -14,6 +14,44 @@
namespace Slic3r {
// Naive implementation of the Traveling Salesman Problem, it works by always taking the next closest neighbor.
// This implementation will always produce valid result even if some segments cannot reverse.
template<typename EndPointType, typename KDTreeType, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_closest_point(std::vector<EndPointType> &end_points, KDTreeType &kdtree, CouldReverseFunc &could_reverse_func, EndPointType &first_point)
{
assert((end_points.size() & 1) == 0);
size_t num_segments = end_points.size() / 2;
assert(num_segments >= 2);
for (EndPointType &ep : end_points)
ep.chain_id = 0;
std::vector<std::pair<size_t, bool>> out;
out.reserve(num_segments);
size_t first_point_idx = &first_point - end_points.data();
out.emplace_back(first_point_idx / 2, (first_point_idx & 1) != 0);
first_point.chain_id = 1;
size_t this_idx = first_point_idx ^ 1;
for (int iter = (int)num_segments - 2; iter >= 0; -- iter) {
EndPointType &this_point = end_points[this_idx];
this_point.chain_id = 1;
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda).
// Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it.
size_t next_idx = find_closest_point(kdtree, this_point.pos,
[this_idx, &end_points, &could_reverse_func](size_t idx) {
return (idx ^ this_idx) > 1 && end_points[idx].chain_id == 0 && ((idx ^ 1) == 0 || could_reverse_func(idx >> 1));
});
assert(next_idx < end_points.size());
EndPointType &end_point = end_points[next_idx];
end_point.chain_id = 1;
this_idx = next_idx ^ 1;
}
#ifndef _NDEBUG
assert(end_points[this_idx].chain_id == 0);
for (EndPointType &ep : end_points)
assert(&ep == &end_points[this_idx] || ep.chain_id == 1);
#endif /* _NDEBUG */
return out;
}
// Chain perimeters (always closed) and thin fills (closed or open) using a greedy algorithm.
// Solving a Traveling Salesman Problem (TSP) with the modification, that the sites are not always points, but points and segments.
// Solving using a greedy algorithm, where a shortest edge is added to the solution if it does not produce a bifurcation or a cycle.
@ -22,8 +60,8 @@ namespace Slic3r {
// The algorithm builds a tour for the traveling salesman one edge at a time and thus maintains multiple tour fragments, each of which
// is a simple path in the complete graph of cities. At each stage, the algorithm selects the edge of minimal cost that either creates
// a new fragment, extends one of the existing paths or creates a cycle of length equal to the number of cities.
template<typename PointType, typename SegmentEndPointFunc>
std::vector<std::pair<size_t, bool>> chain_segments(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near)
template<typename PointType, typename SegmentEndPointFunc, bool REVERSE_COULD_FAIL, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
{
std::vector<std::pair<size_t, bool>> out;
@ -132,6 +170,8 @@ std::vector<std::pair<size_t, bool>> chain_segments(SegmentEndPointFunc end_poin
first_point->chain_id = equivalent_chain.next();
first_point_idx = idx;
}
EndPoint *initial_point = first_point;
EndPoint *last_point = nullptr;
// Assign the closest point and distance to the end points.
for (EndPoint &end_point : end_points) {
@ -240,12 +280,15 @@ std::vector<std::pair<size_t, bool>> chain_segments(SegmentEndPointFunc end_poin
if (iter == 0) {
// Last iteration. There shall be exactly one or two end points waiting to be connected.
assert(queue.size() == ((first_point == nullptr) ? 2 : 1));
if (first_point == nullptr)
if (first_point == nullptr) {
first_point = queue.top();
while (! queue.empty()) {
queue.top()->edge_out = nullptr;
queue.pop();
first_point->edge_out = nullptr;
}
last_point = queue.top();
last_point->edge_out = nullptr;
queue.pop();
assert(queue.empty());
break;
}
} else {
@ -280,24 +323,77 @@ std::vector<std::pair<size_t, bool>> chain_segments(SegmentEndPointFunc end_poin
// Now interconnect pairs of segments into a chain.
assert(first_point != nullptr);
out.reserve(num_segments);
bool failed = false;
do {
assert(out.size() < num_segments);
size_t first_point_id = first_point - &end_points.front();
size_t segment_id = first_point_id >> 1;
bool reverse = (first_point_id & 1) != 0;
EndPoint *second_point = &end_points[first_point_id ^ 1];
out.emplace_back(segment_id, (first_point_id & 1) != 0);
if (REVERSE_COULD_FAIL) {
if (reverse && ! could_reverse_func(segment_id)) {
failed = true;
break;
}
} else {
assert(! reverse || could_reverse_func(segment_id));
}
out.emplace_back(segment_id, reverse);
first_point = second_point->edge_out;
} while (first_point != nullptr);
if (REVERSE_COULD_FAIL) {
if (failed) {
if (start_near == nullptr) {
// We may try the reverse order.
out.clear();
first_point = last_point;
failed = false;
do {
assert(out.size() < num_segments);
size_t first_point_id = first_point - &end_points.front();
size_t segment_id = first_point_id >> 1;
bool reverse = (first_point_id & 1) != 0;
EndPoint *second_point = &end_points[first_point_id ^ 1];
if (reverse && ! could_reverse_func(segment_id)) {
failed = true;
break;
}
out.emplace_back(segment_id, reverse);
first_point = second_point->edge_out;
} while (first_point != nullptr);
}
}
if (failed)
// As a last resort, try a dumb algorithm, which is not sensitive to edge reversal constraints.
out = chain_segments_closest_point<EndPoint, decltype(kdtree), CouldReverseFunc>(end_points, kdtree, could_reverse_func, (initial_point != nullptr) ? *initial_point : end_points.front());
} else {
assert(! failed);
}
}
assert(out.size() == num_segments);
return out;
}
template<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
{
return chain_segments_greedy_constrained_reversals_<PointType, SegmentEndPointFunc, true, CouldReverseFunc>(end_point_func, could_reverse_func, num_segments, start_near);
}
template<typename PointType, typename SegmentEndPointFunc>
std::vector<std::pair<size_t, bool>> chain_segments_greedy(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near)
{
auto could_reverse_func = [](size_t /* idx */) -> bool { return true; };
return chain_segments_greedy_constrained_reversals_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
}
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
{
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
std::vector<std::pair<size_t, bool>> out = chain_segments<Point, decltype(segment_end_point)>(segment_end_point, entities.size(), start_near);
auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); };
std::vector<std::pair<size_t, bool>> out = chain_segments_greedy_constrained_reversals<Point, decltype(segment_end_point), decltype(could_reverse)>(segment_end_point, could_reverse, entities.size(), start_near);
for (size_t i = 0; i < entities.size(); ++ i) {
ExtrusionEntity *ee = entities[i];
if (ee->is_loop())
@ -328,10 +424,34 @@ void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entitie
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, start_near));
}
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
{
auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); };
return chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, extrusion_paths.size(), start_near);
}
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain)
{
assert(extrusion_paths.size() == chain.size());
std::vector<ExtrusionPath> out;
out.reserve(extrusion_paths.size());
for (const std::pair<size_t, bool> &idx : chain) {
out.emplace_back(std::move(extrusion_paths[idx.first]));
if (idx.second)
out.back().reverse();
}
extrusion_paths.swap(out);
}
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near)
{
reorder_extrusion_paths(extrusion_paths, chain_extrusion_paths(extrusion_paths, start_near));
}
std::vector<size_t> chain_points(const Points &points, Point *start_near)
{
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
std::vector<std::pair<size_t, bool>> ordered = chain_segments<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), start_near);
std::vector<size_t> out;
out.reserve(ordered.size());
for (auto &segment_and_reversal : ordered)
@ -339,12 +459,12 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
return out;
}
Polylines chain_infill_polylines(Polylines &polylines)
Polylines chain_polylines(Polylines &polylines, const Point *start_near)
{
auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); };
std::vector<std::pair<size_t, bool>> ordered = chain_segments<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), nullptr);
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
Polylines out;
out.reserve(polylines.size());
out.reserve(polylines.size());
for (auto &segment_and_reversal : ordered) {
out.emplace_back(std::move(polylines[segment_and_reversal.first]));
if (segment_and_reversal.second)
@ -356,7 +476,7 @@ Polylines chain_infill_polylines(Polylines &polylines)
template<class T> static inline T chain_path_items(const Points &points, const T &items)
{
auto segment_end_point = [&points](size_t idx, bool /* first_point */) -> const Point& { return points[idx]; };
std::vector<std::pair<size_t, bool>> ordered = chain_segments<Point, decltype(segment_end_point)>(segment_end_point, points.size(), nullptr);
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, points.size(), nullptr);
T out;
out.reserve(items.size());
for (auto &segment_and_reversal : ordered)
@ -382,7 +502,7 @@ std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print
}
}
auto segment_end_point = [&object_reference_points](size_t idx, bool /* first_point */) -> const Point& { return object_reference_points[idx]; };
std::vector<std::pair<size_t, bool>> ordered = chain_segments<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), nullptr);
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, instances.size(), nullptr);
std::vector<std::pair<size_t, size_t>> out;
out.reserve(instances.size());
for (auto &segment_and_reversal : ordered)

View file

@ -18,7 +18,11 @@ std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<Extrus
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
Polylines chain_infill_polylines(Polylines &src);
std::vector<std::pair<size_t, bool>> chain_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
void reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, std::vector<std::pair<size_t, bool>> &chain);
void chain_and_reorder_extrusion_paths(std::vector<ExtrusionPath> &extrusion_paths, const Point *start_near = nullptr);
Polylines chain_polylines(Polylines &src, const Point *start_near = nullptr);
std::vector<ClipperLib::PolyNode*> chain_clipper_polynodes(const Points &points, const std::vector<ClipperLib::PolyNode*> &items);

View file

@ -923,7 +923,7 @@ namespace SupportMaterialInternal {
//FIXME add supports at regular intervals to support long bridges!
bridges = diff(bridges,
// Offset unsupported edges into polygons.
offset(layerm->unsupported_bridge_edges.polylines, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
offset(layerm->unsupported_bridge_edges, scale_(SUPPORT_MATERIAL_MARGIN), SUPPORT_SURFACES_OFFSET_PARAMETERS));
// Remove bridged areas from the supported areas.
contact_polygons = diff(contact_polygons, bridges, true);
}

View file

@ -14,13 +14,17 @@
void clear();
ExtrusionEntityCollection* chained_path(bool no_reverse, ExtrusionRole role = erMixed)
%code{%
if (no_reverse)
croak("no_reverse must be false");
RETVAL = new ExtrusionEntityCollection();
THIS->chained_path(RETVAL, no_reverse, role);
*RETVAL = THIS->chained_path_from(THIS->entities.front()->first_point());
%};
ExtrusionEntityCollection* chained_path_from(Point* start_near, bool no_reverse, ExtrusionRole role = erMixed)
%code{%
if (no_reverse)
croak("no_reverse must be false");
RETVAL = new ExtrusionEntityCollection();
THIS->chained_path_from(*start_near, RETVAL, no_reverse, role);
*RETVAL = THIS->chained_path_from(*start_near, role);
%};
Clone<Point> first_point();
Clone<Point> last_point();

View file

@ -3,7 +3,6 @@
%{
#include <xsinit.h>
#include "libslic3r/Fill/Fill.hpp"
#include "libslic3r/PolylineCollection.hpp"
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExtrusionEntityCollection.hpp"
%}

View file

@ -19,8 +19,6 @@
%code%{ RETVAL = &THIS->fill_surfaces; %};
Polygons bridged()
%code%{ RETVAL = THIS->bridged; %};
Ref<PolylineCollection> unsupported_bridge_edges()
%code%{ RETVAL = &THIS->unsupported_bridge_edges; %};
Ref<ExtrusionEntityCollection> perimeters()
%code%{ RETVAL = &THIS->perimeters; %};
Ref<ExtrusionEntityCollection> fills()

View file

@ -2,7 +2,11 @@
%{
#include <xsinit.h>
#include "libslic3r/PolylineCollection.hpp"
#include "libslic3r.h"
#include "Polyline.hpp"
#include "ShortestPath.hpp"
%}
%name{Slic3r::Polyline::Collection} class PolylineCollection {
@ -14,16 +18,15 @@
PolylineCollection* chained_path(bool no_reverse)
%code{%
RETVAL = new PolylineCollection();
THIS->chained_path(RETVAL, no_reverse);
RETVAL->polylines = chain_polylines(THIS->polylines, &THIS->polylines.front().first_point());
%};
PolylineCollection* chained_path_from(Point* start_near, bool no_reverse)
%code{%
RETVAL = new PolylineCollection();
THIS->chained_path_from(*start_near, RETVAL, no_reverse);
RETVAL->polylines = chain_polylines(THIS->polylines, start_near);
%};
int count()
%code{% RETVAL = THIS->polylines.size(); %};
Clone<Point> leftmost_point();
%{
PolylineCollection*