Merge branch 'lh_avoid_crossing_perimeters'

This commit is contained in:
Vojtech Bubnik 2020-12-08 13:39:33 +01:00
commit 5f456ebbe7
26 changed files with 1490 additions and 760 deletions

View File

@ -99,6 +99,8 @@ add_library(libslic3r STATIC
GCode/WipeTower.hpp
GCode/GCodeProcessor.cpp
GCode/GCodeProcessor.hpp
GCode/AvoidCrossingPerimeters.cpp
GCode/AvoidCrossingPerimeters.hpp
GCode.cpp
GCode.hpp
GCodeReader.cpp
@ -126,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

@ -259,7 +259,14 @@ public:
{
const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first];
size_t ipt = contour_and_segment_idx.second;
return std::pair<const Slic3r::Point&, const Slic3r::Point&>(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]);
return std::pair<const Slic3r::Point&, const Slic3r::Point&>(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]);
}
Line line(const std::pair<size_t, size_t> &contour_and_segment_idx) const
{
const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first];
size_t ipt = contour_and_segment_idx.second;
return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]);
}
protected:

View File

@ -254,11 +254,12 @@ std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t id
grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {}
void init(const Points &contour, const Point &apoint) {
this->idx_point = &apoint - contour.data();
this->point = apoint;
this->found = false;
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
}
this->idx_point = &apoint - contour.data();
this->point = apoint;
this->found = false;
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
this->distance = std::numeric_limits<double>::max();
}
bool operator()(coord_t iy, coord_t ix) {
// Called with a row and colum of the grid cell, which is intersected by a line.

View File

@ -42,11 +42,11 @@ void ExPolygon::scale(double factor)
hole.scale(factor);
}
void ExPolygon::translate(double x, double y)
void ExPolygon::translate(const Point &p)
{
contour.translate(x, y);
contour.translate(p);
for (Polygon &hole : holes)
hole.translate(x, y);
hole.translate(p);
}
void ExPolygon::rotate(double angle)

View File

@ -42,7 +42,8 @@ public:
operator Polylines() const;
void clear() { contour.points.clear(); holes.clear(); }
void scale(double factor);
void translate(double x, double y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
void translate(const Point &vector);
void rotate(double angle);
void rotate(double angle, const Point &center);
double area() const;

View File

@ -10,6 +10,7 @@
#include "ShortestPath.hpp"
#include "Print.hpp"
#include "Utils.hpp"
#include "ClipperUtils.hpp"
#include "libslic3r.h"
#include <algorithm>
@ -89,94 +90,6 @@ namespace Slic3r {
return ok;
}
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;
}
std::string OozePrevention::pre_toolchange(GCode& gcodegen)
{
std::string gcode;
@ -322,7 +235,7 @@ namespace Slic3r {
// Move over the wipe tower.
// Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos),
erMixed,
@ -419,7 +332,7 @@ namespace Slic3r {
}
// Let the planner know we are traveling between objects.
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true;
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
return gcode;
}
@ -1235,13 +1148,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();
@ -1277,12 +1183,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Move to the origin position for the copy we're going to print.
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters.use_external_mp_once = true;
m_avoid_crossing_perimeters.use_external_mp_once();
_write(file, this->retract());
_write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
m_enable_cooling_markers = true;
// Disable motion planner when traveling to first object point.
m_avoid_crossing_perimeters.disable_once = true;
m_avoid_crossing_perimeters.disable_once();
// Ff we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. This happens before the Z move
// is triggered, so machine has more time to reach such temperatures.
@ -2108,7 +2014,7 @@ void GCode::process_layer(
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
const std::pair<size_t, size_t> loops = loops_it->second;
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true;
m_avoid_crossing_perimeters.use_external_mp();
Flow layer_skirt_flow(print.skirt_flow());
layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
@ -2122,23 +2028,23 @@ void GCode::process_layer(
//FIXME using the support_material_speed of the 1st object printed.
gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
}
m_avoid_crossing_perimeters.use_external_mp = false;
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
if (first_layer && loops.first == 0)
m_avoid_crossing_perimeters.disable_once = true;
m_avoid_crossing_perimeters.disable_once();
}
// Extrude brim with the extruder of the 1st region.
if (! m_brim_done) {
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true;
m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity *ee : print.brim().entities) {
gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value);
}
m_brim_done = true;
m_avoid_crossing_perimeters.use_external_mp = false;
m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters.disable_once = true;
m_avoid_crossing_perimeters.disable_once();
}
@ -2158,15 +2064,14 @@ void GCode::process_layer(
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));
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.
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once = true;
m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
@ -2758,43 +2663,51 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
/* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by
this->origin in order to get G-code coordinates. */
Polyline travel;
travel.append(this->last_pos());
travel.append(point);
Polyline travel { this->last_pos(), point };
// check whether a straight travel move would need retraction
bool needs_retraction = this->needs_retraction(travel, role);
bool needs_retraction = this->needs_retraction(travel, role);
// check whether wipe could be disabled without causing visible stringing
bool could_be_wipe_disabled = false;
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space
if (needs_retraction
&& m_config.avoid_crossing_perimeters
&& ! m_avoid_crossing_perimeters.disable_once) {
travel = m_avoid_crossing_perimeters.travel_to(*this, point);
&& ! m_avoid_crossing_perimeters.disabled_once()) {
travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled);
// check again whether the new travel path still needs a retraction
needs_retraction = this->needs_retraction(travel, role);
//if (needs_retraction && m_layer_index > 1) exit(0);
}
// Re-allow avoid_crossing_perimeters for the next travel moves
m_avoid_crossing_perimeters.disable_once = false;
m_avoid_crossing_perimeters.use_external_mp_once = false;
m_avoid_crossing_perimeters.reset_once_modifiers();
// generate G-code for the travel move
std::string gcode;
if (needs_retraction)
if (needs_retraction) {
if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
m_wipe.reset_path();
Point last_post_before_retract = this->last_pos();
gcode += this->retract();
else
// When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
// Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction()
if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) {
Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract);
append(retract_travel.points, travel.points);
travel = std::move(retract_travel);
}
} else
// Reset the wipe path when traveling, so one would not wipe along an old path.
m_wipe.reset_path();
// use G1 because we rely on paths being straight (G0 may make round paths)
Lines lines = travel.lines();
if (! lines.empty()) {
for (const Line &line : lines)
gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment);
this->set_last_pos(lines.back().b);
if (travel.size() >= 2) {
for (size_t i = 1; i < travel.size(); ++ i)
gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
this->set_last_pos(travel.points.back());
}
return gcode;
}

View File

@ -5,10 +5,10 @@
#include "ExPolygon.hpp"
#include "GCodeWriter.hpp"
#include "Layer.hpp"
#include "MotionPlanner.hpp"
#include "Point.hpp"
#include "PlaceholderParser.hpp"
#include "PrintConfig.hpp"
#include "GCode/AvoidCrossingPerimeters.hpp"
#include "GCode/CoolingBuffer.hpp"
#include "GCode/SpiralVase.hpp"
#include "GCode/ToolOrdering.hpp"
@ -35,35 +35,6 @@ namespace { struct Item; }
struct PrintInstance;
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) {}
~AvoidCrossingPerimeters() {}
void reset() { m_external_mp.reset(); m_layer_mp.reset(); }
void init_external_mp(const Print &print);
void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique<MotionPlanner>(islands); }
Polyline travel_to(const GCode &gcodegen, const Point &point);
private:
// 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 OozePrevention {
public:
bool enable;
@ -185,6 +156,7 @@ public:
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }
GCodeWriter& writer() { return m_writer; }
const GCodeWriter& writer() const { return m_writer; }
PlaceholderParser& placeholder_parser() { return m_placeholder_parser; }
const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; }
// Process a template through the placeholder parser, collect error messages to be reported

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
#ifndef slic3r_AvoidCrossingPerimeters_hpp_
#define slic3r_AvoidCrossingPerimeters_hpp_
#include "../libslic3r.h"
#include "../ExPolygon.hpp"
#include "../EdgeGrid.hpp"
namespace Slic3r {
// Forward declarations.
class GCode;
class Layer;
class Point;
class AvoidCrossingPerimeters
{
public:
// Routing around the objects vs. inside a single object.
void use_external_mp(bool use = true) { m_use_external_mp = use; };
void use_external_mp_once() { m_use_external_mp_once = true; }
void disable_once() { m_disabled_once = true; }
bool disabled_once() const { return m_disabled_once; }
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
void init_layer(const Layer &layer);
Polyline travel_to(const GCode& gcodegen, const Point& point)
{
bool could_be_wipe_disabled;
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
}
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
struct Boundary {
// Collection of boundaries used for detection of crossing perimeters for travels
Polygons boundaries;
// Bounding box of boundaries
BoundingBoxf bbox;
// Precomputed distances of all points in boundaries
std::vector<std::vector<float>> boundaries_params;
// Used for detection of intersection between line and any polygon from boundaries
EdgeGrid::Grid grid;
void clear()
{
boundaries.clear();
boundaries_params.clear();
}
};
private:
bool m_use_external_mp { false };
// just for the next travel move
bool m_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 m_disabled_once { true };
// Used for detection of line or polyline is inside of any polygon.
EdgeGrid::Grid m_grid_lslice;
// Store all needed data for travels inside object
Boundary m_internal;
// Store all needed data for travels outside object
Boundary m_external;
};
} // 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.points;
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

@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y)
}
}
void MultiPoint::translate(double x, double y)
{
Vector v(x, y);
for (Point &pt : points)
pt += v;
}
void MultiPoint::translate(const Point &v)
{
for (Point &pt : points)
@ -140,6 +133,17 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const
return found;
}
bool MultiPoint::intersections(const Line &line, Points *intersections) const
{
size_t intersections_size = intersections->size();
for (const Line &polygon_line : this->lines()) {
Point intersection;
if (polygon_line.intersection(line, &intersection))
intersections->emplace_back(std::move(intersection));
}
return intersections->size() > intersections_size;
}
std::vector<Point> MultiPoint::_douglas_peucker(const std::vector<Point>& pts, const double tolerance)
{
std::vector<Point> result_pts;

View File

@ -26,7 +26,7 @@ public:
MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
void scale(double factor);
void scale(double factor_x, double factor_y);
void translate(double x, double y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
void translate(const Point &vector);
void rotate(double angle) { this->rotate(cos(angle), sin(angle)); }
void rotate(double cos_angle, double sin_angle);
@ -79,7 +79,8 @@ public:
bool intersection(const Line& line, Point* intersection) const;
bool first_intersection(const Line& line, Point* intersection) const;
bool intersections(const Line &line, Points *intersections) const;
static Points _douglas_peucker(const Points &points, const double tolerance);
static Points visivalingam(const Points& pts, const double& tolerance);
};

View File

@ -410,7 +410,7 @@ const std::vector<std::string>& Preset::print_options()
"infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle",
"solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first",
"ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing",
"max_print_speed", "max_volumetric_speed",
"max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour",
#ifdef HAS_PRESSURE_EQUALIZER
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
#endif /* HAS_PRESSURE_EQUALIZER */

View File

@ -71,6 +71,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
// or they are only notes not influencing the generated G-code.
static std::unordered_set<std::string> steps_gcode = {
"avoid_crossing_perimeters",
"avoid_crossing_perimeters_max_detour",
"bed_shape",
"bed_temperature",
"before_layer_gcode",

View File

@ -184,6 +184,16 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("avoid_crossing_perimeters_max_detour", coFloat);
def->label = L("Avoid crossing perimeters - The max detour lenght");
def->category = L("Layers and Perimeters");
def->tooltip = L("The maximum detour length for avoid crossing perimeters. "
"If the detour is longer than this value, avoid crossing perimeters is not applied for this path.");
def->sidetext = L("mm (zero to disable)");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.));
def = this->add("bed_temperature", coInts);
def->label = L("Other layers");
def->tooltip = L("Bed temperature for layers after the first one. "

View File

@ -828,6 +828,7 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig
public:
ConfigOptionBool avoid_crossing_perimeters;
ConfigOptionFloat avoid_crossing_perimeters_max_detour;
ConfigOptionPoints bed_shape;
ConfigOptionInts bed_temperature;
ConfigOptionFloat bridge_acceleration;
@ -902,6 +903,7 @@ protected:
this->MachineEnvelopeConfig::initialize(cache, base_ptr);
this->GCodeConfig::initialize(cache, base_ptr);
OPT_PTR(avoid_crossing_perimeters);
OPT_PTR(avoid_crossing_perimeters_max_detour);
OPT_PTR(bed_shape);
OPT_PTR(bed_temperature);
OPT_PTR(bridge_acceleration);

View File

@ -184,7 +184,7 @@ inline std::unique_ptr<T> make_unique(Args&&... args) {
// Variant of std::lower_bound() with compare predicate, but without the key.
// This variant is very useful in case that the T type is large or it does not even have a public constructor.
template<class ForwardIt, class LowerThanKeyPredicate>
ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key)
ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_than_key)
{
ForwardIt it;
typename std::iterator_traits<ForwardIt>::difference_type count, step;
@ -194,7 +194,7 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey
it = first;
step = count / 2;
std::advance(it, step);
if (lower_thank_key(*it)) {
if (lower_than_key(*it)) {
first = ++it;
count -= step + 1;
}

View File

@ -307,6 +307,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
"wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
toggle_field(el, have_wipe_tower);
bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters");
toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters);
}
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)

View File

@ -542,7 +542,7 @@ void Tab::decorate()
wxColour* colored_label_clr = nullptr;
if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" ||
opt.first == "compatible_prints" || opt.first == "compatible_printers")
opt.first == "compatible_prints" || opt.first == "compatible_printers")
colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : m_colored_Label_colors.at(opt.first);
if (!colored_label_clr) {
@ -1424,6 +1424,7 @@ void TabPrint::build()
optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed");
optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness");
optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters");
optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour");
optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls");
optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters");

View File

@ -62,7 +62,6 @@ set(XS_XSP_FILES
${XSP_DIR}/Layer.xsp
${XSP_DIR}/Line.xsp
${XSP_DIR}/Model.xsp
${XSP_DIR}/MotionPlanner.xsp
${XSP_DIR}/PerimeterGenerator.xsp
${XSP_DIR}/PlaceholderParser.xsp
${XSP_DIR}/Point.xsp

View File

@ -32,7 +32,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};