Removed the old motion planner.

This commit is contained in:
Vojtech Bubnik 2020-11-17 09:33:10 +01:00
parent 49ce613be7
commit 04c2fde671
12 changed files with 67 additions and 787 deletions

View File

@ -128,8 +128,6 @@ add_library(libslic3r STATIC
CustomGCode.hpp
Arrange.hpp
Arrange.cpp
MotionPlanner.cpp
MotionPlanner.hpp
MultiPoint.cpp
MultiPoint.hpp
MutablePriorityQueue.hpp

View File

@ -1140,13 +1140,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Set other general things.
_write(file, this->preamble());
// Initialize a motion planner for object-to-object travel moves.
m_avoid_crossing_perimeters.reset();
if (print.config().avoid_crossing_perimeters.value) {
m_avoid_crossing_perimeters.init_external_mp(print);
print.throw_if_canceled();
}
// Calculate wiping points if needed
DoExport::init_ooze_prevention(print, m_ooze_prevention);
print.throw_if_canceled();
@ -2054,11 +2047,8 @@ void GCode::process_layer(
for (InstanceToPrint &instance_to_print : instances_to_print) {
m_config.apply(instance_to_print.print_object.config(), true);
m_layer = layers[instance_to_print.layer_id].layer();
if (m_config.avoid_crossing_perimeters) {
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true));
if (m_config.avoid_crossing_perimeters)
m_avoid_crossing_perimeters.init_layer(*m_layer);
}
if (this->config().gcode_label_objects)
gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n";
// When starting a new object, use the external motion planner for the first travel move.

View File

@ -5,7 +5,6 @@
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "MotionPlanner.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "PrintConfig.hpp"
@ -299,7 +298,7 @@ private:
std::set<std::string> m_placeholder_parser_failed_templates;
OozePrevention m_ooze_prevention;
Wipe m_wipe;
AvoidCrossingPerimeters2 m_avoid_crossing_perimeters;
AvoidCrossingPerimeters m_avoid_crossing_perimeters;
bool m_enable_loop_clipping;
// If enabled, the G-code generator will put following comments at the ends
// of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END

View File

@ -1,7 +1,5 @@
#include "../Layer.hpp"
#include "../MotionPlanner.hpp"
#include "../GCode.hpp"
#include "../MotionPlanner.hpp"
#include "../EdgeGrid.hpp"
#include "../Geometry.hpp"
#include "../ShortestPath.hpp"
@ -21,93 +19,6 @@
namespace Slic3r {
void AvoidCrossingPerimeters::init_external_mp(const Print& print)
{
m_external_mp = Slic3r::make_unique<MotionPlanner>(union_ex(this->collect_contours_all_layers(print.objects())));
}
// Plan a travel move while minimizing the number of perimeter crossings.
// point is in unscaled coordinates, in the coordinate system of the current active object
// (set by gcodegen.set_origin()).
Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = this->use_external_mp || this->use_external_mp_once;
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())->
shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin);
if (use_external)
result.translate(-scaled_origin);
return result;
}
// Collect outer contours of all objects over all layers.
// Discard objects only containing thin walls (offset would fail on an empty polygon).
// Used by avoid crossing perimeters feature.
Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects)
{
Polygons islands;
for (const PrintObject* object : objects) {
// Reducing all the object slices into the Z projection in a logarithimc fashion.
// First reduce to half the number of layers.
std::vector<Polygons> polygons_per_layer((object->layers().size() + 1) / 2);
tbb::parallel_for(tbb::blocked_range<size_t>(0, object->layers().size() / 2),
[&object, &polygons_per_layer](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
const Layer* layer1 = object->layers()[i * 2];
const Layer* layer2 = object->layers()[i * 2 + 1];
Polygons polys;
polys.reserve(layer1->lslices.size() + layer2->lslices.size());
for (const ExPolygon& expoly : layer1->lslices)
//FIXME no holes?
polys.emplace_back(expoly.contour);
for (const ExPolygon& expoly : layer2->lslices)
//FIXME no holes?
polys.emplace_back(expoly.contour);
polygons_per_layer[i] = union_(polys);
}
});
if (object->layers().size() & 1) {
const Layer* layer = object->layers().back();
Polygons polys;
polys.reserve(layer->lslices.size());
for (const ExPolygon& expoly : layer->lslices)
//FIXME no holes?
polys.emplace_back(expoly.contour);
polygons_per_layer.back() = union_(polys);
}
// Now reduce down to a single layer.
size_t cnt = polygons_per_layer.size();
while (cnt > 1) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, cnt / 2),
[&polygons_per_layer](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
Polygons polys;
polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size());
polygons_append(polys, polygons_per_layer[i * 2]);
polygons_append(polys, polygons_per_layer[i * 2 + 1]);
polygons_per_layer[i * 2] = union_(polys);
}
});
for (size_t i = 1; i < cnt / 2; ++i)
polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]);
if (cnt & 1)
polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]);
cnt = (cnt + 1) / 2;
}
// And collect copies of the objects.
for (const PrintInstance& instance : object->instances()) {
// All the layers were reduced to the 1st item of polygons_per_layer.
size_t i = islands.size();
polygons_append(islands, polygons_per_layer.front());
for (; i < islands.size(); ++i)
islands[i].translate(instance.shift);
}
}
return islands;
}
// Create a rotation matrix for projection on the given vector
static Matrix2d rotation_by_direction(const Point &direction)
{
@ -274,16 +185,16 @@ static std::pair<Polygons, Polygons> split_expolygon(const ExPolygons &ex_polygo
return std::make_pair(std::move(contours), std::move(holes));
}
static Polyline to_polyline(const std::vector<AvoidCrossingPerimeters2::TravelPoint> &travel)
static Polyline to_polyline(const std::vector<AvoidCrossingPerimeters::TravelPoint> &travel)
{
Polyline result;
result.points.reserve(travel.size());
for (const AvoidCrossingPerimeters2::TravelPoint &t_point : travel)
for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel)
result.append(t_point.point);
return result;
}
static double travel_length(const std::vector<AvoidCrossingPerimeters2::TravelPoint> &travel) {
static double travel_length(const std::vector<AvoidCrossingPerimeters::TravelPoint> &travel) {
double total_length = 0;
for (size_t idx = 1; idx < travel.size(); ++idx)
total_length += (travel[idx].point - travel[idx - 1].point).cast<double>().norm();
@ -295,7 +206,7 @@ static double travel_length(const std::vector<AvoidCrossingPerimeters2::TravelPo
static void export_travel_to_svg(const Polygons &boundary,
const Line &original_travel,
const Polyline &result_travel,
const std::vector<AvoidCrossingPerimeters2::Intersection> &intersections,
const std::vector<AvoidCrossingPerimeters::Intersection> &intersections,
const std::string &path)
{
BoundingBox bbox = get_extents(boundary);
@ -306,21 +217,21 @@ static void export_travel_to_svg(const Polygons
svg.draw(original_travel.a, "black");
svg.draw(original_travel.b, "grey");
for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections)
for (const AvoidCrossingPerimeters::Intersection &intersection : intersections)
svg.draw(intersection.point, "lightseagreen");
}
static void export_travel_to_svg(const Polygons &boundary,
const Line &original_travel,
const std::vector<AvoidCrossingPerimeters2::TravelPoint> &result_travel,
const std::vector<AvoidCrossingPerimeters2::Intersection> &intersections,
const std::vector<AvoidCrossingPerimeters::TravelPoint> &result_travel,
const std::vector<AvoidCrossingPerimeters::Intersection> &intersections,
const std::string &path)
{
export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path);
}
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer)
ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer)
{
const float perimeter_spacing = get_perimeter_spacing(layer);
const float perimeter_offset = perimeter_spacing / 2.f;
@ -380,7 +291,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer)
return result_boundary;
}
ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer)
ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer)
{
const float perimeter_spacing = get_perimeter_spacing_external(layer);
const float perimeter_offset = perimeter_spacing / 2.f;
@ -417,7 +328,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer)
}
// Returns a direction of the shortest path along the polygon boundary
AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines,
AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines,
const size_t start_idx,
const size_t end_idx,
const Point &intersection_first,
@ -447,7 +358,7 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc
return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward;
}
std::vector<AvoidCrossingPerimeters2::TravelPoint> AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid,
std::vector<AvoidCrossingPerimeters::TravelPoint> AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries,
const bool use_heuristics)
@ -521,7 +432,7 @@ std::vector<AvoidCrossingPerimeters2::TravelPoint> AvoidCrossingPerimeters2::sim
return simplified_path;
}
std::vector<AvoidCrossingPerimeters2::TravelPoint> AvoidCrossingPerimeters2::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
std::vector<AvoidCrossingPerimeters::TravelPoint> AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries)
{
@ -600,7 +511,7 @@ std::vector<AvoidCrossingPerimeters2::TravelPoint> AvoidCrossingPerimeters2::sim
return simplified_path;
}
size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries,
size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries,
const EdgeGrid::Grid &edge_grid,
const Point &start,
const Point &end,
@ -697,7 +608,7 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boun
return intersections.size();
}
bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen,
bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen,
const Line & original_travel,
const Polyline &result_travel,
const size_t intersection_count)
@ -731,7 +642,7 @@ bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen,
}
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object.
@ -772,7 +683,7 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point
return result_pl;
}
void AvoidCrossingPerimeters2::init_layer(const Layer &layer)
void AvoidCrossingPerimeters::init_layer(const Layer &layer)
{
m_slice.clear();
m_boundaries.clear();

View File

@ -13,7 +13,6 @@ namespace Slic3r {
// Forward declarations.
class GCode;
class Layer;
class MotionPlanner;
class Point;
class Print;
class PrintObject;
@ -23,44 +22,6 @@ using PrintObjectPtrs = std::vector<PrintObject *>;
class AvoidCrossingPerimeters
{
public:
// this flag triggers the use of the external configuration space
bool use_external_mp;
bool use_external_mp_once; // just for the next travel move
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool disable_once;
AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {}
virtual ~AvoidCrossingPerimeters() = default;
void reset()
{
m_external_mp.reset();
m_layer_mp.reset();
}
virtual void init_external_mp(const Print &print);
virtual void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique<MotionPlanner>(islands); }
virtual Polyline travel_to(const GCode &gcodegen, const Point &point);
virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
{
*could_be_wipe_disabled = true;
return this->travel_to(gcodegen, point);
}
protected:
// For initializing the regions to avoid.
static Polygons collect_contours_all_layers(const PrintObjectPtrs &objects);
std::unique_ptr<MotionPlanner> m_external_mp;
std::unique_ptr<MotionPlanner> m_layer_mp;
};
class AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters
{
public:
struct Intersection
{
@ -88,12 +49,12 @@ public:
struct AllIntersectionsVisitor
{
AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<AvoidCrossingPerimeters2::Intersection> &intersections)
AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<AvoidCrossingPerimeters::Intersection> &intersections)
: grid(grid), intersections(intersections)
{}
AllIntersectionsVisitor(const EdgeGrid::Grid &grid,
std::vector<AvoidCrossingPerimeters2::Intersection> &intersections,
std::vector<AvoidCrossingPerimeters::Intersection> &intersections,
const Matrix2d &transform_to_x_axis,
const Line &travel_line)
: grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line)
@ -125,7 +86,7 @@ public:
}
const EdgeGrid::Grid &grid;
std::vector<AvoidCrossingPerimeters2::Intersection> &intersections;
std::vector<AvoidCrossingPerimeters::Intersection> &intersections;
Matrix2d transform_to_x_axis;
Line travel_line;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> intersection_set;
@ -141,12 +102,12 @@ private:
static Direction get_shortest_direction(
const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last);
static std::vector<AvoidCrossingPerimeters2::TravelPoint> simplify_travel(const EdgeGrid::Grid &edge_grid,
static std::vector<AvoidCrossingPerimeters::TravelPoint> simplify_travel(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries,
const bool use_heuristics);
static std::vector<AvoidCrossingPerimeters2::TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
static std::vector<AvoidCrossingPerimeters::TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries);
@ -173,24 +134,27 @@ private:
EdgeGrid::Grid m_grid_external;
public:
AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {}
// this flag triggers the use of the external configuration space
bool use_external_mp { false };
// just for the next travel move
bool use_external_mp_once { false };
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool disable_once { true };
virtual ~AvoidCrossingPerimeters2() = default;
AvoidCrossingPerimeters() = default;
// Used for disabling unnecessary calling collect_contours_all_layers
virtual void init_external_mp(const Print &print) override {};
virtual void init_layer_mp(const ExPolygons &islands) override {};
virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override
Polyline travel_to(const GCode& gcodegen, const Point& point)
{
bool could_be_wipe_disabled;
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
}
virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) override;
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
void init_layer(const Layer &layer);
};
} // namespace Slic3r
#endif // slic3r_AvoidCrossingPerimeters_hpp_

View File

@ -1,363 +0,0 @@
#include "BoundingBox.hpp"
#include "MotionPlanner.hpp"
#include "MutablePriorityQueue.hpp"
#include "Utils.hpp"
#include <limits> // for numeric_limits
#include <assert.h>
#define BOOST_VORONOI_USE_GMP 1
#include "boost/polygon/voronoi.hpp"
using boost::polygon::voronoi_builder;
using boost::polygon::voronoi_diagram;
namespace Slic3r {
MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false)
{
ExPolygons expp;
for (const ExPolygon &island : islands) {
island.simplify(SCALED_EPSILON, &expp);
for (ExPolygon &island : expp)
m_islands.emplace_back(MotionPlannerEnv(island));
expp.clear();
}
}
void MotionPlanner::initialize()
{
// prevent initialization of empty BoundingBox
if (m_initialized || m_islands.empty())
return;
// loop through islands in order to create inner expolygons and collect their contours.
Polygons outer_holes;
for (MotionPlannerEnv &island : m_islands) {
// Generate the internal env boundaries by shrinking the island
// we'll use these inner rings for motion planning (endpoints of the Voronoi-based
// graph, visibility check) in order to avoid moving too close to the boundaries.
island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN));
// Island contours are holes of our external environment.
outer_holes.push_back(island.m_island.contour);
}
// Generate a box contour around everyting.
Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2);
assert(contour.size() == 1);
// make expolygon for outer environment
ExPolygons outer = diff_ex(contour, outer_holes);
assert(outer.size() == 1);
// If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion
// from Clipper data structure into the Slic3r expolygons inside diff_ex().
m_outer = MotionPlannerEnv(outer.front());
m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN)));
m_graphs.resize(m_islands.size() + 1);
m_initialized = true;
}
Polyline MotionPlanner::shortest_path(const Point &from, const Point &to)
{
// If we have an empty configuration space, return a straight move.
if (m_islands.empty())
return Polyline(from, to);
// Are both points in the same island?
int island_idx_from = -1;
int island_idx_to = -1;
int island_idx = -1;
for (MotionPlannerEnv &island : m_islands) {
int idx = &island - m_islands.data();
if (island.island_contains(from))
island_idx_from = idx;
if (island.island_contains(to))
island_idx_to = idx;
if (island_idx_from == idx && island_idx_to == idx) {
// Since both points are in the same island, is a direct move possible?
// If so, we avoid generating the visibility environment.
if (island.m_island.contains(Line(from, to)))
return Polyline(from, to);
// Both points are inside a single island, but the straight line crosses the island boundary.
island_idx = idx;
break;
}
}
// lazy generation of configuration space.
this->initialize();
// Get environment. If the from / to points do not share an island, then they cross an open space,
// therefore island_idx == -1 and env will be set to the environment of the empty space.
const MotionPlannerEnv &env = this->get_env(island_idx);
if (env.m_env.expolygons.empty()) {
// if this environment is empty (probably because it's too small), perform straight move
// and avoid running the algorithms on empty dataset
return Polyline(from, to);
}
// Now check whether points are inside the environment.
Point inner_from = from;
Point inner_to = to;
if (island_idx == -1) {
// The end points do not share the same island. In that case some of the travel
// will be likely performed inside the empty space.
// TODO: instead of using the nearest_env_point() logic, we should
// create a temporary graph where we connect 'from' and 'to' to the
// nodes which don't require more than one crossing, and let Dijkstra
// figure out the entire path - this should also replace the call to
// find_node() below
if (island_idx_from != -1)
// The start point is inside some island. Find the closest point at the empty space to start from.
inner_from = env.nearest_env_point(from, to);
if (island_idx_to != -1)
// The start point is inside some island. Find the closest point at the empty space to start from.
inner_to = env.nearest_env_point(to, inner_from);
}
// Perform a path search either in the open space, or in a common island of from/to.
const MotionPlannerGraph &graph = this->init_graph(island_idx);
// If no path exists without crossing perimeters, returns a straight segment.
Polyline polyline = graph.shortest_path(inner_from, inner_to);
polyline.points.insert(polyline.points.begin(), from);
polyline.points.emplace_back(to);
{
// grow our environment slightly in order for simplify_by_visibility()
// to work best by considering moves on boundaries valid as well
ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON)));
if (island_idx == -1) {
/* If 'from' or 'to' are not inside our env, they were connected using the
nearest_env_point() search which maybe produce ugly paths since it does not
include the endpoint in the Dijkstra search; the simplify_by_visibility()
call below will not work in many cases where the endpoint is not contained in
grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN,
which may not be enough for, say, including a skirt point). So we prune
the extra points manually. */
if (! grown_env.contains(from)) {
// delete second point while the line connecting first to third crosses the
// boundaries as many times as the current first to second
while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1)
polyline.points.erase(polyline.points.begin() + 1);
}
if (! grown_env.contains(to))
while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1)
polyline.points.erase(polyline.points.end() - 2);
}
// Perform some quick simplification (simplify_by_visibility() would make this
// unnecessary, but this is much faster)
polyline.simplify(MP_INNER_MARGIN/10);
// remove unnecessary vertices
// Note: this is computationally intensive and does not look very necessary
// now that we prune the endpoints with the logic above,
// so we comment it for now until a good test case arises
//polyline.simplify_by_visibility(grown_env);
/*
SVG svg("shortest_path.svg");
svg.draw(grown_env.expolygons);
svg.arrows = false;
for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) {
Point a = graph->nodes[it - graph->adjacency_list.begin()];
for (std::vector<MotionPlannerGraph::Neighbor>::const_iterator n = it->begin(); n != it->end(); ++n) {
Point b = graph->nodes[n->target];
svg.draw(Line(a, b));
}
}
svg.arrows = true;
svg.draw(from);
svg.draw(inner_from, "red");
svg.draw(to);
svg.draw(inner_to, "red");
svg.draw(polyline, "red");
svg.Close();
*/
}
return polyline;
}
const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx)
{
// 0th graph is the graph for m_outer. Other graphs are 1 indexed.
MotionPlannerGraph *graph = m_graphs[island_idx + 1].get();
if (graph == nullptr) {
// If this graph doesn't exist, initialize it.
m_graphs[island_idx + 1] = make_unique<MotionPlannerGraph>();
graph = m_graphs[island_idx + 1].get();
/* We don't add polygon boundaries as graph edges, because we'd need to connect
them to the Voronoi-generated edges by recognizing coinciding nodes. */
typedef voronoi_diagram<double> VD;
VD vd;
// Mapping between Voronoi vertices and graph nodes.
std::map<const VD::vertex_type*, size_t> vd_vertices;
// get boundaries as lines
const MotionPlannerEnv &env = this->get_env(island_idx);
Lines lines = env.m_env.lines();
boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd);
// traverse the Voronoi diagram and generate graph nodes and edges
for (const VD::edge_type &edge : vd.edges()) {
if (edge.is_infinite())
continue;
const VD::vertex_type* v0 = edge.vertex0();
const VD::vertex_type* v1 = edge.vertex1();
Point p0(v0->x(), v0->y());
Point p1(v1->x(), v1->y());
// Insert only Voronoi edges fully contained in the island.
//FIXME This test has a terrible O(n^2) time complexity.
if (env.island_contains_b(p0) && env.island_contains_b(p1)) {
// Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet.
auto i_v0 = vd_vertices.find(v0);
size_t v0_idx;
if (i_v0 == vd_vertices.end())
vd_vertices[v0] = v0_idx = graph->add_node(p0);
else
v0_idx = i_v0->second;
// Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet.
auto i_v1 = vd_vertices.find(v1);
size_t v1_idx;
if (i_v1 == vd_vertices.end())
vd_vertices[v1] = v1_idx = graph->add_node(p1);
else
v1_idx = i_v1->second;
// Euclidean distance is used as weight for the graph edge
graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast<double>().norm());
}
}
}
return *graph;
}
// Find a middle point on the path from start_point to end_point with the shortest path.
static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point)
{
size_t idx = size_t(-1);
double dmin = std::numeric_limits<double>::infinity();
for (const Point &p : middle_points) {
double d = (p - start_point).cast<double>().norm() + (end_point - p).cast<double>().norm();
if (d < dmin) {
idx = &p - middle_points.data();
dmin = d;
if (dmin < EPSILON)
break;
}
}
return idx;
}
Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const
{
/* In order to ensure that the move between 'from' and the initial env point does
not violate any of the configuration space boundaries, we limit our search to
the points that satisfy this condition. */
/* Assume that this method is never called when 'env' contains 'from';
so 'from' is either inside a hole or outside all contours */
// get the points of the hole containing 'from', if any
Points pp;
for (const ExPolygon &ex : m_env.expolygons) {
for (const Polygon &hole : ex.holes)
if (hole.contains(from))
pp = hole;
if (! pp.empty())
break;
}
// If 'from' is not inside a hole, it's outside of all contours, so take all contours' points.
if (pp.empty())
for (const ExPolygon &ex : m_env.expolygons)
append(pp, ex.contour.points);
// Find the candidate result and check that it doesn't cross too many boundaries.
while (pp.size() > 1) {
// find the point in pp that is closest to both 'from' and 'to'
size_t result = nearest_waypoint_index(from, pp, to);
// as we assume 'from' is outside env, any node will require at least one crossing
if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) {
// discard result
pp.erase(pp.begin() + result);
} else
return pp[result];
}
// if we're here, return last point if any (better than nothing)
// if we have no points at all, then we have an empty environment and we
// make this method behave as a no-op (we shouldn't get here by the way)
return pp.empty() ? from : pp.front();
}
// Add a new directed edge to the adjacency graph.
void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight)
{
// Extend adjacency list until this start node.
if (m_adjacency_list.size() < from + 1) {
// Reserve in powers of two to avoid repeated reallocation.
m_adjacency_list.reserve(std::max<uint32_t>(8, next_highest_power_of_2((uint32_t)(from + 1))));
// Allocate new empty adjacency vectors.
m_adjacency_list.resize(from + 1);
}
m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight));
}
// Dijkstra's shortest path in a weighted graph from node_start to node_end.
// The returned path contains the end points.
// If no path exists from node_start to node_end, a straight segment is returned.
Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const
{
// This prevents a crash in case for some reason we got here with an empty adjacency list.
if (this->empty())
return Polyline();
// Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start.
std::vector<node_t> previous(m_adjacency_list.size(), -1);
std::vector<weight_t> distance(m_adjacency_list.size(), std::numeric_limits<weight_t>::infinity());
std::vector<size_t> map_node_to_queue_id(m_adjacency_list.size(), size_t(-1));
distance[node_start] = 0.;
auto queue = make_mutable_priority_queue<node_t, false>(
[&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; },
[&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; });
queue.reserve(m_adjacency_list.size());
for (size_t i = 0; i < m_adjacency_list.size(); ++ i)
queue.push(node_t(i));
while (! queue.empty()) {
// Get the next node with the lowest distance to node_start.
node_t u = node_t(queue.top());
queue.pop();
map_node_to_queue_id[u] = size_t(-1);
// Stop searching if we reached our destination.
if (size_t(u) == node_end)
break;
// Visit each edge starting at node u.
for (const Neighbor& neighbor : m_adjacency_list[u])
if (map_node_to_queue_id[neighbor.target] != size_t(-1)) {
weight_t alt = distance[u] + neighbor.weight;
// If total distance through u is shorter than the previous
// distance (if any) between node_start and neighbor.target, replace it.
if (alt < distance[neighbor.target]) {
distance[neighbor.target] = alt;
previous[neighbor.target] = u;
queue.update(map_node_to_queue_id[neighbor.target]);
}
}
}
// In case the end point was not reached, previous[node_end] contains -1
// and a straight line from node_start to node_end is returned.
Polyline polyline;
polyline.points.reserve(m_adjacency_list.size());
for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex])
polyline.points.emplace_back(m_nodes[vertex]);
polyline.points.emplace_back(m_nodes[node_start]);
polyline.reverse();
return polyline;
}
}

View File

@ -1,91 +0,0 @@
#ifndef slic3r_MotionPlanner_hpp_
#define slic3r_MotionPlanner_hpp_
#include "libslic3r.h"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "ExPolygonCollection.hpp"
#include "Polyline.hpp"
#include <map>
#include <utility>
#include <memory>
#include <vector>
#define MP_INNER_MARGIN scale_(1.0)
#define MP_OUTER_MARGIN scale_(2.0)
namespace Slic3r {
class MotionPlanner;
class MotionPlannerEnv
{
friend class MotionPlanner;
public:
MotionPlannerEnv() {};
MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {};
Point nearest_env_point(const Point &from, const Point &to) const;
bool island_contains(const Point &pt) const
{ return m_island_bbox.contains(pt) && m_island.contains(pt); }
bool island_contains_b(const Point &pt) const
{ return m_island_bbox.contains(pt) && m_island.contains_b(pt); }
private:
ExPolygon m_island;
BoundingBox m_island_bbox;
// Region, where the travel is allowed.
ExPolygonCollection m_env;
};
// A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm.
class MotionPlannerGraph
{
public:
// Add a directed edge into the graph.
size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; }
void add_edge(size_t from, size_t to, double weight);
size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); }
bool empty() const { return m_adjacency_list.empty(); }
Polyline shortest_path(size_t from, size_t to) const;
Polyline shortest_path(const Point &from, const Point &to) const
{ return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); }
private:
typedef int node_t;
typedef double weight_t;
struct Neighbor {
Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {}
node_t target;
weight_t weight;
};
Points m_nodes;
std::vector<std::vector<Neighbor>> m_adjacency_list;
};
class MotionPlanner
{
public:
MotionPlanner(const ExPolygons &islands);
~MotionPlanner() {}
Polyline shortest_path(const Point &from, const Point &to);
size_t islands_count() const { return m_islands.size(); }
private:
bool m_initialized;
std::vector<MotionPlannerEnv> m_islands;
MotionPlannerEnv m_outer;
// 0th graph is the graph for m_outer. Other graphs are 1 indexed.
std::vector<std::unique_ptr<MotionPlannerGraph>> m_graphs;
void initialize();
const MotionPlannerGraph& init_graph(int island_idx);
const MotionPlannerEnv& get_env(int island_idx) const
{ return (island_idx == -1) ? m_outer : m_islands[island_idx]; }
};
}
#endif

View File

@ -15,8 +15,6 @@ REGISTER_CLASS(Filler, "Filler");
REGISTER_CLASS(Flow, "Flow");
REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer");
REGISTER_CLASS(GCode, "GCode");
//REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData");
// REGISTER_CLASS(GCodeSender, "GCode::Sender");
REGISTER_CLASS(Layer, "Layer");
REGISTER_CLASS(SupportLayer, "Layer::Support");
REGISTER_CLASS(LayerRegion, "Layer::Region");
@ -35,7 +33,6 @@ REGISTER_CLASS(ModelMaterial, "Model::Material");
REGISTER_CLASS(ModelObject, "Model::Object");
REGISTER_CLASS(ModelVolume, "Model::Volume");
REGISTER_CLASS(ModelInstance, "Model::Instance");
REGISTER_CLASS(MotionPlanner, "MotionPlanner");
REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox");
REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf");
REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3");

View File

@ -1,92 +0,0 @@
#!/usr/bin/perl
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../../lib";
}
use Slic3r::XS;
use Test::More tests => 20;
my $square = Slic3r::Polygon->new( # ccw
[100, 100],
[200, 100],
[200, 200],
[100, 200],
);
my $hole_in_square = Slic3r::Polygon->new( # cw
[140, 140],
[140, 160],
[160, 160],
[160, 140],
);
$_->scale(1/0.000001) for $square, $hole_in_square;
my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square);
{
my $mp = Slic3r::MotionPlanner->new([ $expolygon ]);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(120, 120);
my $to = Slic3r::Point->new(180,180);
$_->scale(1/0.000001) for $from, $to;
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line';
ok $path->first_point->coincides_with($from), 'first path point coincides with initial point';
ok $path->last_point->coincides_with($to), 'last path point coincides with destination point';
ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon';
}
{
my $mp = Slic3r::MotionPlanner->new([ $expolygon ]);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(80, 100);
my $to = Slic3r::Point->new(220,200);
$_->scale(1/0.000001) for $from, $to;
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line';
ok $path->first_point->coincides_with($from), 'first path point coincides with initial point';
ok $path->last_point->coincides_with($to), 'last path point coincides with destination point';
is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon';
}
{
my $expolygon2 = $expolygon->clone;
$expolygon2->translate(300/0.000001, 0);
my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(120, 120);
my $to = Slic3r::Point->new(120 + 300, 120);
$_->scale(1/0.000001) for $from, $to;
ok $expolygon->contains_point($from), 'start point is contained in first expolygon';
ok $expolygon2->contains_point($to), 'end point is contained in second expolygon';
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
}
{
my $expolygons = [
Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]),
Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]),
];
my $mp = Slic3r::MotionPlanner->new($expolygons);
isa_ok $mp, 'Slic3r::MotionPlanner';
my $from = Slic3r::Point->new(79120520, 107839491);
my $to = Slic3r::Point->new(104664164, 108335852);
ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon';
ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon';
my $path = $mp->shortest_path($from, $to);
ok $path->is_valid(), 'return path is valid';
}
__END__

View File

@ -1,15 +0,0 @@
%module{Slic3r::XS};
%{
#include <xsinit.h>
#include "libslic3r/MotionPlanner.hpp"
%}
%name{Slic3r::MotionPlanner} class MotionPlanner {
MotionPlanner(ExPolygons islands);
~MotionPlanner();
int islands_count();
Clone<Polyline> shortest_path(Point* from, Point* to)
%code%{ RETVAL = THIS->shortest_path(*from, *to); %};
};

View File

@ -191,18 +191,6 @@ GCode* O_OBJECT_SLIC3R
Ref<GCode> O_OBJECT_SLIC3R_T
Clone<GCode> O_OBJECT_SLIC3R_T
//GCodePreviewData* O_OBJECT_SLIC3R
//Ref<GCodePreviewData> O_OBJECT_SLIC3R_T
//Clone<GCodePreviewData> O_OBJECT_SLIC3R_T
MotionPlanner* O_OBJECT_SLIC3R
Ref<MotionPlanner> O_OBJECT_SLIC3R_T
Clone<MotionPlanner> O_OBJECT_SLIC3R_T
// GCodeSender* O_OBJECT_SLIC3R
// Ref<GCodeSender> O_OBJECT_SLIC3R_T
// Clone<GCodeSender> O_OBJECT_SLIC3R_T
BridgeDetector* O_OBJECT_SLIC3R
Ref<BridgeDetector> O_OBJECT_SLIC3R_T
Clone<BridgeDetector> O_OBJECT_SLIC3R_T

View File

@ -97,12 +97,6 @@
%typemap{PolylineCollection*};
%typemap{Ref<PolylineCollection>}{simple};
%typemap{Clone<PolylineCollection>}{simple};
%typemap{MotionPlanner*};
%typemap{Ref<MotionPlanner>}{simple};
%typemap{Clone<MotionPlanner>}{simple};
// %typemap{GCodeSender*};
// %typemap{Ref<GCodeSender>}{simple};
// %typemap{Clone<GCodeSender>}{simple};
%typemap{BridgeDetector*};
%typemap{Ref<BridgeDetector>}{simple};
%typemap{Clone<BridgeDetector>}{simple};