mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-11 00:37:51 -06:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_3dconnexion
This commit is contained in:
commit
4b3eab0ed9
53 changed files with 1055 additions and 365 deletions
|
@ -89,33 +89,34 @@ struct stl_neighbors {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stl_stats {
|
struct stl_stats {
|
||||||
stl_stats() { this->reset(); }
|
stl_stats() { memset(&header, 0, 81); }
|
||||||
void reset() { memset(this, 0, sizeof(stl_stats)); this->volume = -1.0; }
|
|
||||||
char header[81];
|
char header[81];
|
||||||
stl_type type;
|
stl_type type = (stl_type)0;
|
||||||
uint32_t number_of_facets;
|
uint32_t number_of_facets = 0;
|
||||||
stl_vertex max;
|
stl_vertex max = stl_vertex::Zero();
|
||||||
stl_vertex min;
|
stl_vertex min = stl_vertex::Zero();
|
||||||
stl_vertex size;
|
stl_vertex size = stl_vertex::Zero();
|
||||||
float bounding_diameter;
|
float bounding_diameter = 0.f;
|
||||||
float shortest_edge;
|
float shortest_edge = 0.f;
|
||||||
float volume;
|
float volume = -1.f;
|
||||||
int connected_edges;
|
int connected_edges = 0;
|
||||||
int connected_facets_1_edge;
|
int connected_facets_1_edge = 0;
|
||||||
int connected_facets_2_edge;
|
int connected_facets_2_edge = 0;
|
||||||
int connected_facets_3_edge;
|
int connected_facets_3_edge = 0;
|
||||||
int facets_w_1_bad_edge;
|
int facets_w_1_bad_edge = 0;
|
||||||
int facets_w_2_bad_edge;
|
int facets_w_2_bad_edge = 0;
|
||||||
int facets_w_3_bad_edge;
|
int facets_w_3_bad_edge = 0;
|
||||||
int original_num_facets;
|
int original_num_facets = 0;
|
||||||
int edges_fixed;
|
int edges_fixed = 0;
|
||||||
int degenerate_facets;
|
int degenerate_facets = 0;
|
||||||
int facets_removed;
|
int facets_removed = 0;
|
||||||
int facets_added;
|
int facets_added = 0;
|
||||||
int facets_reversed;
|
int facets_reversed = 0;
|
||||||
int backwards_edges;
|
int backwards_edges = 0;
|
||||||
int normals_fixed;
|
int normals_fixed = 0;
|
||||||
int number_of_parts;
|
int number_of_parts = 0;
|
||||||
|
|
||||||
|
void clear() { *this = stl_stats(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stl_file {
|
struct stl_file {
|
||||||
|
@ -124,7 +125,7 @@ struct stl_file {
|
||||||
void clear() {
|
void clear() {
|
||||||
this->facet_start.clear();
|
this->facet_start.clear();
|
||||||
this->neighbors_start.clear();
|
this->neighbors_start.clear();
|
||||||
this->stats.reset();
|
this->stats.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t memsize() const {
|
size_t memsize() const {
|
||||||
|
|
|
@ -15,4 +15,4 @@
|
||||||
#undef clipper_hpp
|
#undef clipper_hpp
|
||||||
#undef use_xyz
|
#undef use_xyz
|
||||||
|
|
||||||
#endif clipper_z_hpp
|
#endif // clipper_z_hpp
|
||||||
|
|
|
@ -100,7 +100,7 @@ add_library(libslic3r STATIC
|
||||||
Geometry.cpp
|
Geometry.cpp
|
||||||
Geometry.hpp
|
Geometry.hpp
|
||||||
Int128.hpp
|
Int128.hpp
|
||||||
# KdTree.hpp
|
KDTreeIndirect.hpp
|
||||||
Layer.cpp
|
Layer.cpp
|
||||||
Layer.hpp
|
Layer.hpp
|
||||||
LayerRegion.cpp
|
LayerRegion.cpp
|
||||||
|
@ -142,6 +142,8 @@ add_library(libslic3r STATIC
|
||||||
PrintObject.cpp
|
PrintObject.cpp
|
||||||
PrintRegion.cpp
|
PrintRegion.cpp
|
||||||
Semver.cpp
|
Semver.cpp
|
||||||
|
ShortestPath.cpp
|
||||||
|
ShortestPath.hpp
|
||||||
SLAPrint.cpp
|
SLAPrint.cpp
|
||||||
SLAPrint.hpp
|
SLAPrint.hpp
|
||||||
SLA/SLAAutoSupports.hpp
|
SLA/SLAAutoSupports.hpp
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
|
#include "ShortestPath.hpp"
|
||||||
|
|
||||||
// #define CLIPPER_UTILS_DEBUG
|
// #define CLIPPER_UTILS_DEBUG
|
||||||
|
|
||||||
|
@ -671,21 +672,19 @@ void traverse_pt(ClipperLib::PolyNodes &nodes, Polygons* retval)
|
||||||
// collect ordering points
|
// collect ordering points
|
||||||
Points ordering_points;
|
Points ordering_points;
|
||||||
ordering_points.reserve(nodes.size());
|
ordering_points.reserve(nodes.size());
|
||||||
for (ClipperLib::PolyNodes::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
|
for (ClipperLib::PolyNode *pn : nodes)
|
||||||
Point p((*it)->Contour.front().X, (*it)->Contour.front().Y);
|
ordering_points.emplace_back(Point(pn->Contour.front().X, pn->Contour.front().Y));
|
||||||
ordering_points.emplace_back(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// perform the ordering
|
// perform the ordering
|
||||||
ClipperLib::PolyNodes ordered_nodes;
|
ClipperLib::PolyNodes ordered_nodes = chain_clipper_polynodes(ordering_points, nodes);
|
||||||
Slic3r::Geometry::chained_path_items(ordering_points, nodes, ordered_nodes);
|
|
||||||
|
|
||||||
// push results recursively
|
// push results recursively
|
||||||
for (ClipperLib::PolyNodes::iterator it = ordered_nodes.begin(); it != ordered_nodes.end(); ++it) {
|
for (ClipperLib::PolyNode *pn : ordered_nodes) {
|
||||||
// traverse the next depth
|
// traverse the next depth
|
||||||
traverse_pt((*it)->Childs, retval);
|
traverse_pt(pn->Childs, retval);
|
||||||
retval->emplace_back(ClipperPath_to_Slic3rPolygon((*it)->Contour));
|
retval->emplace_back(ClipperPath_to_Slic3rPolygon(pn->Contour));
|
||||||
if ((*it)->IsHole()) retval->back().reverse(); // ccw
|
if (pn->IsHole())
|
||||||
|
retval->back().reverse(); // ccw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "Polygon.hpp"
|
#include "Polygon.hpp"
|
||||||
#include "Polyline.hpp"
|
#include "Polyline.hpp"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class ExPolygonCollection;
|
class ExPolygonCollection;
|
||||||
|
|
|
@ -16,7 +16,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE
|
||||||
this->entities = other.entities;
|
this->entities = other.entities;
|
||||||
for (size_t i = 0; i < this->entities.size(); ++i)
|
for (size_t i = 0; i < this->entities.size(); ++i)
|
||||||
this->entities[i] = this->entities[i]->clone();
|
this->entities[i] = this->entities[i]->clone();
|
||||||
this->orig_indices = other.orig_indices;
|
|
||||||
this->no_sort = other.no_sort;
|
this->no_sort = other.no_sort;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +23,6 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator=(const ExtrusionE
|
||||||
void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
|
void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
|
||||||
{
|
{
|
||||||
std::swap(this->entities, c.entities);
|
std::swap(this->entities, c.entities);
|
||||||
std::swap(this->orig_indices, c.orig_indices);
|
|
||||||
std::swap(this->no_sort, c.no_sort);
|
std::swap(this->no_sort, c.no_sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,10 +80,10 @@ ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_revers
|
||||||
return coll;
|
return coll;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
|
void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role) const
|
||||||
{
|
{
|
||||||
if (this->entities.empty()) return;
|
if (this->entities.empty()) return;
|
||||||
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices);
|
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 ExtrusionEntityCollection::chained_path_from(Point start_near, bool no_reverse, ExtrusionRole role) const
|
||||||
|
@ -95,7 +93,7 @@ ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(Point sta
|
||||||
return coll;
|
return coll;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
|
void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role) const
|
||||||
{
|
{
|
||||||
if (this->no_sort) {
|
if (this->no_sort) {
|
||||||
*retval = *this;
|
*retval = *this;
|
||||||
|
@ -103,7 +101,6 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
|
||||||
}
|
}
|
||||||
|
|
||||||
retval->entities.reserve(this->entities.size());
|
retval->entities.reserve(this->entities.size());
|
||||||
retval->orig_indices.reserve(this->entities.size());
|
|
||||||
|
|
||||||
// if we're asked to return the original indices, build a map
|
// if we're asked to return the original indices, build a map
|
||||||
std::map<ExtrusionEntity*,size_t> indices_map;
|
std::map<ExtrusionEntity*,size_t> indices_map;
|
||||||
|
@ -122,8 +119,8 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
|
||||||
|
|
||||||
ExtrusionEntity *entity = entity_src->clone();
|
ExtrusionEntity *entity = entity_src->clone();
|
||||||
my_paths.push_back(entity);
|
my_paths.push_back(entity);
|
||||||
if (orig_indices != nullptr)
|
// if (orig_indices != nullptr)
|
||||||
indices_map[entity] = &entity_src - &this->entities.front();
|
// indices_map[entity] = &entity_src - &this->entities.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
Points endpoints;
|
Points endpoints;
|
||||||
|
@ -142,8 +139,8 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
|
||||||
if (start_index % 2 && !no_reverse && entity->can_reverse())
|
if (start_index % 2 && !no_reverse && entity->can_reverse())
|
||||||
entity->reverse();
|
entity->reverse();
|
||||||
retval->entities.push_back(my_paths.at(path_index));
|
retval->entities.push_back(my_paths.at(path_index));
|
||||||
if (orig_indices != nullptr)
|
// if (orig_indices != nullptr)
|
||||||
orig_indices->push_back(indices_map[entity]);
|
// orig_indices->push_back(indices_map[entity]);
|
||||||
my_paths.erase(my_paths.begin() + path_index);
|
my_paths.erase(my_paths.begin() + path_index);
|
||||||
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
|
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
|
||||||
start_near = retval->entities.back()->last_point();
|
start_near = retval->entities.back()->last_point();
|
||||||
|
|
|
@ -14,15 +14,14 @@ public:
|
||||||
ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
|
ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
|
||||||
|
|
||||||
ExtrusionEntitiesPtr entities; // we own these entities
|
ExtrusionEntitiesPtr entities; // we own these entities
|
||||||
std::vector<size_t> orig_indices; // handy for XS
|
|
||||||
bool no_sort;
|
bool no_sort;
|
||||||
ExtrusionEntityCollection(): no_sort(false) {};
|
ExtrusionEntityCollection(): no_sort(false) {};
|
||||||
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : orig_indices(other.orig_indices), no_sort(other.no_sort) { this->append(other.entities); }
|
ExtrusionEntityCollection(const ExtrusionEntityCollection &other) : no_sort(other.no_sort) { this->append(other.entities); }
|
||||||
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), orig_indices(std::move(other.orig_indices)), no_sort(other.no_sort) {}
|
ExtrusionEntityCollection(ExtrusionEntityCollection &&other) : entities(std::move(other.entities)), no_sort(other.no_sort) {}
|
||||||
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
|
explicit ExtrusionEntityCollection(const ExtrusionPaths &paths);
|
||||||
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
|
ExtrusionEntityCollection& operator=(const ExtrusionEntityCollection &other);
|
||||||
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
|
ExtrusionEntityCollection& operator=(ExtrusionEntityCollection &&other)
|
||||||
{ this->entities = std::move(other.entities); this->orig_indices = std::move(other.orig_indices); this->no_sort = other.no_sort; return *this; }
|
{ this->entities = std::move(other.entities); this->no_sort = other.no_sort; return *this; }
|
||||||
~ExtrusionEntityCollection() { clear(); }
|
~ExtrusionEntityCollection() { clear(); }
|
||||||
explicit operator ExtrusionPaths() const;
|
explicit operator ExtrusionPaths() const;
|
||||||
|
|
||||||
|
@ -67,9 +66,9 @@ public:
|
||||||
void replace(size_t i, const ExtrusionEntity &entity);
|
void replace(size_t i, const ExtrusionEntity &entity);
|
||||||
void remove(size_t i);
|
void remove(size_t i);
|
||||||
ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const;
|
ExtrusionEntityCollection chained_path(bool no_reverse = false, ExtrusionRole role = erMixed) const;
|
||||||
void chained_path(ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed, std::vector<size_t>* orig_indices = nullptr) 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;
|
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, std::vector<size_t>* orig_indices = nullptr) const;
|
void chained_path_from(Point start_near, ExtrusionEntityCollection* retval, bool no_reverse = false, ExtrusionRole role = erMixed) const;
|
||||||
void reverse();
|
void reverse();
|
||||||
Point first_point() const { return this->entities.front()->first_point(); }
|
Point first_point() const { return this->entities.front()->first_point(); }
|
||||||
Point last_point() const { return this->entities.back()->last_point(); }
|
Point last_point() const { return this->entities.back()->last_point(); }
|
||||||
|
|
|
@ -15,40 +15,39 @@ namespace Slic3r {
|
||||||
|
|
||||||
struct SurfaceFillParams
|
struct SurfaceFillParams
|
||||||
{
|
{
|
||||||
SurfaceFillParams() : flow(0.f, 0.f, 0.f, false) { memset(this, 0, sizeof(*this)); }
|
|
||||||
// Zero based extruder ID.
|
// Zero based extruder ID.
|
||||||
unsigned int extruder;
|
unsigned int extruder = 0;
|
||||||
// Infill pattern, adjusted for the density etc.
|
// Infill pattern, adjusted for the density etc.
|
||||||
InfillPattern pattern;
|
InfillPattern pattern = InfillPattern(0);
|
||||||
|
|
||||||
// FillBase
|
// FillBase
|
||||||
// in unscaled coordinates
|
// in unscaled coordinates
|
||||||
coordf_t spacing;
|
coordf_t spacing = 0.;
|
||||||
// infill / perimeter overlap, in unscaled coordinates
|
// infill / perimeter overlap, in unscaled coordinates
|
||||||
coordf_t overlap;
|
coordf_t overlap = 0.;
|
||||||
// Angle as provided by the region config, in radians.
|
// Angle as provided by the region config, in radians.
|
||||||
float angle;
|
float angle = 0.f;
|
||||||
// Non-negative for a bridge.
|
// Non-negative for a bridge.
|
||||||
float bridge_angle;
|
float bridge_angle = 0.f;
|
||||||
|
|
||||||
// FillParams
|
// FillParams
|
||||||
float density;
|
float density = 0.f;
|
||||||
// Don't connect the fill lines around the inner perimeter.
|
// Don't connect the fill lines around the inner perimeter.
|
||||||
bool dont_connect;
|
bool dont_connect = false;
|
||||||
// Don't adjust spacing to fill the space evenly.
|
// Don't adjust spacing to fill the space evenly.
|
||||||
bool dont_adjust;
|
bool dont_adjust = false;
|
||||||
|
|
||||||
// width, height of extrusion, nozzle diameter, is bridge
|
// width, height of extrusion, nozzle diameter, is bridge
|
||||||
// For the output, for fill generator.
|
// For the output, for fill generator.
|
||||||
Flow flow;
|
Flow flow = Flow(0.f, 0.f, 0.f, false);
|
||||||
|
|
||||||
// For the output
|
// For the output
|
||||||
ExtrusionRole extrusion_role;
|
ExtrusionRole extrusion_role = ExtrusionRole(0);
|
||||||
|
|
||||||
// Various print settings?
|
// Various print settings?
|
||||||
|
|
||||||
// Index of this entry in a linear vector.
|
// Index of this entry in a linear vector.
|
||||||
size_t idx;
|
size_t idx = 0;
|
||||||
|
|
||||||
|
|
||||||
bool operator<(const SurfaceFillParams &rhs) const {
|
bool operator<(const SurfaceFillParams &rhs) const {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "../ClipperUtils.hpp"
|
#include "../ClipperUtils.hpp"
|
||||||
#include "../PolylineCollection.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
|
|
||||||
#include "Fill3DHoneycomb.hpp"
|
#include "Fill3DHoneycomb.hpp"
|
||||||
|
@ -175,27 +175,24 @@ void Fill3DHoneycomb::_fill_surface_single(
|
||||||
std::swap(expolygon_off, expolygons_off.front());
|
std::swap(expolygon_off, expolygons_off.front());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Polylines chained = PolylineCollection::chained_path_from(
|
|
||||||
std::move(polylines),
|
|
||||||
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
|
for (Polyline &polyline : chain_infill_polylines(std::move(polylines))) {
|
||||||
if (! first) {
|
if (! first) {
|
||||||
// Try to connect the lines.
|
// Try to connect the lines.
|
||||||
Points &pts_end = polylines_out.back().points;
|
Points &pts_end = polylines_out.back().points;
|
||||||
const Point &first_point = it_polyline->points.front();
|
const Point &first_point = polyline.points.front();
|
||||||
const Point &last_point = pts_end.back();
|
const Point &last_point = pts_end.back();
|
||||||
// TODO: we should also check that both points are on a fill_boundary to avoid
|
// TODO: we should also check that both points are on a fill_boundary to avoid
|
||||||
// connecting paths on the boundaries of internal regions
|
// connecting paths on the boundaries of internal regions
|
||||||
if ((last_point - first_point).cast<double>().norm() <= 1.5 * distance &&
|
if ((last_point - first_point).cast<double>().norm() <= 1.5 * distance &&
|
||||||
expolygon_off.contains(Line(last_point, first_point))) {
|
expolygon_off.contains(Line(last_point, first_point))) {
|
||||||
// Append the polyline.
|
// Append the polyline.
|
||||||
pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end());
|
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The lines cannot be connected.
|
// The lines cannot be connected.
|
||||||
polylines_out.emplace_back(std::move(*it_polyline));
|
polylines_out.emplace_back(std::move(polyline));
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "../ClipperUtils.hpp"
|
#include "../ClipperUtils.hpp"
|
||||||
#include "../PolylineCollection.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -166,11 +166,8 @@ void FillGyroid::_fill_surface_single(
|
||||||
std::swap(expolygon_off, expolygons_off.front());
|
std::swap(expolygon_off, expolygons_off.front());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Polylines chained = PolylineCollection::chained_path_from(
|
|
||||||
std::move(polylines),
|
|
||||||
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (Polyline &polyline : chained) {
|
for (Polyline &polyline : chain_infill_polylines(std::move(polylines))) {
|
||||||
if (! first) {
|
if (! first) {
|
||||||
// Try to connect the lines.
|
// Try to connect the lines.
|
||||||
Points &pts_end = polylines_out.back().points;
|
Points &pts_end = polylines_out.back().points;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "../ClipperUtils.hpp"
|
#include "../ClipperUtils.hpp"
|
||||||
#include "../PolylineCollection.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
|
|
||||||
#include "FillHoneycomb.hpp"
|
#include "FillHoneycomb.hpp"
|
||||||
|
@ -93,22 +93,20 @@ void FillHoneycomb::_fill_surface_single(
|
||||||
|
|
||||||
// connect paths
|
// connect paths
|
||||||
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
|
if (! paths.empty()) { // prevent calling leftmost_point() on empty collections
|
||||||
Polylines chained = PolylineCollection::chained_path_from(
|
Polylines chained = chain_infill_polylines(std::move(paths));
|
||||||
std::move(paths),
|
|
||||||
PolylineCollection::leftmost_point(paths), false);
|
|
||||||
assert(paths.empty());
|
assert(paths.empty());
|
||||||
paths.clear();
|
paths.clear();
|
||||||
for (Polylines::iterator it_path = chained.begin(); it_path != chained.end(); ++ it_path) {
|
for (Polyline &path : chained) {
|
||||||
if (! paths.empty()) {
|
if (! paths.empty()) {
|
||||||
// distance between first point of this path and last point of last path
|
// distance between first point of this path and last point of last path
|
||||||
double distance = (it_path->first_point() - paths.back().last_point()).cast<double>().norm();
|
double distance = (path.first_point() - paths.back().last_point()).cast<double>().norm();
|
||||||
if (distance <= m.hex_width) {
|
if (distance <= m.hex_width) {
|
||||||
paths.back().points.insert(paths.back().points.end(), it_path->points.begin(), it_path->points.end());
|
paths.back().points.insert(paths.back().points.end(), path.points.begin(), path.points.end());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Don't connect the paths.
|
// Don't connect the paths.
|
||||||
paths.push_back(*it_path);
|
paths.push_back(std::move(path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "../ClipperUtils.hpp"
|
#include "../ClipperUtils.hpp"
|
||||||
#include "../ExPolygon.hpp"
|
#include "../ExPolygon.hpp"
|
||||||
#include "../PolylineCollection.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
|
|
||||||
#include "FillRectilinear.hpp"
|
#include "FillRectilinear.hpp"
|
||||||
|
@ -92,15 +92,12 @@ void FillRectilinear::_fill_surface_single(
|
||||||
std::swap(expolygon_off, expolygons_off.front());
|
std::swap(expolygon_off, expolygons_off.front());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Polylines chained = PolylineCollection::chained_path_from(
|
|
||||||
std::move(polylines),
|
|
||||||
PolylineCollection::leftmost_point(polylines), false); // reverse allowed
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) {
|
for (Polyline &polyline : chain_infill_polylines(std::move(polylines))) {
|
||||||
if (! first) {
|
if (! first) {
|
||||||
// Try to connect the lines.
|
// Try to connect the lines.
|
||||||
Points &pts_end = polylines_out.back().points;
|
Points &pts_end = polylines_out.back().points;
|
||||||
const Point &first_point = it_polyline->points.front();
|
const Point &first_point = polyline.points.front();
|
||||||
const Point &last_point = pts_end.back();
|
const Point &last_point = pts_end.back();
|
||||||
// Distance in X, Y.
|
// Distance in X, Y.
|
||||||
const Vector distance = last_point - first_point;
|
const Vector distance = last_point - first_point;
|
||||||
|
@ -109,12 +106,12 @@ void FillRectilinear::_fill_surface_single(
|
||||||
if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) &&
|
if (this->_can_connect(std::abs(distance(0)), std::abs(distance(1))) &&
|
||||||
expolygon_off.contains(Line(last_point, first_point))) {
|
expolygon_off.contains(Line(last_point, first_point))) {
|
||||||
// Append the polyline.
|
// Append the polyline.
|
||||||
pts_end.insert(pts_end.end(), it_polyline->points.begin(), it_polyline->points.end());
|
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The lines cannot be connected.
|
// The lines cannot be connected.
|
||||||
polylines_out.emplace_back(std::move(*it_polyline));
|
polylines_out.emplace_back(std::move(polyline));
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,7 +246,7 @@ static void extract_model_from_archive(
|
||||||
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
|
sscanf(normal_buf[2], "%f", &facet.normal(2)) != 1) {
|
||||||
// Normal was mangled. Maybe denormals or "not a number" were stored?
|
// Normal was mangled. Maybe denormals or "not a number" were stored?
|
||||||
// Just reset the normal and silently ignore it.
|
// Just reset the normal and silently ignore it.
|
||||||
memset(&facet.normal, 0, sizeof(facet.normal));
|
facet.normal = stl_normal::Zero();
|
||||||
}
|
}
|
||||||
facets.emplace_back(facet);
|
facets.emplace_back(facet);
|
||||||
}
|
}
|
||||||
|
@ -278,7 +278,7 @@ static void extract_model_from_archive(
|
||||||
instance->set_rotation(instance_rotation);
|
instance->set_rotation(instance_rotation);
|
||||||
instance->set_scaling_factor(instance_scaling_factor);
|
instance->set_scaling_factor(instance_scaling_factor);
|
||||||
instance->set_offset(instance_offset);
|
instance->set_offset(instance_offset);
|
||||||
if (group_id != (size_t)-1)
|
if (group_id != (unsigned int)(-1))
|
||||||
group_to_model_object[group_id] = model_object;
|
group_to_model_object[group_id] = model_object;
|
||||||
} else {
|
} else {
|
||||||
// This is not the 1st mesh of a group. Add it to the ModelObject.
|
// This is not the 1st mesh of a group. Add it to the ModelObject.
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "GCode/PrintExtents.hpp"
|
#include "GCode/PrintExtents.hpp"
|
||||||
#include "GCode/WipeTower.hpp"
|
#include "GCode/WipeTower.hpp"
|
||||||
|
#include "ShortestPath.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -1160,7 +1161,7 @@ void GCode::_do_export(Print &print, FILE *file)
|
||||||
for (const LayerToPrint <p : layers_to_print) {
|
for (const LayerToPrint <p : layers_to_print) {
|
||||||
std::vector<LayerToPrint> lrs;
|
std::vector<LayerToPrint> lrs;
|
||||||
lrs.emplace_back(std::move(ltp));
|
lrs.emplace_back(std::move(ltp));
|
||||||
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object.copies().data());
|
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), nullptr, © - object.copies().data());
|
||||||
print.throw_if_canceled();
|
print.throw_if_canceled();
|
||||||
}
|
}
|
||||||
#ifdef HAS_PRESSURE_EQUALIZER
|
#ifdef HAS_PRESSURE_EQUALIZER
|
||||||
|
@ -1174,12 +1175,8 @@ void GCode::_do_export(Print &print, FILE *file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Order objects using a nearest neighbor search.
|
// Order object instances using a nearest neighbor search.
|
||||||
std::vector<size_t> object_indices;
|
std::vector<std::pair<size_t, size_t>> print_object_instances_ordering = chain_print_object_instances(print);
|
||||||
Points object_reference_points;
|
|
||||||
for (PrintObject *object : print.objects())
|
|
||||||
object_reference_points.push_back(object->copies().front());
|
|
||||||
Slic3r::Geometry::chained_path(object_reference_points, object_indices);
|
|
||||||
// Sort layers by Z.
|
// Sort layers by Z.
|
||||||
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
// All extrusion moves with the same top layer height are extruded uninterrupted.
|
||||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
|
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print);
|
||||||
|
@ -1218,7 +1215,7 @@ void GCode::_do_export(Print &print, FILE *file)
|
||||||
const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first);
|
const LayerTools &layer_tools = tool_ordering.tools_for_layer(layer.first);
|
||||||
if (m_wipe_tower && layer_tools.has_wipe_tower)
|
if (m_wipe_tower && layer_tools.has_wipe_tower)
|
||||||
m_wipe_tower->next_layer();
|
m_wipe_tower->next_layer();
|
||||||
this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
|
this->process_layer(file, print, layer.second, layer_tools, &print_object_instances_ordering, size_t(-1));
|
||||||
print.throw_if_canceled();
|
print.throw_if_canceled();
|
||||||
}
|
}
|
||||||
#ifdef HAS_PRESSURE_EQUALIZER
|
#ifdef HAS_PRESSURE_EQUALIZER
|
||||||
|
@ -1529,8 +1526,54 @@ inline std::vector<GCode::ObjectByExtruder::Island>& object_islands_by_extruder(
|
||||||
return islands;
|
return islands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<GCode::InstanceToPrint> GCode::sort_print_object_instances(
|
||||||
|
std::vector<GCode::ObjectByExtruder> &objects_by_extruder,
|
||||||
|
const std::vector<LayerToPrint> &layers,
|
||||||
|
// Ordering must be defined for normal (non-sequential print).
|
||||||
|
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||||
|
// For sequential print, the instance of the object to be printing has to be defined.
|
||||||
|
const size_t single_object_instance_idx)
|
||||||
|
{
|
||||||
|
std::vector<InstanceToPrint> out;
|
||||||
|
|
||||||
|
if (ordering == nullptr) {
|
||||||
|
// Sequential print, single object is being printed.
|
||||||
|
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
||||||
|
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
||||||
|
const PrintObject *print_object = layers[layer_id].object();
|
||||||
|
if (print_object)
|
||||||
|
out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create mapping from PrintObject* to ObjectByExtruder*.
|
||||||
|
std::vector<std::pair<const PrintObject*, ObjectByExtruder*>> sorted;
|
||||||
|
sorted.reserve(objects_by_extruder.size());
|
||||||
|
for (ObjectByExtruder &object_by_extruder : objects_by_extruder) {
|
||||||
|
const size_t layer_id = &object_by_extruder - objects_by_extruder.data();
|
||||||
|
const PrintObject *print_object = layers[layer_id].object();
|
||||||
|
if (print_object)
|
||||||
|
sorted.emplace_back(print_object, &object_by_extruder);
|
||||||
|
}
|
||||||
|
std::sort(sorted.begin(), sorted.end());
|
||||||
|
|
||||||
|
if (! sorted.empty()) {
|
||||||
|
const Print &print = *sorted.front().first->print();
|
||||||
|
out.reserve(sorted.size());
|
||||||
|
for (const std::pair<size_t, size_t> &instance_id : *ordering) {
|
||||||
|
const PrintObject &print_object = *print.objects()[instance_id.first];
|
||||||
|
std::pair<const PrintObject*, ObjectByExtruder*> key(&print_object, nullptr);
|
||||||
|
auto it = std::lower_bound(sorted.begin(), sorted.end(), key);
|
||||||
|
if (it != sorted.end() && it->first == &print_object)
|
||||||
|
// ObjectByExtruder for this PrintObject was found.
|
||||||
|
out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance_id.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
// In sequential mode, process_layer is called once per each object and its copy,
|
// In sequential mode, process_layer is called once per each object and its copy,
|
||||||
// therefore layers will contain a single entry and single_object_idx will point to the copy of the object.
|
// therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object.
|
||||||
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
|
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
|
||||||
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
|
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
|
||||||
// and performing the extruder specific extrusions together.
|
// and performing the extruder specific extrusions together.
|
||||||
|
@ -1541,14 +1584,16 @@ void GCode::process_layer(
|
||||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||||
const std::vector<LayerToPrint> &layers,
|
const std::vector<LayerToPrint> &layers,
|
||||||
const LayerTools &layer_tools,
|
const LayerTools &layer_tools,
|
||||||
|
// Pairs of PrintObject index and its instance index.
|
||||||
|
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||||
// If set to size_t(-1), then print all copies of all objects.
|
// If set to size_t(-1), then print all copies of all objects.
|
||||||
// Otherwise print a single copy of a single object.
|
// Otherwise print a single copy of a single object.
|
||||||
const size_t single_object_idx)
|
const size_t single_object_instance_idx)
|
||||||
{
|
{
|
||||||
assert(! layers.empty());
|
assert(! layers.empty());
|
||||||
// assert(! layer_tools.extruders.empty());
|
// assert(! layer_tools.extruders.empty());
|
||||||
// Either printing all copies of all objects, or just a single copy of a single object.
|
// Either printing all copies of all objects, or just a single copy of a single object.
|
||||||
assert(single_object_idx == size_t(-1) || layers.size() == 1);
|
assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
|
||||||
|
|
||||||
if (layer_tools.extruders.empty())
|
if (layer_tools.extruders.empty())
|
||||||
// Nothing to extrude.
|
// Nothing to extrude.
|
||||||
|
@ -1762,6 +1807,17 @@ void GCode::process_layer(
|
||||||
layer_surface_bboxes.reserve(n_slices);
|
layer_surface_bboxes.reserve(n_slices);
|
||||||
for (const ExPolygon &expoly : layer.slices.expolygons)
|
for (const ExPolygon &expoly : layer.slices.expolygons)
|
||||||
layer_surface_bboxes.push_back(get_extents(expoly.contour));
|
layer_surface_bboxes.push_back(get_extents(expoly.contour));
|
||||||
|
// Traverse the slices in an increasing order of bounding box size, so that the islands inside another islands are tested first,
|
||||||
|
// so we can just test a point inside ExPolygon::contour and we may skip testing the holes.
|
||||||
|
std::vector<size_t> slices_test_order;
|
||||||
|
slices_test_order.reserve(n_slices);
|
||||||
|
for (size_t i = 0; i < n_slices; ++ i)
|
||||||
|
slices_test_order.emplace_back(i);
|
||||||
|
std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) {
|
||||||
|
const Vec2d s1 = layer_surface_bboxes[i].size().cast<double>();
|
||||||
|
const Vec2d s2 = layer_surface_bboxes[j].size().cast<double>();
|
||||||
|
return s1.x() * s1.y() < s2.x() * s2.y();
|
||||||
|
});
|
||||||
auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
|
auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) {
|
||||||
const BoundingBox &bbox = layer_surface_bboxes[i];
|
const BoundingBox &bbox = layer_surface_bboxes[i];
|
||||||
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
|
return point(0) >= bbox.min(0) && point(0) < bbox.max(0) &&
|
||||||
|
@ -1809,20 +1865,23 @@ void GCode::process_layer(
|
||||||
extruder,
|
extruder,
|
||||||
&layer_to_print - layers.data(),
|
&layer_to_print - layers.data(),
|
||||||
layers.size(), n_slices+1);
|
layers.size(), n_slices+1);
|
||||||
for (size_t i = 0; i <= n_slices; ++i)
|
for (size_t i = 0; i <= n_slices; ++ i) {
|
||||||
|
bool last = i == n_slices;
|
||||||
|
size_t island_idx = last ? n_slices : slices_test_order[i];
|
||||||
if (// fill->first_point does not fit inside any slice
|
if (// fill->first_point does not fit inside any slice
|
||||||
i == n_slices ||
|
last ||
|
||||||
// fill->first_point fits inside ith slice
|
// fill->first_point fits inside ith slice
|
||||||
point_inside_surface(i, fill->first_point())) {
|
point_inside_surface(island_idx, fill->first_point())) {
|
||||||
if (islands[i].by_region.empty())
|
if (islands[island_idx].by_region.empty())
|
||||||
islands[i].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
|
islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region());
|
||||||
islands[i].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
|
islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // for regions
|
} // for regions
|
||||||
}
|
}
|
||||||
} // for objects
|
} // for objects
|
||||||
|
@ -1883,62 +1942,49 @@ void GCode::process_layer(
|
||||||
if (objects_by_extruder_it == by_extruder.end())
|
if (objects_by_extruder_it == by_extruder.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
std::vector<InstanceToPrint> instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx);
|
||||||
|
|
||||||
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
// We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature):
|
||||||
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
|
bool is_anything_overridden = const_cast<LayerTools&>(layer_tools).wiping_extrusions().is_anything_overridden();
|
||||||
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
|
for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) {
|
||||||
if (is_anything_overridden && print_wipe_extrusions == 0)
|
if (is_anything_overridden && print_wipe_extrusions == 0)
|
||||||
gcode+="; PURGING FINISHED\n";
|
gcode+="; PURGING FINISHED\n";
|
||||||
|
|
||||||
for (ObjectByExtruder &object_by_extruder : objects_by_extruder_it->second) {
|
for (InstanceToPrint &instance_to_print : instances_to_print) {
|
||||||
const size_t layer_id = &object_by_extruder - objects_by_extruder_it->second.data();
|
m_config.apply(instance_to_print.print_object.config(), true);
|
||||||
const PrintObject *print_object = layers[layer_id].object();
|
m_layer = layers[instance_to_print.layer_id].layer();
|
||||||
if (print_object == nullptr)
|
|
||||||
// This layer is empty for this particular object, it has neither object extrusions nor support extrusions at this print_z.
|
|
||||||
continue;
|
|
||||||
|
|
||||||
m_config.apply(print_object->config(), true);
|
|
||||||
m_layer = layers[layer_id].layer();
|
|
||||||
if (m_config.avoid_crossing_perimeters)
|
if (m_config.avoid_crossing_perimeters)
|
||||||
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true));
|
m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->slices, true));
|
||||||
Points copies;
|
|
||||||
if (single_object_idx == size_t(-1))
|
|
||||||
copies = print_object->copies();
|
|
||||||
else
|
|
||||||
copies.push_back(print_object->copies()[single_object_idx]);
|
|
||||||
// Sort the copies by the closest point starting with the current print position.
|
|
||||||
|
|
||||||
unsigned int copy_id = 0;
|
|
||||||
for (const Point © : copies) {
|
|
||||||
if (this->config().gcode_label_objects)
|
if (this->config().gcode_label_objects)
|
||||||
gcode += std::string("; printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n";
|
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.
|
// When starting a new object, use the external motion planner for the first travel move.
|
||||||
std::pair<const PrintObject*, Point> this_object_copy(print_object, copy);
|
const Point &offset = instance_to_print.print_object.copies()[instance_to_print.instance_id];
|
||||||
|
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
|
||||||
if (m_last_obj_copy != this_object_copy)
|
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 = true;
|
||||||
m_last_obj_copy = this_object_copy;
|
m_last_obj_copy = this_object_copy;
|
||||||
this->set_origin(unscale(copy));
|
this->set_origin(unscale(offset));
|
||||||
if (object_by_extruder.support != nullptr && !print_wipe_extrusions) {
|
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
|
||||||
m_layer = layers[layer_id].support_layer;
|
m_layer = layers[instance_to_print.layer_id].support_layer;
|
||||||
gcode += this->extrude_support(
|
gcode += this->extrude_support(
|
||||||
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
|
// support_extrusion_role is erSupportMaterial, erSupportMaterialInterface or erMixed for all extrusion paths.
|
||||||
object_by_extruder.support->chained_path_from(m_last_pos, false, object_by_extruder.support_extrusion_role));
|
instance_to_print.object_by_extruder.support->chained_path_from(m_last_pos, false, instance_to_print.object_by_extruder.support_extrusion_role));
|
||||||
m_layer = layers[layer_id].layer();
|
m_layer = layers[instance_to_print.layer_id].layer();
|
||||||
}
|
}
|
||||||
for (ObjectByExtruder::Island &island : object_by_extruder.islands) {
|
for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) {
|
||||||
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(copy_id, extruder_id, print_wipe_extrusions) : island.by_region;
|
const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region;
|
||||||
|
|
||||||
if (print.config().infill_first) {
|
if (print.config().infill_first) {
|
||||||
gcode += this->extrude_infill(print, by_region_specific);
|
gcode += this->extrude_infill(print, by_region_specific);
|
||||||
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
|
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
|
||||||
} else {
|
} else {
|
||||||
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[layer_id]);
|
gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]);
|
||||||
gcode += this->extrude_infill(print,by_region_specific);
|
gcode += this->extrude_infill(print,by_region_specific);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this->config().gcode_label_objects)
|
if (this->config().gcode_label_objects)
|
||||||
gcode += std::string("; stop printing object ") + print_object->model_object()->name + " id:" + std::to_string(layer_id) + " copy " + std::to_string(copy_id) + "\n";
|
gcode += std::string("; stop 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";
|
||||||
++ copy_id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2542,12 +2588,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vector<ObjectBy
|
||||||
std::string gcode;
|
std::string gcode;
|
||||||
for (const ObjectByExtruder::Island::Region ®ion : by_region) {
|
for (const ObjectByExtruder::Island::Region ®ion : by_region) {
|
||||||
m_config.apply(print.regions()[®ion - &by_region.front()]->config());
|
m_config.apply(print.regions()[®ion - &by_region.front()]->config());
|
||||||
ExtrusionEntityCollection chained = region.infills.chained_path_from(m_last_pos, false);
|
for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos, false).entities) {
|
||||||
for (ExtrusionEntity *fill : chained.entities) {
|
|
||||||
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
|
auto *eec = dynamic_cast<ExtrusionEntityCollection*>(fill);
|
||||||
if (eec) {
|
if (eec) {
|
||||||
ExtrusionEntityCollection chained2 = eec->chained_path_from(m_last_pos, false);
|
for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos, false).entities)
|
||||||
for (ExtrusionEntity *ee : chained2.entities)
|
|
||||||
gcode += this->extrude_entity(*ee, "infill");
|
gcode += this->extrude_entity(*ee, "infill");
|
||||||
} else
|
} else
|
||||||
gcode += this->extrude_entity(*fill, "infill");
|
gcode += this->extrude_entity(*fill, "infill");
|
||||||
|
|
|
@ -202,7 +202,7 @@ protected:
|
||||||
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
|
const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; }
|
||||||
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
|
coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; }
|
||||||
};
|
};
|
||||||
static std::vector<GCode::LayerToPrint> collect_layers_to_print(const PrintObject &object);
|
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
|
||||||
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
|
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
|
||||||
void process_layer(
|
void process_layer(
|
||||||
// Write into the output file.
|
// Write into the output file.
|
||||||
|
@ -211,6 +211,8 @@ protected:
|
||||||
// Set of object & print layers of the same PrintObject and with the same print_z.
|
// Set of object & print layers of the same PrintObject and with the same print_z.
|
||||||
const std::vector<LayerToPrint> &layers,
|
const std::vector<LayerToPrint> &layers,
|
||||||
const LayerTools &layer_tools,
|
const LayerTools &layer_tools,
|
||||||
|
// Pairs of PrintObject index and its instance index.
|
||||||
|
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||||
// If set to size_t(-1), then print all copies of all objects.
|
// If set to size_t(-1), then print all copies of all objects.
|
||||||
// Otherwise print a single copy of a single object.
|
// Otherwise print a single copy of a single object.
|
||||||
const size_t single_object_idx = size_t(-1));
|
const size_t single_object_idx = size_t(-1));
|
||||||
|
@ -258,6 +260,25 @@ protected:
|
||||||
std::vector<Island> islands;
|
std::vector<Island> islands;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct InstanceToPrint
|
||||||
|
{
|
||||||
|
InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) :
|
||||||
|
object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {}
|
||||||
|
|
||||||
|
ObjectByExtruder &object_by_extruder;
|
||||||
|
const size_t layer_id;
|
||||||
|
const PrintObject &print_object;
|
||||||
|
// Instance idx of the copy of a print object.
|
||||||
|
const size_t instance_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<InstanceToPrint> sort_print_object_instances(
|
||||||
|
std::vector<ObjectByExtruder> &objects_by_extruder,
|
||||||
|
const std::vector<LayerToPrint> &layers,
|
||||||
|
// Ordering must be defined for normal (non-sequential print).
|
||||||
|
const std::vector<std::pair<size_t, size_t>> *ordering,
|
||||||
|
// For sequential print, the instance of the object to be printing has to be defined.
|
||||||
|
const size_t single_object_instance_idx);
|
||||||
|
|
||||||
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
|
std::string extrude_perimeters(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region, std::unique_ptr<EdgeGrid::Grid> &lower_layer_edge_grid);
|
||||||
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
|
std::string extrude_infill(const Print &print, const std::vector<ObjectByExtruder::Island::Region> &by_region);
|
||||||
|
|
|
@ -309,49 +309,7 @@ convex_hull(const Polygons &polygons)
|
||||||
return convex_hull(std::move(pp));
|
return convex_hull(std::move(pp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* accepts an arrayref of points and returns a list of indices
|
bool directions_parallel(double angle1, double angle2, double max_diff)
|
||||||
according to a nearest-neighbor walk */
|
|
||||||
void
|
|
||||||
chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near)
|
|
||||||
{
|
|
||||||
PointConstPtrs my_points;
|
|
||||||
std::map<const Point*,Points::size_type> indices;
|
|
||||||
my_points.reserve(points.size());
|
|
||||||
for (Points::const_iterator it = points.begin(); it != points.end(); ++it) {
|
|
||||||
my_points.push_back(&*it);
|
|
||||||
indices[&*it] = it - points.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
retval.reserve(points.size());
|
|
||||||
while (!my_points.empty()) {
|
|
||||||
Points::size_type idx = start_near.nearest_point_index(my_points);
|
|
||||||
start_near = *my_points[idx];
|
|
||||||
retval.push_back(indices[ my_points[idx] ]);
|
|
||||||
my_points.erase(my_points.begin() + idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
chained_path(const Points &points, std::vector<Points::size_type> &retval)
|
|
||||||
{
|
|
||||||
if (points.empty()) return; // can't call front() on empty vector
|
|
||||||
chained_path(points, retval, points.front());
|
|
||||||
}
|
|
||||||
|
|
||||||
/* retval and items must be different containers */
|
|
||||||
template<class T>
|
|
||||||
void
|
|
||||||
chained_path_items(Points &points, T &items, T &retval)
|
|
||||||
{
|
|
||||||
std::vector<Points::size_type> indices;
|
|
||||||
chained_path(points, indices);
|
|
||||||
for (std::vector<Points::size_type>::const_iterator it = indices.begin(); it != indices.end(); ++it)
|
|
||||||
retval.push_back(items[*it]);
|
|
||||||
}
|
|
||||||
template void chained_path_items(Points &points, ClipperLib::PolyNodes &items, ClipperLib::PolyNodes &retval);
|
|
||||||
|
|
||||||
bool
|
|
||||||
directions_parallel(double angle1, double angle2, double max_diff)
|
|
||||||
{
|
{
|
||||||
double diff = fabs(angle1 - angle2);
|
double diff = fabs(angle1 - angle2);
|
||||||
max_diff += EPSILON;
|
max_diff += EPSILON;
|
||||||
|
@ -359,8 +317,7 @@ directions_parallel(double angle1, double angle2, double max_diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
bool
|
bool contains(const std::vector<T> &vector, const Point &point)
|
||||||
contains(const std::vector<T> &vector, const Point &point)
|
|
||||||
{
|
{
|
||||||
for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) {
|
for (typename std::vector<T>::const_iterator it = vector.begin(); it != vector.end(); ++it) {
|
||||||
if (it->contains(point)) return true;
|
if (it->contains(point)) return true;
|
||||||
|
@ -369,16 +326,14 @@ contains(const std::vector<T> &vector, const Point &point)
|
||||||
}
|
}
|
||||||
template bool contains(const ExPolygons &vector, const Point &point);
|
template bool contains(const ExPolygons &vector, const Point &point);
|
||||||
|
|
||||||
double
|
double rad2deg_dir(double angle)
|
||||||
rad2deg_dir(double angle)
|
|
||||||
{
|
{
|
||||||
angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0);
|
angle = (angle < PI) ? (-angle + PI/2.0) : (angle + PI/2.0);
|
||||||
if (angle < 0) angle += PI;
|
if (angle < 0) angle += PI;
|
||||||
return rad2deg(angle);
|
return rad2deg(angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
|
||||||
simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
|
|
||||||
{
|
{
|
||||||
Polygons pp;
|
Polygons pp;
|
||||||
for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) {
|
for (Polygons::const_iterator it = polygons.begin(); it != polygons.end(); ++it) {
|
||||||
|
@ -391,8 +346,7 @@ simplify_polygons(const Polygons &polygons, double tolerance, Polygons* retval)
|
||||||
*retval = Slic3r::simplify_polygons(pp);
|
*retval = Slic3r::simplify_polygons(pp);
|
||||||
}
|
}
|
||||||
|
|
||||||
double
|
double linint(double value, double oldmin, double oldmax, double newmin, double newmax)
|
||||||
linint(double value, double oldmin, double oldmax, double newmin, double newmax)
|
|
||||||
{
|
{
|
||||||
return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
|
return (value - oldmin) * (newmax - newmin) / (oldmax - oldmin) + newmin;
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,9 +138,6 @@ Pointf3s convex_hull(Pointf3s points);
|
||||||
Polygon convex_hull(Points points);
|
Polygon convex_hull(Points points);
|
||||||
Polygon convex_hull(const Polygons &polygons);
|
Polygon convex_hull(const Polygons &polygons);
|
||||||
|
|
||||||
void chained_path(const Points &points, std::vector<Points::size_type> &retval, Point start_near);
|
|
||||||
void chained_path(const Points &points, std::vector<Points::size_type> &retval);
|
|
||||||
template<class T> void chained_path_items(Points &points, T &items, T &retval);
|
|
||||||
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
|
bool directions_parallel(double angle1, double angle2, double max_diff = 0);
|
||||||
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
|
template<class T> bool contains(const std::vector<T> &vector, const Point &point);
|
||||||
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
|
template<typename T> T rad2deg(T angle) { return T(180.0) * angle / T(PI); }
|
||||||
|
|
231
src/libslic3r/KDTreeIndirect.hpp
Normal file
231
src/libslic3r/KDTreeIndirect.hpp
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
// KD tree built upon external data set, referencing the external data by integer indices.
|
||||||
|
|
||||||
|
#ifndef slic3r_KDTreeIndirect_hpp_
|
||||||
|
#define slic3r_KDTreeIndirect_hpp_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// KD tree for N-dimensional closest point search.
|
||||||
|
template<size_t ANumDimensions, typename ACoordType, typename ACoordinateFn>
|
||||||
|
class KDTreeIndirect
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr size_t NumDimensions = ANumDimensions;
|
||||||
|
using CoordinateFn = ACoordinateFn;
|
||||||
|
using CoordType = ACoordType;
|
||||||
|
// Following could be static constexpr size_t, but that would not link in C++11
|
||||||
|
enum : size_t {
|
||||||
|
npos = size_t(-1)
|
||||||
|
};
|
||||||
|
|
||||||
|
KDTreeIndirect(CoordinateFn coordinate) : coordinate(coordinate) {}
|
||||||
|
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> indices) : coordinate(coordinate) { this->build(std::move(indices)); }
|
||||||
|
KDTreeIndirect(CoordinateFn coordinate, std::vector<size_t> &&indices) : coordinate(coordinate) { this->build(std::move(indices)); }
|
||||||
|
KDTreeIndirect(CoordinateFn coordinate, size_t num_indices) : coordinate(coordinate) { this->build(num_indices); }
|
||||||
|
KDTreeIndirect(KDTreeIndirect &&rhs) : m_nodes(std::move(rhs.m_nodes)), coordinate(std::move(rhs.coordinate)) {}
|
||||||
|
KDTreeIndirect& operator=(KDTreeIndirect &&rhs) { m_nodes = std::move(rhs.m_nodes); coordinate = std::move(rhs.coordinate); return *this; }
|
||||||
|
void clear() { m_nodes.clear(); }
|
||||||
|
|
||||||
|
void build(size_t num_indices)
|
||||||
|
{
|
||||||
|
std::vector<size_t> indices;
|
||||||
|
indices.reserve(num_indices);
|
||||||
|
for (size_t i = 0; i < num_indices; ++ i)
|
||||||
|
indices.emplace_back(i);
|
||||||
|
this->build(std::move(indices));
|
||||||
|
}
|
||||||
|
|
||||||
|
void build(std::vector<size_t> &&indices)
|
||||||
|
{
|
||||||
|
if (indices.empty())
|
||||||
|
clear();
|
||||||
|
else {
|
||||||
|
// Allocate a next highest power of 2 nodes, because the incomplete binary tree will not have the leaves filled strictly from the left.
|
||||||
|
m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos);
|
||||||
|
build_recursive(indices, 0, 0, 0, (int)(indices.size() - 1));
|
||||||
|
}
|
||||||
|
indices.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class VisitorReturnMask : unsigned int
|
||||||
|
{
|
||||||
|
CONTINUE_LEFT = 1,
|
||||||
|
CONTINUE_RIGHT = 2,
|
||||||
|
STOP = 4,
|
||||||
|
};
|
||||||
|
template<typename CoordType>
|
||||||
|
unsigned int descent_mask(const CoordType &point_coord, const CoordType &search_radius, size_t idx, size_t dimension) const
|
||||||
|
{
|
||||||
|
CoordType dist = point_coord - this->coordinate(idx, dimension);
|
||||||
|
return (dist * dist < search_radius + CoordType(EPSILON)) ?
|
||||||
|
((unsigned int)(VisitorReturnMask::CONTINUE_LEFT) | (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT)) :
|
||||||
|
(dist < CoordType(0)) ? (unsigned int)(VisitorReturnMask::CONTINUE_RIGHT) : (unsigned int)(VisitorReturnMask::CONTINUE_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visitor is supposed to return a bit mask of VisitorReturnMask.
|
||||||
|
template<typename Visitor>
|
||||||
|
void visit(Visitor &visitor) const
|
||||||
|
{
|
||||||
|
visit_recursive(0, 0, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
CoordinateFn coordinate;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||||
|
void build_recursive(std::vector<size_t> &input, size_t node, int dimension, int left, int right)
|
||||||
|
{
|
||||||
|
if (left > right)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(node < m_nodes.size());
|
||||||
|
|
||||||
|
if (left == right) {
|
||||||
|
// Insert a node into the balanced tree.
|
||||||
|
m_nodes[node] = input[left];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition the input sequence to two equal halves.
|
||||||
|
int center = (left + right) >> 1;
|
||||||
|
partition_input(input, dimension, left, right, center);
|
||||||
|
// Insert a node into the tree.
|
||||||
|
m_nodes[node] = input[center];
|
||||||
|
// Partition the left and right subtrees.
|
||||||
|
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
|
||||||
|
build_recursive(input, (node << 1) + 1, next_dimension, left, center - 1);
|
||||||
|
build_recursive(input, (node << 1) + 2, next_dimension, center + 1, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partition the input m_nodes <left, right> at k using QuickSelect method.
|
||||||
|
// https://en.wikipedia.org/wiki/Quickselect
|
||||||
|
void partition_input(std::vector<size_t> &input, int dimension, int left, int right, int k) const
|
||||||
|
{
|
||||||
|
while (left < right) {
|
||||||
|
// Guess the k'th element.
|
||||||
|
// Pick the pivot as a median of first, center and last value.
|
||||||
|
// Sort first, center and last values.
|
||||||
|
int center = (left + right) >> 1;
|
||||||
|
auto left_value = this->coordinate(input[left], dimension);
|
||||||
|
auto center_value = this->coordinate(input[center], dimension);
|
||||||
|
auto right_value = this->coordinate(input[right], dimension);
|
||||||
|
if (center_value < left_value) {
|
||||||
|
std::swap(input[left], input[center]);
|
||||||
|
std::swap(left_value, center_value);
|
||||||
|
}
|
||||||
|
if (right_value < left_value) {
|
||||||
|
std::swap(input[left], input[right]);
|
||||||
|
std::swap(left_value, right_value);
|
||||||
|
}
|
||||||
|
if (right_value < center_value) {
|
||||||
|
std::swap(input[center], input[right]);
|
||||||
|
// No need to do that, result is not used.
|
||||||
|
// std::swap(center_value, right_value);
|
||||||
|
}
|
||||||
|
// Only two or three values are left and those are sorted already.
|
||||||
|
if (left + 3 > right)
|
||||||
|
break;
|
||||||
|
// left and right items are already at their correct positions.
|
||||||
|
// input[left].point[dimension] <= input[center].point[dimension] <= input[right].point[dimension]
|
||||||
|
// Move the pivot to the (right - 1) position.
|
||||||
|
std::swap(input[center], input[right - 1]);
|
||||||
|
// Pivot value.
|
||||||
|
double pivot = this->coordinate(input[right - 1], dimension);
|
||||||
|
// Partition the set based on the pivot.
|
||||||
|
int i = left;
|
||||||
|
int j = right - 1;
|
||||||
|
for (;;) {
|
||||||
|
// Skip left points that are already at correct positions.
|
||||||
|
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||||
|
while (this->coordinate(input[++ i], dimension) < pivot) ;
|
||||||
|
// Skip right points that are already at correct positions.
|
||||||
|
while (this->coordinate(input[-- j], dimension) > pivot && i < j) ;
|
||||||
|
if (i >= j)
|
||||||
|
break;
|
||||||
|
std::swap(input[i], input[j]);
|
||||||
|
}
|
||||||
|
// Restore pivot to the center of the sequence.
|
||||||
|
std::swap(input[i], input[right]);
|
||||||
|
// Which side the kth element is in?
|
||||||
|
if (k < i)
|
||||||
|
right = i - 1;
|
||||||
|
else if (k == i)
|
||||||
|
// Sequence is partitioned, kth element is at its place.
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
left = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Visitor>
|
||||||
|
void visit_recursive(size_t node, size_t dimension, Visitor &visitor) const
|
||||||
|
{
|
||||||
|
assert(! m_nodes.empty());
|
||||||
|
if (node >= m_nodes.size() || m_nodes[node] == npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Left / right child node index.
|
||||||
|
size_t left = (node << 1) + 1;
|
||||||
|
size_t right = left + 1;
|
||||||
|
unsigned int mask = visitor(m_nodes[node], dimension);
|
||||||
|
if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) {
|
||||||
|
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
|
||||||
|
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_LEFT)
|
||||||
|
visit_recursive(left, next_dimension, visitor);
|
||||||
|
if (mask & (unsigned int)VisitorReturnMask::CONTINUE_RIGHT)
|
||||||
|
visit_recursive(right, next_dimension, visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<size_t> m_nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find a closest point using Euclidian metrics.
|
||||||
|
// Returns npos if not found.
|
||||||
|
template<typename KDTreeIndirectType, typename PointType, typename FilterFn>
|
||||||
|
size_t find_closest_point(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter)
|
||||||
|
{
|
||||||
|
struct Visitor {
|
||||||
|
using CoordType = typename KDTreeIndirectType::CoordType;
|
||||||
|
const KDTreeIndirectType &kdtree;
|
||||||
|
const PointType &point;
|
||||||
|
const FilterFn filter;
|
||||||
|
size_t min_idx = KDTreeIndirectType::npos;
|
||||||
|
CoordType min_dist = std::numeric_limits<CoordType>::max();
|
||||||
|
|
||||||
|
Visitor(const KDTreeIndirectType &kdtree, const PointType &point, FilterFn filter) : kdtree(kdtree), point(point), filter(filter) {}
|
||||||
|
unsigned int operator()(size_t idx, size_t dimension) {
|
||||||
|
if (this->filter(idx)) {
|
||||||
|
auto dist = CoordType(0);
|
||||||
|
for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++ i) {
|
||||||
|
CoordType d = point[i] - kdtree.coordinate(idx, i);
|
||||||
|
dist += d * d;
|
||||||
|
}
|
||||||
|
if (dist < min_dist) {
|
||||||
|
min_dist = dist;
|
||||||
|
min_idx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kdtree.descent_mask(point[dimension], min_dist, idx, dimension);
|
||||||
|
}
|
||||||
|
} visitor(kdtree, point, filter);
|
||||||
|
|
||||||
|
kdtree.visit(visitor);
|
||||||
|
return visitor.min_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename KDTreeIndirectType, typename PointType>
|
||||||
|
size_t find_closest_point(const KDTreeIndirectType& kdtree, const PointType& point)
|
||||||
|
{
|
||||||
|
return find_closest_point(kdtree, point, [](size_t) { return true; });
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_KDTreeIndirect_hpp_ */
|
|
@ -1,8 +1,8 @@
|
||||||
#include "Layer.hpp"
|
#include "Layer.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Geometry.hpp"
|
|
||||||
#include "Print.hpp"
|
#include "Print.hpp"
|
||||||
#include "Fill/Fill.hpp"
|
#include "Fill/Fill.hpp"
|
||||||
|
#include "ShortestPath.hpp"
|
||||||
#include "SVG.hpp"
|
#include "SVG.hpp"
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
@ -57,8 +57,7 @@ void Layer::make_slices()
|
||||||
ordering_points.push_back(ex.contour.first_point());
|
ordering_points.push_back(ex.contour.first_point());
|
||||||
|
|
||||||
// sort slices
|
// sort slices
|
||||||
std::vector<Points::size_type> order;
|
std::vector<Points::size_type> order = chain_points(ordering_points);
|
||||||
Slic3r::Geometry::chained_path(ordering_points, order);
|
|
||||||
|
|
||||||
// populate slices vector
|
// populate slices vector
|
||||||
for (size_t i : order)
|
for (size_t i : order)
|
||||||
|
|
|
@ -1462,7 +1462,7 @@ stl_stats ModelObject::get_object_stl_stats() const
|
||||||
return this->volumes[0]->mesh().stl.stats;
|
return this->volumes[0]->mesh().stl.stats;
|
||||||
|
|
||||||
stl_stats full_stats;
|
stl_stats full_stats;
|
||||||
memset(&full_stats, 0, sizeof(stl_stats));
|
full_stats.volume = 0.f;
|
||||||
|
|
||||||
// fill full_stats from all objet's meshes
|
// fill full_stats from all objet's meshes
|
||||||
for (ModelVolume* volume : this->volumes)
|
for (ModelVolume* volume : this->volumes)
|
||||||
|
|
|
@ -13,21 +13,28 @@ public:
|
||||||
{}
|
{}
|
||||||
~MutablePriorityQueue() { clear(); }
|
~MutablePriorityQueue() { clear(); }
|
||||||
|
|
||||||
inline void clear() { m_heap.clear(); }
|
void clear();
|
||||||
inline void reserve(size_t cnt) { m_heap.reserve(cnt); }
|
void reserve(size_t cnt) { m_heap.reserve(cnt); }
|
||||||
inline void push(const T &item);
|
void push(const T &item);
|
||||||
inline void push(T &&item);
|
void push(T &&item);
|
||||||
inline void pop();
|
void pop();
|
||||||
inline T& top() { return m_heap.front(); }
|
T& top() { return m_heap.front(); }
|
||||||
inline void remove(size_t idx);
|
void remove(size_t idx);
|
||||||
inline void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); }
|
void update(size_t idx) { T item = m_heap[idx]; remove(idx); push(item); }
|
||||||
|
|
||||||
inline size_t size() const { return m_heap.size(); }
|
size_t size() const { return m_heap.size(); }
|
||||||
inline bool empty() const { return m_heap.empty(); }
|
bool empty() const { return m_heap.empty(); }
|
||||||
|
|
||||||
|
using iterator = typename std::vector<T>::iterator;
|
||||||
|
using const_iterator = typename std::vector<T>::const_iterator;
|
||||||
|
iterator begin() { return m_heap.begin(); }
|
||||||
|
iterator end() { return m_heap.end(); }
|
||||||
|
const_iterator cbegin() const { return m_heap.cbegin(); }
|
||||||
|
const_iterator cend() const { return m_heap.cend(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
inline void update_heap_up(size_t top, size_t bottom);
|
void update_heap_up(size_t top, size_t bottom);
|
||||||
inline void update_heap_down(size_t top, size_t bottom);
|
void update_heap_down(size_t top, size_t bottom);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<T> m_heap;
|
std::vector<T> m_heap;
|
||||||
|
@ -42,6 +49,17 @@ MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(
|
||||||
std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
|
std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T, class LessPredicate, class IndexSetter>
|
||||||
|
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::clear()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
for (size_t idx = 0; idx < m_heap.size(); ++ idx)
|
||||||
|
// Mark as removed from the queue.
|
||||||
|
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
|
||||||
|
#endif /* NDEBUG */
|
||||||
|
m_heap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
template<class T, class LessPredicate, class IndexSetter>
|
template<class T, class LessPredicate, class IndexSetter>
|
||||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
|
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
|
||||||
{
|
{
|
||||||
|
@ -64,6 +82,10 @@ template<class T, class LessPredicate, class IndexSetter>
|
||||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
|
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
|
||||||
{
|
{
|
||||||
assert(! m_heap.empty());
|
assert(! m_heap.empty());
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// Mark as removed from the queue.
|
||||||
|
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
|
||||||
|
#endif /* NDEBUG */
|
||||||
if (m_heap.size() > 1) {
|
if (m_heap.size() > 1) {
|
||||||
m_heap.front() = m_heap.back();
|
m_heap.front() = m_heap.back();
|
||||||
m_heap.pop_back();
|
m_heap.pop_back();
|
||||||
|
@ -77,6 +99,10 @@ template<class T, class LessPredicate, class IndexSetter>
|
||||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
|
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
|
||||||
{
|
{
|
||||||
assert(idx < m_heap.size());
|
assert(idx < m_heap.size());
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// Mark as removed from the queue.
|
||||||
|
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
|
||||||
|
#endif /* NDEBUG */
|
||||||
if (idx + 1 == m_heap.size()) {
|
if (idx + 1 == m_heap.size()) {
|
||||||
m_heap.pop_back();
|
m_heap.pop_back();
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "PerimeterGenerator.hpp"
|
#include "PerimeterGenerator.hpp"
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "ExtrusionEntityCollection.hpp"
|
#include "ExtrusionEntityCollection.hpp"
|
||||||
|
#include "ShortestPath.hpp"
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
|
@ -86,24 +88,24 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow)
|
static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector<ExtrusionEntity*> &out)
|
||||||
{
|
{
|
||||||
// This value determines granularity of adaptive width, as G-code does not allow
|
// This value determines granularity of adaptive width, as G-code does not allow
|
||||||
// variable extrusion within a single move; this value shall only affect the amount
|
// variable extrusion within a single move; this value shall only affect the amount
|
||||||
// of segments, and any pruning shall be performed before we apply this tolerance.
|
// of segments, and any pruning shall be performed before we apply this tolerance.
|
||||||
ExtrusionEntityCollection coll;
|
|
||||||
const float tolerance = float(scale_(0.05));
|
const float tolerance = float(scale_(0.05));
|
||||||
for (const ThickPolyline &p : polylines) {
|
for (const ThickPolyline &p : polylines) {
|
||||||
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
|
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
|
||||||
// Append paths to collection.
|
// Append paths to collection.
|
||||||
if (! paths.empty()) {
|
if (! paths.empty()) {
|
||||||
if (paths.front().first_point() == paths.back().last_point())
|
if (paths.front().first_point() == paths.back().last_point())
|
||||||
coll.append(ExtrusionLoop(std::move(paths)));
|
out.emplace_back(new ExtrusionLoop(std::move(paths)));
|
||||||
else
|
else {
|
||||||
coll.append(std::move(paths));
|
for (ExtrusionPath &path : paths)
|
||||||
|
out.emplace_back(new ExtrusionPath(std::move(path)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return coll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hierarchy of perimeters.
|
// Hierarchy of perimeters.
|
||||||
|
@ -186,43 +188,47 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
||||||
paths.push_back(path);
|
paths.push_back(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
coll.append(ExtrusionLoop(paths, loop_role));
|
coll.append(ExtrusionLoop(std::move(paths), loop_role));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append thin walls to the nearest-neighbor search (only for first iteration)
|
// Append thin walls to the nearest-neighbor search (only for first iteration)
|
||||||
if (! thin_walls.empty()) {
|
if (! thin_walls.empty()) {
|
||||||
ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow);
|
variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow, coll.entities);
|
||||||
coll.append(tw.entities);
|
|
||||||
thin_walls.clear();
|
thin_walls.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort entities into a new collection using a nearest-neighbor search,
|
// Traverse children and build the final collection.
|
||||||
// preserving the original indices which are useful for detecting thin walls.
|
Point zero_point(0, 0);
|
||||||
ExtrusionEntityCollection sorted_coll;
|
std::vector<std::pair<size_t, bool>> chain = chain_extrusion_entities(coll.entities, &zero_point);
|
||||||
coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
|
ExtrusionEntityCollection out;
|
||||||
|
for (const std::pair<size_t, bool> &idx : chain) {
|
||||||
// traverse children and build the final collection
|
assert(coll.entities[idx.first] != nullptr);
|
||||||
ExtrusionEntityCollection entities;
|
if (idx.first >= loops.size()) {
|
||||||
for (const size_t &idx : sorted_coll.orig_indices) {
|
// This is a thin wall.
|
||||||
if (idx >= loops.size()) {
|
out.entities.reserve(out.entities.size() + 1);
|
||||||
// This is a thin wall. Let's get it from the sorted collection as it might have been reversed.
|
out.entities.emplace_back(coll.entities[idx.first]);
|
||||||
entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()]));
|
coll.entities[idx.first] = nullptr;
|
||||||
|
if (idx.second)
|
||||||
|
out.entities.back()->reverse();
|
||||||
} else {
|
} else {
|
||||||
const PerimeterGeneratorLoop &loop = loops[idx];
|
const PerimeterGeneratorLoop &loop = loops[idx.first];
|
||||||
ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]);
|
assert(thin_walls.empty());
|
||||||
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
|
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
|
||||||
|
out.entities.reserve(out.entities.size() + children.entities.size() + 1);
|
||||||
|
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
|
||||||
|
coll.entities[idx.first] = nullptr;
|
||||||
if (loop.is_contour) {
|
if (loop.is_contour) {
|
||||||
eloop.make_counter_clockwise();
|
eloop->make_counter_clockwise();
|
||||||
entities.append(std::move(children.entities));
|
out.append(std::move(children.entities));
|
||||||
entities.append(std::move(eloop));
|
out.entities.emplace_back(eloop);
|
||||||
} else {
|
} else {
|
||||||
eloop.make_clockwise();
|
eloop->make_clockwise();
|
||||||
entities.append(std::move(eloop));
|
out.entities.emplace_back(eloop);
|
||||||
entities.append(std::move(children.entities));
|
out.append(std::move(children.entities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return entities;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerimeterGenerator::process()
|
void PerimeterGenerator::process()
|
||||||
|
@ -445,8 +451,8 @@ void PerimeterGenerator::process()
|
||||||
for (const ExPolygon &ex : gaps_ex)
|
for (const ExPolygon &ex : gaps_ex)
|
||||||
ex.medial_axis(max, min, &polylines);
|
ex.medial_axis(max, min, &polylines);
|
||||||
if (! polylines.empty()) {
|
if (! polylines.empty()) {
|
||||||
ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow);
|
ExtrusionEntityCollection gap_fill;
|
||||||
this->gap_fill->append(gap_fill.entities);
|
variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities);
|
||||||
/* Make sure we don't infill narrow parts that are already gap-filled
|
/* Make sure we don't infill narrow parts that are already gap-filled
|
||||||
(we only consider this surface's gaps to reduce the diff() complexity).
|
(we only consider this surface's gaps to reduce the diff() complexity).
|
||||||
Growing actual extrusions ensures that gaps not filled by medial axis
|
Growing actual extrusions ensures that gaps not filled by medial axis
|
||||||
|
@ -456,6 +462,7 @@ void PerimeterGenerator::process()
|
||||||
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
//FIXME Vojtech: This grows by a rounded extrusion width, not by line spacing,
|
||||||
// therefore it may cover the area, but no the volume.
|
// therefore it may cover the area, but no the volume.
|
||||||
last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f));
|
last = diff_ex(to_polygons(last), gap_fill.polygons_covered_by_width(10.f));
|
||||||
|
this->gap_fill->append(std::move(gap_fill.entities));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,6 @@ Polyline::operator Line() const
|
||||||
return Line(this->points.front(), this->points.back());
|
return Line(this->points.front(), this->points.back());
|
||||||
}
|
}
|
||||||
|
|
||||||
Point
|
|
||||||
Polyline::last_point() const
|
|
||||||
{
|
|
||||||
return this->points.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
Point
|
Point
|
||||||
Polyline::leftmost_point() const
|
Polyline::leftmost_point() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -62,7 +62,8 @@ public:
|
||||||
|
|
||||||
operator Polylines() const;
|
operator Polylines() const;
|
||||||
operator Line() const;
|
operator Line() const;
|
||||||
Point last_point() const;
|
Point last_point() const override { return this->points.back(); }
|
||||||
|
|
||||||
Point leftmost_point() const;
|
Point leftmost_point() const;
|
||||||
virtual Lines lines() const;
|
virtual Lines lines() const;
|
||||||
void clip_end(double distance);
|
void clip_end(double distance);
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Flow.hpp"
|
#include "Flow.hpp"
|
||||||
#include "Geometry.hpp"
|
#include "Geometry.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
|
#include "ShortestPath.hpp"
|
||||||
#include "SupportMaterial.hpp"
|
#include "SupportMaterial.hpp"
|
||||||
#include "GCode.hpp"
|
#include "GCode.hpp"
|
||||||
#include "GCode/WipeTower.hpp"
|
#include "GCode/WipeTower.hpp"
|
||||||
|
@ -1824,8 +1825,8 @@ void Print::_make_brim()
|
||||||
[](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) {
|
[](const std::pair<const ClipperLib_Z::Path*, size_t> &l, const std::pair<const ClipperLib_Z::Path*, size_t> &r) {
|
||||||
return l.second < r.second;
|
return l.second < r.second;
|
||||||
});
|
});
|
||||||
Vec3f last_pt(0.f, 0.f, 0.f);
|
|
||||||
|
|
||||||
|
Point last_pt(0, 0);
|
||||||
for (size_t i = 0; i < loops_trimmed_order.size();) {
|
for (size_t i = 0; i < loops_trimmed_order.size();) {
|
||||||
// Find all pieces that the initial loop was split into.
|
// Find all pieces that the initial loop was split into.
|
||||||
size_t j = i + 1;
|
size_t j = i + 1;
|
||||||
|
@ -1841,16 +1842,23 @@ void Print::_make_brim()
|
||||||
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
|
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
|
||||||
i = j;
|
i = j;
|
||||||
} else {
|
} else {
|
||||||
//FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance.
|
//FIXME The path chaining here may not be optimal.
|
||||||
|
ExtrusionEntityCollection this_loop_trimmed;
|
||||||
|
this_loop_trimmed.entities.reserve(j - i);
|
||||||
for (; i < j; ++ i) {
|
for (; i < j; ++ i) {
|
||||||
m_brim.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
|
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height())));
|
||||||
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
|
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
|
||||||
Points &points = static_cast<ExtrusionPath*>(m_brim.entities.back())->polyline.points;
|
Points &points = static_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
|
||||||
points.reserve(path.size());
|
points.reserve(path.size());
|
||||||
for (const ClipperLib_Z::IntPoint &pt : path)
|
for (const ClipperLib_Z::IntPoint &pt : path)
|
||||||
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
|
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
|
||||||
}
|
}
|
||||||
|
chain_and_reorder_extrusion_entities(this_loop_trimmed.entities, &last_pt);
|
||||||
|
m_brim.entities.reserve(m_brim.entities.size() + this_loop_trimmed.entities.size());
|
||||||
|
append(m_brim.entities, std::move(this_loop_trimmed.entities));
|
||||||
|
this_loop_trimmed.entities.clear();
|
||||||
}
|
}
|
||||||
|
last_pt = m_brim.last_point();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -96,6 +96,7 @@ public:
|
||||||
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
|
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
|
||||||
const Transform3d& trafo() const { return m_trafo; }
|
const Transform3d& trafo() const { return m_trafo; }
|
||||||
const Points& copies() const { return m_copies; }
|
const Points& copies() const { return m_copies; }
|
||||||
|
const Point copy_center(size_t idx) const { return m_copies[idx] + m_copies_shift + Point(this->size.x() / 2, this->size.y() / 2); }
|
||||||
|
|
||||||
// since the object is aligned to origin, bounding box coincides with size
|
// since the object is aligned to origin, bounding box coincides with size
|
||||||
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
|
BoundingBox bounding_box() const { return BoundingBox(Point(0,0), to_2d(this->size)); }
|
||||||
|
|
|
@ -268,8 +268,7 @@ public:
|
||||||
std::string text;
|
std::string text;
|
||||||
// Bitmap of flags.
|
// Bitmap of flags.
|
||||||
enum FlagBits {
|
enum FlagBits {
|
||||||
DEFAULT,
|
DEFAULT = 0,
|
||||||
NO_RELOAD_SCENE = 0,
|
|
||||||
RELOAD_SCENE = 1 << 1,
|
RELOAD_SCENE = 1 << 1,
|
||||||
RELOAD_SLA_SUPPORT_POINTS = 1 << 2,
|
RELOAD_SLA_SUPPORT_POINTS = 1 << 2,
|
||||||
RELOAD_SLA_PREVIEW = 1 << 3,
|
RELOAD_SLA_PREVIEW = 1 << 3,
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
|
|
||||||
#include <tbb/task_scheduler_init.h>
|
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
#include <tbb/atomic.h>
|
#include <tbb/atomic.h>
|
||||||
|
|
||||||
|
@ -75,13 +74,9 @@ PrintBase::ApplyStatus PrintObject::set_copies(const Points &points)
|
||||||
{
|
{
|
||||||
// Order copies with a nearest-neighbor search.
|
// Order copies with a nearest-neighbor search.
|
||||||
std::vector<Point> copies;
|
std::vector<Point> copies;
|
||||||
{
|
copies.reserve(points.size());
|
||||||
std::vector<Points::size_type> ordered_copies;
|
for (const Point &pt : points)
|
||||||
Slic3r::Geometry::chained_path(points, ordered_copies);
|
copies.emplace_back(pt + m_copies_shift);
|
||||||
copies.reserve(ordered_copies.size());
|
|
||||||
for (size_t point_idx : ordered_copies)
|
|
||||||
copies.emplace_back(points[point_idx] + m_copies_shift);
|
|
||||||
}
|
|
||||||
// Invalidate and set copies.
|
// Invalidate and set copies.
|
||||||
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
|
PrintBase::ApplyStatus status = PrintBase::APPLY_STATUS_UNCHANGED;
|
||||||
if (copies != m_copies) {
|
if (copies != m_copies) {
|
||||||
|
|
394
src/libslic3r/ShortestPath.cpp
Normal file
394
src/libslic3r/ShortestPath.cpp
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
#if 0
|
||||||
|
#pragma optimize("", off)
|
||||||
|
#undef NDEBUG
|
||||||
|
#undef assert
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "clipper.hpp"
|
||||||
|
#include "ShortestPath.hpp"
|
||||||
|
#include "KDTreeIndirect.hpp"
|
||||||
|
#include "MutablePriorityQueue.hpp"
|
||||||
|
#include "Print.hpp"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// Return index and "reversed" flag.
|
||||||
|
// https://en.wikipedia.org/wiki/Multi-fragment_algorithm
|
||||||
|
// 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)
|
||||||
|
{
|
||||||
|
std::vector<std::pair<size_t, bool>> out;
|
||||||
|
|
||||||
|
if (num_segments == 0) {
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
else if (num_segments == 1)
|
||||||
|
{
|
||||||
|
// Just sort the end points so that the first point visited is closest to start_near.
|
||||||
|
out.emplace_back(0, start_near != nullptr &&
|
||||||
|
(end_point_func(0, true) - *start_near).template cast<double>().squaredNorm() < (end_point_func(0, false) - *start_near).template cast<double>().squaredNorm());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// End points of segments for the KD tree closest point search.
|
||||||
|
// A single end point is inserted into the search structure for loops, two end points are entered for open paths.
|
||||||
|
struct EndPoint {
|
||||||
|
EndPoint(const Vec2d &pos) : pos(pos) {}
|
||||||
|
Vec2d pos;
|
||||||
|
// Identifier of the chain, to which this end point belongs. Zero means unassigned.
|
||||||
|
size_t chain_id = 0;
|
||||||
|
// Link to the closest currently valid end point.
|
||||||
|
EndPoint *edge_out = nullptr;
|
||||||
|
// Distance to the next end point following the link.
|
||||||
|
// Zero value -> start of the final path.
|
||||||
|
double distance_out = std::numeric_limits<double>::max();
|
||||||
|
size_t heap_idx = std::numeric_limits<size_t>::max();
|
||||||
|
};
|
||||||
|
std::vector<EndPoint> end_points;
|
||||||
|
end_points.reserve(num_segments * 2);
|
||||||
|
for (size_t i = 0; i < num_segments; ++ i) {
|
||||||
|
end_points.emplace_back(end_point_func(i, true ).template cast<double>());
|
||||||
|
end_points.emplace_back(end_point_func(i, false).template cast<double>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the closest point KD tree over end points of segments.
|
||||||
|
auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; };
|
||||||
|
KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size());
|
||||||
|
|
||||||
|
// Helper to detect loops in already connected paths.
|
||||||
|
// Unique chain IDs are assigned to paths. If paths are connected, end points will not have their chain IDs updated, but the chain IDs
|
||||||
|
// will remember an "equivalent" chain ID, which is the lowest ID of all the IDs in the path, and the lowest ID is equivalent to itself.
|
||||||
|
class EquivalentChains {
|
||||||
|
public:
|
||||||
|
// Zero'th chain ID is invalid.
|
||||||
|
EquivalentChains(size_t reserve) { m_equivalent_with.reserve(reserve); m_equivalent_with.emplace_back(0); }
|
||||||
|
// Generate next equivalence class.
|
||||||
|
size_t next() {
|
||||||
|
m_equivalent_with.emplace_back(++ m_last_chain_id);
|
||||||
|
return m_last_chain_id;
|
||||||
|
}
|
||||||
|
// Get equivalence class for chain ID.
|
||||||
|
size_t operator()(size_t chain_id) {
|
||||||
|
if (chain_id != 0) {
|
||||||
|
for (size_t last = chain_id;;) {
|
||||||
|
size_t lower = m_equivalent_with[last];
|
||||||
|
if (lower == last) {
|
||||||
|
m_equivalent_with[chain_id] = lower;
|
||||||
|
chain_id = lower;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = lower;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain_id;
|
||||||
|
}
|
||||||
|
size_t merge(size_t chain_id1, size_t chain_id2) {
|
||||||
|
size_t chain_id = std::min((*this)(chain_id1), (*this)(chain_id2));
|
||||||
|
m_equivalent_with[chain_id1] = chain_id;
|
||||||
|
m_equivalent_with[chain_id2] = chain_id;
|
||||||
|
return chain_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
bool validate()
|
||||||
|
{
|
||||||
|
assert(m_last_chain_id >= 0);
|
||||||
|
assert(m_last_chain_id + 1 == m_equivalent_with.size());
|
||||||
|
for (size_t i = 0; i < m_equivalent_with.size(); ++ i) {
|
||||||
|
for (size_t last = i;;) {
|
||||||
|
size_t lower = m_equivalent_with[last];
|
||||||
|
assert(lower <= last);
|
||||||
|
if (lower == last)
|
||||||
|
break;
|
||||||
|
last = lower;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif /* NDEBUG */
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Unique chain ID assigned to chains of end points of segments.
|
||||||
|
size_t m_last_chain_id = 0;
|
||||||
|
std::vector<size_t> m_equivalent_with;
|
||||||
|
} equivalent_chain(num_segments);
|
||||||
|
|
||||||
|
// Find the first end point closest to start_near.
|
||||||
|
EndPoint *first_point = nullptr;
|
||||||
|
size_t first_point_idx = std::numeric_limits<size_t>::max();
|
||||||
|
if (start_near != nullptr) {
|
||||||
|
size_t idx = find_closest_point(kdtree, start_near->template cast<double>());
|
||||||
|
assert(idx < end_points.size());
|
||||||
|
first_point = &end_points[idx];
|
||||||
|
first_point->distance_out = 0.;
|
||||||
|
first_point->chain_id = equivalent_chain.next();
|
||||||
|
first_point_idx = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the closest point and distance to the end points.
|
||||||
|
for (EndPoint &end_point : end_points) {
|
||||||
|
assert(end_point.edge_out == nullptr);
|
||||||
|
if (&end_point != first_point) {
|
||||||
|
size_t this_idx = &end_point - &end_points.front();
|
||||||
|
// 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, end_point.pos,
|
||||||
|
[this_idx, first_point_idx](size_t idx){ return idx != first_point_idx && (idx ^ this_idx) > 1; });
|
||||||
|
assert(next_idx < end_points.size());
|
||||||
|
EndPoint &end_point2 = end_points[next_idx];
|
||||||
|
end_point.edge_out = &end_point2;
|
||||||
|
end_point.distance_out = (end_point2.pos - end_point.pos).squaredNorm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a heap of end points sorted by the lowest distance to the next valid point of a path.
|
||||||
|
auto queue = make_mutable_priority_queue<EndPoint*>(
|
||||||
|
[](EndPoint *ep, size_t idx){ ep->heap_idx = idx; },
|
||||||
|
[](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; });
|
||||||
|
queue.reserve(end_points.size() * 2 - 1);
|
||||||
|
for (EndPoint &ep : end_points)
|
||||||
|
if (first_point != &ep)
|
||||||
|
queue.push(&ep);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
auto validate_graph_and_queue = [&equivalent_chain, &end_points, &queue, first_point]() -> bool {
|
||||||
|
assert(equivalent_chain.validate());
|
||||||
|
for (EndPoint &ep : end_points) {
|
||||||
|
if (ep.heap_idx < queue.size()) {
|
||||||
|
// End point is on the heap.
|
||||||
|
assert(*(queue.cbegin() + ep.heap_idx) == &ep);
|
||||||
|
assert(ep.chain_id == 0);
|
||||||
|
} else {
|
||||||
|
// End point is NOT on the heap, therefore it is part of the output path.
|
||||||
|
assert(ep.heap_idx == std::numeric_limits<size_t>::max());
|
||||||
|
assert(ep.chain_id != 0);
|
||||||
|
if (&ep == first_point) {
|
||||||
|
assert(ep.edge_out == nullptr);
|
||||||
|
} else {
|
||||||
|
assert(ep.edge_out != nullptr);
|
||||||
|
// Detect loops.
|
||||||
|
for (EndPoint *pt = &ep; pt != nullptr;) {
|
||||||
|
// Out of queue. It is a final point.
|
||||||
|
assert(pt->heap_idx == std::numeric_limits<size_t>::max());
|
||||||
|
EndPoint *pt_other = &end_points[(pt - &end_points.front()) ^ 1];
|
||||||
|
if (pt_other->heap_idx < queue.size())
|
||||||
|
// The other side of this segment is undecided yet.
|
||||||
|
break;
|
||||||
|
pt = pt_other->edge_out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (EndPoint *ep : queue)
|
||||||
|
// Points in the queue are not connected yet.
|
||||||
|
assert(ep->chain_id == 0);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
#endif /* NDEBUG */
|
||||||
|
|
||||||
|
// Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops.
|
||||||
|
assert(num_segments >= 2);
|
||||||
|
for (int iter = int(num_segments) - 2;; -- iter) {
|
||||||
|
assert(validate_graph_and_queue());
|
||||||
|
// Take the first end point, for which the link points to the currently closest valid neighbor.
|
||||||
|
EndPoint &end_point1 = *queue.top();
|
||||||
|
assert(end_point1.edge_out != nullptr);
|
||||||
|
// No point on the queue may be connected yet.
|
||||||
|
assert(end_point1.chain_id == 0);
|
||||||
|
// Take the closest end point to the first end point,
|
||||||
|
EndPoint &end_point2 = *end_point1.edge_out;
|
||||||
|
bool valid = true;
|
||||||
|
size_t end_point1_other_chain_id = 0;
|
||||||
|
size_t end_point2_other_chain_id = 0;
|
||||||
|
if (end_point2.chain_id > 0) {
|
||||||
|
// The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one.
|
||||||
|
valid = false;
|
||||||
|
} else {
|
||||||
|
// End points of the opposite ends of the segments.
|
||||||
|
end_point1_other_chain_id = equivalent_chain(end_points[(&end_point1 - &end_points.front()) ^ 1].chain_id);
|
||||||
|
end_point2_other_chain_id = equivalent_chain(end_points[(&end_point2 - &end_points.front()) ^ 1].chain_id);
|
||||||
|
if (end_point1_other_chain_id == end_point2_other_chain_id && end_point1_other_chain_id != 0)
|
||||||
|
// This edge forms a loop. Update end_point1 and try another one.
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (valid) {
|
||||||
|
// Remove the first and second point from the queue.
|
||||||
|
queue.pop();
|
||||||
|
queue.remove(end_point2.heap_idx);
|
||||||
|
assert(end_point1.edge_out = &end_point2);
|
||||||
|
end_point2.edge_out = &end_point1;
|
||||||
|
end_point2.distance_out = end_point1.distance_out;
|
||||||
|
// Assign chain IDs to the newly connected end points, set equivalent_chain if two chains were merged.
|
||||||
|
size_t chain_id =
|
||||||
|
(end_point1_other_chain_id == 0) ?
|
||||||
|
((end_point2_other_chain_id == 0) ? equivalent_chain.next() : end_point2_other_chain_id) :
|
||||||
|
((end_point2_other_chain_id == 0) ? end_point1_other_chain_id :
|
||||||
|
(end_point1_other_chain_id == end_point2_other_chain_id) ?
|
||||||
|
end_point1_other_chain_id :
|
||||||
|
equivalent_chain.merge(end_point1_other_chain_id, end_point2_other_chain_id));
|
||||||
|
end_point1.chain_id = chain_id;
|
||||||
|
end_point2.chain_id = chain_id;
|
||||||
|
assert(validate_graph_and_queue());
|
||||||
|
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)
|
||||||
|
first_point = queue.top();
|
||||||
|
while (! queue.empty()) {
|
||||||
|
queue.top()->edge_out = nullptr;
|
||||||
|
queue.pop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This edge forms a loop. Update end_point1 and try another one.
|
||||||
|
++ iter;
|
||||||
|
end_point1.edge_out = nullptr;
|
||||||
|
// Update edge_out and distance.
|
||||||
|
size_t this_idx = &end_point1 - &end_points.front();
|
||||||
|
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda).
|
||||||
|
size_t next_idx = find_closest_point(kdtree, end_point1.pos, [&end_points, &equivalent_chain, this_idx](size_t idx) {
|
||||||
|
assert(end_points[this_idx].edge_out == nullptr);
|
||||||
|
assert(end_points[this_idx].chain_id == 0);
|
||||||
|
if ((idx ^ this_idx) <= 1 || end_points[idx].chain_id != 0)
|
||||||
|
// Points of the same segment shall not be connected,
|
||||||
|
// cannot connect to an already connected point (ideally those would be removed from the KD tree, but the update is difficult).
|
||||||
|
return false;
|
||||||
|
size_t chain1 = equivalent_chain(end_points[this_idx ^ 1].chain_id);
|
||||||
|
size_t chain2 = equivalent_chain(end_points[idx ^ 1].chain_id);
|
||||||
|
return chain1 != chain2 || chain1 == 0;
|
||||||
|
});
|
||||||
|
assert(next_idx < end_points.size());
|
||||||
|
end_point1.edge_out = &end_points[next_idx];
|
||||||
|
end_point1.distance_out = (end_points[next_idx].pos - end_point1.pos).squaredNorm();
|
||||||
|
// Update position of this end point in the queue based on the distance calculated at the line above.
|
||||||
|
queue.update(end_point1.heap_idx);
|
||||||
|
//FIXME Remove the other end point from the KD tree.
|
||||||
|
// As the KD tree update is expensive, do it only after some larger number of points is removed from the queue.
|
||||||
|
assert(validate_graph_and_queue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(queue.empty());
|
||||||
|
|
||||||
|
// Now interconnect pairs of segments into a chain.
|
||||||
|
assert(first_point != nullptr);
|
||||||
|
do {
|
||||||
|
assert(out.size() < num_segments);
|
||||||
|
size_t first_point_id = first_point - &end_points.front();
|
||||||
|
size_t segment_id = first_point_id >> 1;
|
||||||
|
EndPoint *second_point = &end_points[first_point_id ^ 1];
|
||||||
|
out.emplace_back(segment_id, (first_point_id & 1) != 0);
|
||||||
|
first_point = second_point->edge_out;
|
||||||
|
} while (first_point != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(out.size() == num_segments);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
for (size_t i = 0; i < entities.size(); ++ i) {
|
||||||
|
ExtrusionEntity *ee = entities[i];
|
||||||
|
if (ee->is_loop())
|
||||||
|
// Ignore reversals for loops, as the start point equals the end point.
|
||||||
|
out[i].second = false;
|
||||||
|
// Is can_reverse() respected by the reversals?
|
||||||
|
assert(entities[i]->can_reverse() || ! out[i].second);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const std::vector<std::pair<size_t, bool>> &chain)
|
||||||
|
{
|
||||||
|
assert(entities.size() == chain.size());
|
||||||
|
std::vector<ExtrusionEntity*> out;
|
||||||
|
out.reserve(entities.size());
|
||||||
|
for (const std::pair<size_t, bool> &idx : chain) {
|
||||||
|
assert(entities[idx.first] != nullptr);
|
||||||
|
out.emplace_back(entities[idx.first]);
|
||||||
|
if (idx.second)
|
||||||
|
out.back()->reverse();
|
||||||
|
}
|
||||||
|
entities.swap(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chain_and_reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
|
||||||
|
{
|
||||||
|
reorder_extrusion_entities(entities, chain_extrusion_entities(entities, 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<size_t> out;
|
||||||
|
out.reserve(ordered.size());
|
||||||
|
for (auto &segment_and_reversal : ordered)
|
||||||
|
out.emplace_back(segment_and_reversal.first);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Polylines chain_infill_polylines(Polylines &&polylines)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
Polylines out;
|
||||||
|
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)
|
||||||
|
out.back().reverse();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
T out;
|
||||||
|
out.reserve(items.size());
|
||||||
|
for (auto &segment_and_reversal : ordered)
|
||||||
|
out.emplace_back(items[segment_and_reversal.first]);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const ClipperLib::PolyNodes &items)
|
||||||
|
{
|
||||||
|
return chain_path_items(points, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print)
|
||||||
|
{
|
||||||
|
// Order objects using a nearest neighbor search.
|
||||||
|
Points object_reference_points;
|
||||||
|
std::vector<std::pair<size_t, size_t>> instances;
|
||||||
|
for (size_t i = 0; i < print.objects().size(); ++ i) {
|
||||||
|
const PrintObject &object = *print.objects()[i];
|
||||||
|
for (size_t j = 0; j < object.copies().size(); ++ j) {
|
||||||
|
object_reference_points.emplace_back(object.copy_center(j));
|
||||||
|
instances.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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, size_t>> out;
|
||||||
|
out.reserve(instances.size());
|
||||||
|
for (auto &segment_and_reversal : ordered)
|
||||||
|
out.emplace_back(instances[segment_and_reversal.first]);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
33
src/libslic3r/ShortestPath.hpp
Normal file
33
src/libslic3r/ShortestPath.hpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef slic3r_ShortestPath_hpp_
|
||||||
|
#define slic3r_ShortestPath_hpp_
|
||||||
|
|
||||||
|
#include "libslic3r.h"
|
||||||
|
#include "ExtrusionEntity.hpp"
|
||||||
|
#include "Point.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ClipperLib { class PolyNode; }
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
std::vector<size_t> chain_points(const Points &points, Point *start_near = nullptr);
|
||||||
|
|
||||||
|
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near = nullptr);
|
||||||
|
void reorder_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const 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<ClipperLib::PolyNode*> chain_clipper_polynodes(const Points &points, const std::vector<ClipperLib::PolyNode*> &items);
|
||||||
|
|
||||||
|
// Chain instances of print objects by an approximate shortest path.
|
||||||
|
// Returns pairs of PrintObject idx and instance of that PrintObject.
|
||||||
|
class Print;
|
||||||
|
std::vector<std::pair<size_t, size_t>> chain_print_object_instances(const Print &print);
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif /* slic3r_ShortestPath_hpp_ */
|
|
@ -2125,7 +2125,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
|
||||||
}
|
}
|
||||||
// $layer->slices contains the full shape of layer, thus including
|
// $layer->slices contains the full shape of layer, thus including
|
||||||
// perimeter's width. $support contains the full shape of support
|
// perimeter's width. $support contains the full shape of support
|
||||||
// material, thus including the width of its foremost extrusion.
|
// material, thus including the width of its foremost extrusion.
|
||||||
// We leave a gap equal to a full extrusion width.
|
// We leave a gap equal to a full extrusion width.
|
||||||
support_layer.polygons = diff(support_layer.polygons, polygons_trimming);
|
support_layer.polygons = diff(support_layer.polygons, polygons_trimming);
|
||||||
}
|
}
|
||||||
|
@ -2934,20 +2934,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||||
// Prepare fillers.
|
// Prepare fillers.
|
||||||
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
SupportMaterialPattern support_pattern = m_object_config->support_material_pattern;
|
||||||
bool with_sheath = m_object_config->support_material_with_sheath;
|
bool with_sheath = m_object_config->support_material_with_sheath;
|
||||||
InfillPattern infill_pattern;
|
InfillPattern infill_pattern = (support_pattern == smpHoneycomb ? ipHoneycomb : ipRectilinear);
|
||||||
std::vector<float> angles;
|
std::vector<float> angles;
|
||||||
angles.push_back(base_angle);
|
angles.push_back(base_angle);
|
||||||
switch (support_pattern) {
|
|
||||||
case smpRectilinearGrid:
|
if (support_pattern == smpRectilinearGrid)
|
||||||
angles.push_back(interface_angle);
|
angles.push_back(interface_angle);
|
||||||
// fall through
|
|
||||||
case smpRectilinear:
|
|
||||||
infill_pattern = ipRectilinear;
|
|
||||||
break;
|
|
||||||
case smpHoneycomb:
|
|
||||||
infill_pattern = ipHoneycomb;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
|
BoundingBox bbox_object(Point(-scale_(1.), -scale_(1.0)), Point(scale_(1.), scale_(1.)));
|
||||||
|
|
||||||
// const coordf_t link_max_length_factor = 3.;
|
// const coordf_t link_max_length_factor = 3.;
|
||||||
|
@ -3217,7 +3210,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
|
||||||
density = 0.5f;
|
density = 0.5f;
|
||||||
flow = m_first_layer_flow;
|
flow = m_first_layer_flow;
|
||||||
// use the proper spacing for first layer as we don't need to align
|
// use the proper spacing for first layer as we don't need to align
|
||||||
// its pattern to the other layers
|
// its pattern to the other layers
|
||||||
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
|
||||||
filler->spacing = flow.spacing();
|
filler->spacing = flow.spacing();
|
||||||
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
|
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
|
||||||
|
|
|
@ -3413,11 +3413,10 @@ void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool foc
|
||||||
m_sidebar_field = focus_on ? opt_key : "";
|
m_sidebar_field = focus_on ? opt_key : "";
|
||||||
|
|
||||||
if (!m_sidebar_field.empty())
|
if (!m_sidebar_field.empty())
|
||||||
{
|
|
||||||
m_gizmos.reset_all_states();
|
m_gizmos.reset_all_states();
|
||||||
|
|
||||||
m_dirty = true;
|
m_dirty = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type)
|
void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type)
|
||||||
{
|
{
|
||||||
|
|
|
@ -753,9 +753,9 @@ void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& vol
|
||||||
}
|
}
|
||||||
|
|
||||||
select_items(items);
|
select_items(items);
|
||||||
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#endif //no __WXOSX__ //__WXMSW__
|
//#endif //no __WXOSX__ //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs)
|
void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs)
|
||||||
|
@ -773,9 +773,9 @@ void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs)
|
||||||
wxGetApp().plater()->changed_objects(object_idxs);
|
wxGetApp().plater()->changed_objects(object_idxs);
|
||||||
|
|
||||||
select_items(items);
|
select_items(items);
|
||||||
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#endif //no __WXOSX__ //__WXMSW__
|
//#endif //no __WXOSX__ //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __WXOSX__
|
#ifdef __WXOSX__
|
||||||
|
@ -815,7 +815,9 @@ void ObjectList::list_manipulation(bool evt_context_menu/* = false*/)
|
||||||
if (col == nullptr) {
|
if (col == nullptr) {
|
||||||
if (wxOSX)
|
if (wxOSX)
|
||||||
UnselectAll();
|
UnselectAll();
|
||||||
else
|
else if (!evt_context_menu)
|
||||||
|
// Case, when last item was deleted and under GTK was called wxEVT_DATAVIEW_SELECTION_CHANGED,
|
||||||
|
// which invoked next list_manipulation(false)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1720,9 +1722,9 @@ void ObjectList::load_subobject(ModelVolumeType type)
|
||||||
if (sel_item)
|
if (sel_item)
|
||||||
select_item(sel_item);
|
select_item(sel_item);
|
||||||
|
|
||||||
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#endif //no __WXOSX__ //__WXMSW__
|
//#endif //no __WXOSX__ //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::load_part( ModelObject* model_object,
|
void ObjectList::load_part( ModelObject* model_object,
|
||||||
|
@ -1858,9 +1860,9 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||||
const auto object_item = m_objects_model->GetTopParent(GetSelection());
|
const auto object_item = m_objects_model->GetTopParent(GetSelection());
|
||||||
select_item(m_objects_model->AddVolumeChild(object_item, name, type,
|
select_item(m_objects_model->AddVolumeChild(object_item, name, type,
|
||||||
new_volume->get_mesh_errors_count()>0));
|
new_volume->get_mesh_errors_count()>0));
|
||||||
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
//#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
|
||||||
selection_changed();
|
selection_changed();
|
||||||
#endif //no __WXOSX__ //__WXMSW__
|
//#endif //no __WXOSX__ //__WXMSW__
|
||||||
}
|
}
|
||||||
|
|
||||||
void ObjectList::load_shape_object(const std::string& type_name)
|
void ObjectList::load_shape_object(const std::string& type_name)
|
||||||
|
@ -1899,6 +1901,9 @@ void ObjectList::load_shape_object(const std::string& type_name)
|
||||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||||
new_object->invalidate_bounding_box();
|
new_object->invalidate_bounding_box();
|
||||||
|
|
||||||
|
new_object->center_around_origin();
|
||||||
|
new_object->ensure_on_bed();
|
||||||
|
|
||||||
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
|
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
|
||||||
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
|
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
|
||||||
|
|
||||||
|
|
|
@ -3346,7 +3346,7 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||||
this->statusbar()->set_progress(evt.status.percent);
|
this->statusbar()->set_progress(evt.status.percent);
|
||||||
this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…"));
|
this->statusbar()->set_status_text(_(evt.status.text) + wxString::FromUTF8("…"));
|
||||||
}
|
}
|
||||||
if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) {
|
if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE | PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) {
|
||||||
switch (this->printer_technology) {
|
switch (this->printer_technology) {
|
||||||
case ptFFF:
|
case ptFFF:
|
||||||
this->update_fff_scene();
|
this->update_fff_scene();
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
%{
|
%{
|
||||||
#include <xsinit.h>
|
#include <xsinit.h>
|
||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
|
#include "libslic3r/ShortestPath.hpp"
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@ std::vector<Points::size_type>
|
||||||
chained_path(points)
|
chained_path(points)
|
||||||
Points points
|
Points points
|
||||||
CODE:
|
CODE:
|
||||||
Slic3r::Geometry::chained_path(points, RETVAL);
|
RETVAL = chain_points(points);
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
RETVAL
|
RETVAL
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ chained_path_from(points, start_from)
|
||||||
Points points
|
Points points
|
||||||
Point* start_from
|
Point* start_from
|
||||||
CODE:
|
CODE:
|
||||||
Slic3r::Geometry::chained_path(points, RETVAL, *start_from);
|
RETVAL = chain_points(points, start_from);
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
RETVAL
|
RETVAL
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue