Merge branch 'dev' of https://github.com/prusa3d/PrusaSlicer into et_reload_from_disk

This commit is contained in:
Enrico Turri 2019-09-16 08:26:06 +02:00
commit ffe62d543d
22 changed files with 846 additions and 601 deletions

View file

@ -4,4 +4,6 @@ cmake_minimum_required(VERSION 2.6)
add_library(clipper STATIC
clipper.cpp
clipper.hpp
clipper_z.cpp
clipper_z.hpp
)

View file

@ -51,7 +51,11 @@
#include <Shiny/Shiny.h>
#include <libslic3r/Int128.hpp>
#ifdef use_xyz
namespace ClipperLib_Z {
#else /* use_xyz */
namespace ClipperLib {
#endif /* use_xyz */
static double const pi = 3.141592653589793238;
static double const two_pi = pi *2;
@ -1616,7 +1620,7 @@ void Clipper::SetZ(IntPoint& pt, TEdge& e1, TEdge& e2)
else if (pt == e1.Top) pt.Z = e1.Top.Z;
else if (pt == e2.Bot) pt.Z = e2.Bot.Z;
else if (pt == e2.Top) pt.Z = e2.Top.Z;
else (*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
else m_ZFill(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
}
//------------------------------------------------------------------------------
#endif

View file

@ -35,6 +35,7 @@
#define clipper_hpp
#include <inttypes.h>
#include <functional>
#define CLIPPER_VERSION "6.2.6"
@ -56,7 +57,11 @@
#include <functional>
#include <queue>
#ifdef use_xyz
namespace ClipperLib_Z {
#else /* use_xyz */
namespace ClipperLib {
#endif /* use_xyz */
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
enum PolyType { ptSubject, ptClip };
@ -114,7 +119,7 @@ struct DoublePoint
//------------------------------------------------------------------------------
#ifdef use_xyz
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
typedef std::function<void(const IntPoint& e1bot, const IntPoint& e1top, const IntPoint& e2bot, const IntPoint& e2top, IntPoint& pt)> ZFillCallback;
#endif
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};

View file

@ -0,0 +1,7 @@
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
// Enable the Z coordinate support.
#define use_xyz
// and let it compile
#include "clipper.cpp"

18
src/clipper/clipper_z.hpp Normal file
View file

@ -0,0 +1,18 @@
// Hackish wrapper around the ClipperLib library to compile the Clipper library with the Z support.
#ifndef clipper_z_hpp
#ifdef clipper_hpp
#error "You should include the clipper_z.hpp first"
#endif
#define clipper_z_hpp
// Enable the Z coordinate support.
#define use_xyz
#include "clipper.hpp"
#undef clipper_hpp
#undef use_xyz
#endif clipper_z_hpp

View file

@ -33,10 +33,16 @@ protected:
// then it should be removed from the list
auto it = c.begin();
while (it != c.end() && !stopcond_()) {
Placer p{bin};
p.configure(pcfg);
// WARNING: The copy of itm needs to be created before Placer.
// Placer is working with references and its destructor still
// manipulates the item this is why the order of stack creation
// matters here.
const Item& itm = *it;
Item cpy{itm};
Placer p{bin};
p.configure(pcfg);
if (!p.pack(cpy)) it = c.erase(it);
else it++;
}

View file

@ -12,44 +12,35 @@
namespace Slic3r {
void
ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(intersection_pl(this->polyline, collection), retval);
}
void
ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const
{
this->_inflate_collection(diff_pl(this->polyline, collection), retval);
}
void
ExtrusionPath::clip_end(double distance)
void ExtrusionPath::clip_end(double distance)
{
this->polyline.clip_end(distance);
}
void
ExtrusionPath::simplify(double tolerance)
void ExtrusionPath::simplify(double tolerance)
{
this->polyline.simplify(tolerance);
}
double
ExtrusionPath::length() const
double ExtrusionPath::length() const
{
return this->polyline.length();
}
void
ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
void ExtrusionPath::_inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const
{
for (Polylines::const_iterator it = polylines.begin(); it != polylines.end(); ++it) {
ExtrusionPath* path = this->clone();
path->polyline = *it;
collection->entities.push_back(path);
}
for (const Polyline &polyline : polylines)
collection->entities.emplace_back(new ExtrusionPath(polyline, *this));
}
void ExtrusionPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
@ -67,36 +58,36 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
void ExtrusionMultiPath::reverse()
{
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->reverse();
for (ExtrusionPath &path : this->paths)
path.reverse();
std::reverse(this->paths.begin(), this->paths.end());
}
double ExtrusionMultiPath::length() const
{
double len = 0;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
len += path->polyline.length();
for (const ExtrusionPath &path : this->paths)
len += path.polyline.length();
return len;
}
void ExtrusionMultiPath::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_width(out, scaled_epsilon);
for (const ExtrusionPath &path : this->paths)
path.polygons_covered_by_width(out, scaled_epsilon);
}
void ExtrusionMultiPath::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_spacing(out, scaled_epsilon);
for (const ExtrusionPath &path : this->paths)
path.polygons_covered_by_spacing(out, scaled_epsilon);
}
double ExtrusionMultiPath::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm);
for (const ExtrusionPath &path : this->paths)
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
return min_mm3_per_mm;
}
@ -121,52 +112,46 @@ Polyline ExtrusionMultiPath::as_polyline() const
return out;
}
bool
ExtrusionLoop::make_clockwise()
bool ExtrusionLoop::make_clockwise()
{
bool was_ccw = this->polygon().is_counter_clockwise();
if (was_ccw) this->reverse();
return was_ccw;
}
bool
ExtrusionLoop::make_counter_clockwise()
bool ExtrusionLoop::make_counter_clockwise()
{
bool was_cw = this->polygon().is_clockwise();
if (was_cw) this->reverse();
return was_cw;
}
void
ExtrusionLoop::reverse()
void ExtrusionLoop::reverse()
{
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->reverse();
for (ExtrusionPath &path : this->paths)
path.reverse();
std::reverse(this->paths.begin(), this->paths.end());
}
Polygon
ExtrusionLoop::polygon() const
Polygon ExtrusionLoop::polygon() const
{
Polygon polygon;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
for (const ExtrusionPath &path : this->paths) {
// for each polyline, append all points except the last one (because it coincides with the first one of the next polyline)
polygon.points.insert(polygon.points.end(), path->polyline.points.begin(), path->polyline.points.end()-1);
polygon.points.insert(polygon.points.end(), path.polyline.points.begin(), path.polyline.points.end()-1);
}
return polygon;
}
double
ExtrusionLoop::length() const
double ExtrusionLoop::length() const
{
double len = 0;
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
len += path->polyline.length();
for (const ExtrusionPath &path : this->paths)
len += path.polyline.length();
return len;
}
bool
ExtrusionLoop::split_at_vertex(const Point &point)
bool ExtrusionLoop::split_at_vertex(const Point &point)
{
for (ExtrusionPaths::iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
int idx = path->polyline.find_point(point);
@ -220,18 +205,18 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
Point p_non_overhang;
size_t path_idx_non_overhang = 0;
double min_non_overhang = std::numeric_limits<double>::max();
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
Point p_tmp = point.projection_onto(path->polyline);
for (const ExtrusionPath &path : this->paths) {
Point p_tmp = point.projection_onto(path.polyline);
double dist = (p_tmp - point).cast<double>().norm();
if (dist < min) {
p = p_tmp;
min = dist;
path_idx = path - this->paths.begin();
path_idx = &path - &this->paths.front();
}
if (prefer_non_overhang && ! is_bridge(path->role()) && dist < min_non_overhang) {
if (prefer_non_overhang && ! is_bridge(path.role()) && dist < min_non_overhang) {
p_non_overhang = p_tmp;
min_non_overhang = dist;
path_idx_non_overhang = path - this->paths.begin();
path_idx_non_overhang = &path - &this->paths.front();
}
}
if (prefer_non_overhang && min_non_overhang != std::numeric_limits<double>::max()) {
@ -267,8 +252,7 @@ void ExtrusionLoop::split_at(const Point &point, bool prefer_non_overhang)
this->split_at_vertex(p);
}
void
ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
void ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
{
*paths = this->paths;
@ -285,15 +269,14 @@ ExtrusionLoop::clip_end(double distance, ExtrusionPaths* paths) const
}
}
bool
ExtrusionLoop::has_overhang_point(const Point &point) const
bool ExtrusionLoop::has_overhang_point(const Point &point) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path) {
int pos = path->polyline.find_point(point);
for (const ExtrusionPath &path : this->paths) {
int pos = path.polyline.find_point(point);
if (pos != -1) {
// point belongs to this path
// we consider it overhang only if it's not an endpoint
return (is_bridge(path->role()) && pos > 0 && pos != (int)(path->polyline.points.size())-1);
return (is_bridge(path.role()) && pos > 0 && pos != (int)(path.polyline.points.size())-1);
}
}
return false;
@ -301,22 +284,21 @@ ExtrusionLoop::has_overhang_point(const Point &point) const
void ExtrusionLoop::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_width(out, scaled_epsilon);
for (const ExtrusionPath &path : this->paths)
path.polygons_covered_by_width(out, scaled_epsilon);
}
void ExtrusionLoop::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
path->polygons_covered_by_spacing(out, scaled_epsilon);
for (const ExtrusionPath &path : this->paths)
path.polygons_covered_by_spacing(out, scaled_epsilon);
}
double
ExtrusionLoop::min_mm3_per_mm() const
double ExtrusionLoop::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (ExtrusionPaths::const_iterator path = this->paths.begin(); path != this->paths.end(); ++path)
min_mm3_per_mm = std::min(min_mm3_per_mm, path->mm3_per_mm);
for (const ExtrusionPath &path : this->paths)
min_mm3_per_mm = std::min(min_mm3_per_mm, path.mm3_per_mm);
return min_mm3_per_mm;
}
@ -344,15 +326,4 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role)
return "";
}
//std::string ExtrusionLoop::role_to_string(ExtrusionLoopRole role)
//{
// switch (role) {
// case elrDefault : return "elrDefault";
// case elrContourInternalPerimeter: return "elrContourInternalPerimeter";
// case elrSkirt : return "elrSkirt";
// default : assert(false);
// }
//};
}

View file

@ -75,6 +75,8 @@ public:
virtual bool is_loop() const { return false; }
virtual bool can_reverse() const { return true; }
virtual ExtrusionEntity* clone() const = 0;
// Create a new object, initialize it with this object using the move semantics.
virtual ExtrusionEntity* clone_move() = 0;
virtual ~ExtrusionEntity() {}
virtual void reverse() = 0;
virtual Point first_point() const = 0;
@ -111,25 +113,29 @@ public:
double mm3_per_mm;
// Width of the extrusion, used for visualization purposes.
float width;
// Height of the extrusion, used for visualization purposed.
// Height of the extrusion, used for visualization purposes.
float height;
// Feedrate of the extrusion, used for visualization purposed.
// Feedrate of the extrusion, used for visualization purposes.
float feedrate;
// Id of the extruder, used for visualization purposed.
// Id of the extruder, used for visualization purposes.
unsigned int extruder_id;
// Id of the color, used for visualization purposed in the color printing case.
// Id of the color, used for visualization purposes in the color printing case.
unsigned int cp_color_id;
ExtrusionPath(ExtrusionRole role) : mm3_per_mm(-1), width(-1), height(-1), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {}
ExtrusionPath(ExtrusionRole role, double mm3_per_mm, float width, float height) : mm3_per_mm(mm3_per_mm), width(width), height(height), feedrate(0.0f), extruder_id(0), cp_color_id(0), m_role(role) {}
ExtrusionPath(const ExtrusionPath &rhs) : polyline(rhs.polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
ExtrusionPath(const Polyline &polyline, const ExtrusionPath &rhs) : polyline(polyline), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
ExtrusionPath(ExtrusionPath &&rhs) : polyline(std::move(rhs.polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
ExtrusionPath(Polyline &&polyline, const ExtrusionPath &rhs) : polyline(std::move(polyline)), mm3_per_mm(rhs.mm3_per_mm), width(rhs.width), height(rhs.height), feedrate(rhs.feedrate), extruder_id(rhs.extruder_id), cp_color_id(rhs.cp_color_id), m_role(rhs.m_role) {}
// ExtrusionPath(ExtrusionRole role, const Flow &flow) : m_role(role), mm3_per_mm(flow.mm3_per_mm()), width(flow.width), height(flow.height), feedrate(0.0f), extruder_id(0) {};
ExtrusionPath& operator=(const ExtrusionPath &rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = rhs.polyline; return *this; }
ExtrusionPath& operator=(ExtrusionPath &&rhs) { m_role = rhs.m_role; this->mm3_per_mm = rhs.mm3_per_mm; this->width = rhs.width; this->height = rhs.height; this->feedrate = rhs.feedrate; this->extruder_id = rhs.extruder_id; this->cp_color_id = rhs.cp_color_id; this->polyline = std::move(rhs.polyline); return *this; }
ExtrusionPath* clone() const override { return new ExtrusionPath (*this); }
ExtrusionEntity* clone() const override { return new ExtrusionPath(*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionPath(std::move(*this)); }
void reverse() override { this->polyline.reverse(); }
Point first_point() const override { return this->polyline.points.front(); }
Point last_point() const override { return this->polyline.points.back(); }
@ -188,7 +194,9 @@ public:
bool is_loop() const override { return false; }
bool can_reverse() const override { return true; }
ExtrusionMultiPath* clone() const override { return new ExtrusionMultiPath(*this); }
ExtrusionEntity* clone() const override { return new ExtrusionMultiPath(*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionMultiPath(std::move(*this)); }
void reverse() override;
Point first_point() const override { return this->paths.front().polyline.points.front(); }
Point last_point() const override { return this->paths.back().polyline.points.back(); }
@ -227,7 +235,9 @@ public:
{ this->paths.emplace_back(std::move(path)); }
bool is_loop() const override{ return true; }
bool can_reverse() const override { return false; }
ExtrusionLoop* clone() const override{ return new ExtrusionLoop (*this); }
ExtrusionEntity* clone() const override{ return new ExtrusionLoop (*this); }
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionLoop(std::move(*this)); }
bool make_clockwise();
bool make_counter_clockwise();
void reverse() override;

View file

@ -21,8 +21,7 @@ ExtrusionEntityCollection& ExtrusionEntityCollection::operator= (const Extrusion
return *this;
}
void
ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
void ExtrusionEntityCollection::swap(ExtrusionEntityCollection &c)
{
std::swap(this->entities, c.entities);
std::swap(this->orig_indices, c.orig_indices);
@ -39,15 +38,14 @@ void ExtrusionEntityCollection::clear()
ExtrusionEntityCollection::operator ExtrusionPaths() const
{
ExtrusionPaths paths;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if (const ExtrusionPath* path = dynamic_cast<const ExtrusionPath*>(*it))
for (const ExtrusionEntity *ptr : this->entities) {
if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ptr))
paths.push_back(*path);
}
return paths;
}
ExtrusionEntityCollection*
ExtrusionEntityCollection::clone() const
ExtrusionEntity* ExtrusionEntityCollection::clone() const
{
ExtrusionEntityCollection* coll = new ExtrusionEntityCollection(*this);
for (size_t i = 0; i < coll->entities.size(); ++i)
@ -55,41 +53,36 @@ ExtrusionEntityCollection::clone() const
return coll;
}
void
ExtrusionEntityCollection::reverse()
void ExtrusionEntityCollection::reverse()
{
for (ExtrusionEntitiesPtr::iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
for (ExtrusionEntity *ptr : this->entities)
// Don't reverse it if it's a loop, as it doesn't change anything in terms of elements ordering
// and caller might rely on winding order
if (!(*it)->is_loop()) (*it)->reverse();
}
if (! ptr->is_loop())
ptr->reverse();
std::reverse(this->entities.begin(), this->entities.end());
}
void
ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
void ExtrusionEntityCollection::replace(size_t i, const ExtrusionEntity &entity)
{
delete this->entities[i];
this->entities[i] = entity.clone();
}
void
ExtrusionEntityCollection::remove(size_t i)
void ExtrusionEntityCollection::remove(size_t i)
{
delete this->entities[i];
this->entities.erase(this->entities.begin() + i);
}
ExtrusionEntityCollection
ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
ExtrusionEntityCollection ExtrusionEntityCollection::chained_path(bool no_reverse, ExtrusionRole role) const
{
ExtrusionEntityCollection coll;
this->chained_path(&coll, no_reverse, role);
return coll;
}
void
ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
void ExtrusionEntityCollection::chained_path(ExtrusionEntityCollection* retval, bool no_reverse, ExtrusionRole role, std::vector<size_t>* orig_indices) const
{
if (this->entities.empty()) return;
this->chained_path_from(this->entities.front()->first_point(), retval, no_reverse, role, orig_indices);
@ -108,6 +101,7 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
*retval = *this;
return;
}
retval->entities.reserve(this->entities.size());
retval->orig_indices.reserve(this->entities.size());
@ -115,10 +109,10 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
std::map<ExtrusionEntity*,size_t> indices_map;
ExtrusionEntitiesPtr my_paths;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
for (ExtrusionEntity * const &entity_src : this->entities) {
if (role != erMixed) {
// The caller wants only paths with a specific extrusion role.
auto role2 = (*it)->role();
auto role2 = entity_src->role();
if (role != role2) {
// This extrusion entity does not match the role asked.
assert(role2 != erMixed);
@ -126,32 +120,30 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
}
}
ExtrusionEntity* entity = (*it)->clone();
ExtrusionEntity *entity = entity_src->clone();
my_paths.push_back(entity);
if (orig_indices != NULL) indices_map[entity] = it - this->entities.begin();
if (orig_indices != nullptr)
indices_map[entity] = &entity_src - &this->entities.front();
}
Points endpoints;
for (ExtrusionEntitiesPtr::iterator it = my_paths.begin(); it != my_paths.end(); ++it) {
endpoints.push_back((*it)->first_point());
if (no_reverse || !(*it)->can_reverse()) {
endpoints.push_back((*it)->first_point());
} else {
endpoints.push_back((*it)->last_point());
}
for (const ExtrusionEntity *entity : my_paths) {
endpoints.push_back(entity->first_point());
endpoints.push_back((no_reverse || ! entity->can_reverse()) ?
entity->first_point() : entity->last_point());
}
while (!my_paths.empty()) {
while (! my_paths.empty()) {
// find nearest point
int start_index = start_near.nearest_point_index(endpoints);
int path_index = start_index/2;
ExtrusionEntity* entity = my_paths.at(path_index);
// never reverse loops, since it's pointless for chained path and callers might depend on orientation
if (start_index % 2 && !no_reverse && entity->can_reverse()) {
if (start_index % 2 && !no_reverse && entity->can_reverse())
entity->reverse();
}
retval->entities.push_back(my_paths.at(path_index));
if (orig_indices != NULL) orig_indices->push_back(indices_map[entity]);
if (orig_indices != nullptr)
orig_indices->push_back(indices_map[entity]);
my_paths.erase(my_paths.begin() + path_index);
endpoints.erase(endpoints.begin() + 2*path_index, endpoints.begin() + 2*path_index + 2);
start_near = retval->entities.back()->last_point();
@ -160,60 +152,50 @@ void ExtrusionEntityCollection::chained_path_from(Point start_near, ExtrusionEnt
void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
(*it)->polygons_covered_by_width(out, scaled_epsilon);
for (const ExtrusionEntity *entity : this->entities)
entity->polygons_covered_by_width(out, scaled_epsilon);
}
void ExtrusionEntityCollection::polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
(*it)->polygons_covered_by_spacing(out, scaled_epsilon);
for (const ExtrusionEntity *entity : this->entities)
entity->polygons_covered_by_spacing(out, scaled_epsilon);
}
/* Recursively count paths and loops contained in this collection */
size_t
ExtrusionEntityCollection::items_count() const
// Recursively count paths and loops contained in this collection.
size_t ExtrusionEntityCollection::items_count() const
{
size_t count = 0;
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if ((*it)->is_collection()) {
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
count += collection->items_count();
} else {
++count;
}
}
for (const ExtrusionEntity *entity : this->entities)
if (entity->is_collection())
count += static_cast<const ExtrusionEntityCollection*>(entity)->items_count();
else
++ count;
return count;
}
/* Returns a single vector of pointers to all non-collection items contained in this one */
void
ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
// Returns a single vector of pointers to all non-collection items contained in this one.
void ExtrusionEntityCollection::flatten(ExtrusionEntityCollection* retval) const
{
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it) {
if ((*it)->is_collection()) {
ExtrusionEntityCollection* collection = dynamic_cast<ExtrusionEntityCollection*>(*it);
retval->append(collection->flatten().entities);
} else {
retval->append(**it);
}
}
for (const ExtrusionEntity *entity : this->entities)
if (entity->is_collection())
retval->append(static_cast<const ExtrusionEntityCollection*>(entity)->flatten().entities);
else
retval->append(*entity);
}
ExtrusionEntityCollection
ExtrusionEntityCollection::flatten() const
ExtrusionEntityCollection ExtrusionEntityCollection::flatten() const
{
ExtrusionEntityCollection coll;
this->flatten(&coll);
return coll;
}
double
ExtrusionEntityCollection::min_mm3_per_mm() const
double ExtrusionEntityCollection::min_mm3_per_mm() const
{
double min_mm3_per_mm = std::numeric_limits<double>::max();
for (ExtrusionEntitiesPtr::const_iterator it = this->entities.begin(); it != this->entities.end(); ++it)
min_mm3_per_mm = std::min(min_mm3_per_mm, (*it)->min_mm3_per_mm());
for (const ExtrusionEntity *entity : this->entities)
min_mm3_per_mm = std::min(min_mm3_per_mm, entity->min_mm3_per_mm());
return min_mm3_per_mm;
}

View file

@ -9,7 +9,10 @@ namespace Slic3r {
class ExtrusionEntityCollection : public ExtrusionEntity
{
public:
ExtrusionEntityCollection* clone() const;
ExtrusionEntity* clone() const override;
// Create a new object, initialize it with this object using the move semantics.
ExtrusionEntity* clone_move() override { return new ExtrusionEntityCollection(std::move(*this)); }
ExtrusionEntitiesPtr entities; // we own these entities
std::vector<size_t> orig_indices; // handy for XS
bool no_sort;
@ -36,11 +39,12 @@ public:
bool empty() const { return this->entities.empty(); };
void clear();
void swap (ExtrusionEntityCollection &c);
void append(const ExtrusionEntity &entity) { this->entities.push_back(entity.clone()); }
void append(const ExtrusionEntitiesPtr &entities) {
void append(const ExtrusionEntity &entity) { this->entities.emplace_back(entity.clone()); }
void append(ExtrusionEntity &&entity) { this->entities.emplace_back(entity.clone_move()); }
void append(const ExtrusionEntitiesPtr &entities) {
this->entities.reserve(this->entities.size() + entities.size());
for (ExtrusionEntitiesPtr::const_iterator ptr = entities.begin(); ptr != entities.end(); ++ptr)
this->entities.push_back((*ptr)->clone());
for (const ExtrusionEntity *ptr : entities)
this->entities.emplace_back(ptr->clone());
}
void append(ExtrusionEntitiesPtr &&src) {
if (entities.empty())

View file

@ -1869,8 +1869,9 @@ void GCode::process_layer(
if (! m_brim_done) {
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true;
for (const ExtrusionEntity *ee : print.brim().entities)
gcode += this->extrude_loop(*dynamic_cast<const ExtrusionLoop*>(ee), "brim", m_config.support_material_speed.value);
for (const ExtrusionEntity *ee : print.brim().entities) {
gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value);
}
m_brim_done = true;
m_avoid_crossing_perimeters.use_external_mp = false;
// Allow a straight travel move to the first object point.
@ -2504,10 +2505,9 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des
return this->extrude_multi_path(*multipath, description, speed);
else if (const ExtrusionLoop* loop = dynamic_cast<const ExtrusionLoop*>(&entity))
return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid);
else {
else
throw std::invalid_argument("Invalid argument supplied to extrude()");
return "";
}
return "";
}
std::string GCode::extrude_path(ExtrusionPath path, std::string description, double speed)

View file

@ -6,21 +6,240 @@
namespace Slic3r {
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
{
ExtrusionPaths paths;
ExtrusionPath path(role);
ThickLines lines = thick_polyline.thicklines();
for (int i = 0; i < (int)lines.size(); ++i) {
const ThickLine& line = lines[i];
const coordf_t line_len = line.length();
if (line_len < SCALED_EPSILON) continue;
double thickness_delta = fabs(line.a_width - line.b_width);
if (thickness_delta > tolerance) {
const unsigned int segments = (unsigned int)ceil(thickness_delta / tolerance);
const coordf_t seg_len = line_len / segments;
Points pp;
std::vector<coordf_t> width;
{
pp.push_back(line.a);
width.push_back(line.a_width);
for (size_t j = 1; j < segments; ++j) {
pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
width.push_back(w);
width.push_back(w);
}
pp.push_back(line.b);
width.push_back(line.b_width);
assert(pp.size() == segments + 1u);
assert(width.size() == segments*2);
}
// delete this line and insert new ones
lines.erase(lines.begin() + i);
for (size_t j = 0; j < segments; ++j) {
ThickLine new_line(pp[j], pp[j+1]);
new_line.a_width = width[2*j];
new_line.b_width = width[2*j+1];
lines.insert(lines.begin() + i + j, new_line);
}
-- i;
continue;
}
const double w = fmax(line.a_width, line.b_width);
if (path.polyline.points.empty()) {
path.polyline.append(line.a);
path.polyline.append(line.b);
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
flow.width = unscale<float>(w) + flow.height * float(1. - 0.25 * PI);
#ifdef SLIC3R_DEBUG
printf(" filling %f gap\n", flow.width);
#endif
path.mm3_per_mm = flow.mm3_per_mm();
path.width = flow.width;
path.height = flow.height;
} else {
thickness_delta = fabs(scale_(flow.width) - w);
if (thickness_delta <= tolerance) {
// the width difference between this line and the current flow width is
// within the accepted tolerance
path.polyline.append(line.b);
} else {
// we need to initialize a new line
paths.emplace_back(std::move(path));
path = ExtrusionPath(role);
-- i;
}
}
}
if (path.polyline.is_valid())
paths.emplace_back(std::move(path));
return paths;
}
static ExtrusionEntityCollection variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow)
{
// 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
// of segments, and any pruning shall be performed before we apply this tolerance.
ExtrusionEntityCollection coll;
const float tolerance = float(scale_(0.05));
for (const ThickPolyline &p : polylines) {
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
// Append paths to collection.
if (! paths.empty()) {
if (paths.front().first_point() == paths.back().last_point())
coll.append(ExtrusionLoop(std::move(paths)));
else
coll.append(std::move(paths));
}
}
return coll;
}
// Hierarchy of perimeters.
class PerimeterGeneratorLoop {
public:
// Polygon of this contour.
Polygon polygon;
// Is it a contour or a hole?
// Contours are CCW oriented, holes are CW oriented.
bool is_contour;
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
unsigned short depth;
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std::vector<PerimeterGeneratorLoop> children;
PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) :
polygon(polygon), is_contour(is_contour), depth(depth) {}
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
bool is_external() const { return this->depth == 0; }
// An island, which may have holes, but it does not have another internal island.
bool is_internal_contour() const;
};
typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls)
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
ExtrusionEntityCollection coll;
for (const PerimeterGeneratorLoop &loop : loops) {
bool is_external = loop.is_external();
ExtrusionRole role;
ExtrusionLoopRole loop_role;
role = is_external ? erExternalPerimeter : erPerimeter;
if (loop.is_internal_contour()) {
// Note that we set loop role to ContourInternalPerimeter
// also when loop is both internal and external (i.e.
// there's only one contour loop).
loop_role = elrContourInternalPerimeter;
} else {
loop_role = elrDefault;
}
// detect overhanging/bridging perimeters
ExtrusionPaths paths;
if (perimeter_generator.config->overhangs && perimeter_generator.layer_id > 0
&& !(perimeter_generator.object_config->support_material && perimeter_generator.object_config->support_material_contact_distance.value == 0)) {
// get non-overhang paths by intersecting this loop with the grown lower slices
extrusion_paths_append(
paths,
intersection_pl(loop.polygon, perimeter_generator.lower_slices_polygons()),
role,
is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(),
is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width,
(float)perimeter_generator.layer_height);
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
// the loop centerline and original lower slices is >= half nozzle diameter
extrusion_paths_append(
paths,
diff_pl(loop.polygon, perimeter_generator.lower_slices_polygons()),
erOverhangPerimeter,
perimeter_generator.mm3_per_mm_overhang(),
perimeter_generator.overhang_flow.width,
perimeter_generator.overhang_flow.height);
// reapply the nearest point search for starting point
// We allow polyline reversal because Clipper may have randomly
// reversed polylines during clipping.
paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
} else {
ExtrusionPath path(role);
path.polyline = loop.polygon.split_at_first_point();
path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm();
path.width = is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width;
path.height = (float)perimeter_generator.layer_height;
paths.push_back(path);
}
coll.append(ExtrusionLoop(paths, loop_role));
}
// Append thin walls to the nearest-neighbor search (only for first iteration)
if (! thin_walls.empty()) {
ExtrusionEntityCollection tw = variable_width(thin_walls, erExternalPerimeter, perimeter_generator.ext_perimeter_flow);
coll.append(tw.entities);
thin_walls.clear();
}
// Sort entities into a new collection using a nearest-neighbor search,
// preserving the original indices which are useful for detecting thin walls.
ExtrusionEntityCollection sorted_coll;
coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
// traverse children and build the final collection
ExtrusionEntityCollection entities;
for (const size_t &idx : sorted_coll.orig_indices) {
if (idx >= loops.size()) {
// This is a thin wall. Let's get it from the sorted collection as it might have been reversed.
entities.append(std::move(*sorted_coll.entities[&idx - &sorted_coll.orig_indices.front()]));
} else {
const PerimeterGeneratorLoop &loop = loops[idx];
ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[idx]);
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls);
if (loop.is_contour) {
eloop.make_counter_clockwise();
entities.append(std::move(children.entities));
entities.append(std::move(eloop));
} else {
eloop.make_clockwise();
entities.append(std::move(eloop));
entities.append(std::move(children.entities));
}
}
}
return entities;
}
void PerimeterGenerator::process()
{
// other perimeters
this->_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
m_mm3_per_mm = this->perimeter_flow.mm3_per_mm();
coord_t perimeter_width = this->perimeter_flow.scaled_width();
coord_t perimeter_spacing = this->perimeter_flow.scaled_spacing();
// external perimeters
this->_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
// overhang perimeters
this->_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
// solid infill
coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing();
@ -35,8 +254,8 @@ void PerimeterGenerator::process()
// which is the spacing between external and internal, which is not correct
// and would make the collapsing (thus the details resolution) dependent on
// internal flow which is unrelated.
coord_t min_spacing = perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE);
coord_t ext_min_spacing = ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE);
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
bool has_gap_fill = this->config->gap_fill_speed.value > 0;
// prepare grown lower layer slices for overhang detection
@ -45,7 +264,7 @@ void PerimeterGenerator::process()
// lower layer, so we take lower slices and offset them by half the nozzle diameter used
// in the current layer
double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->perimeter_extruder-1);
this->_lower_slices_p = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter/2)));
}
// we need to process each island separately because we might have different
@ -70,20 +289,20 @@ void PerimeterGenerator::process()
offsets = this->config->thin_walls ?
offset2_ex(
last,
-(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1),
+(ext_min_spacing / 2 - 1)) :
offset_ex(last, - ext_perimeter_width / 2);
- float(ext_perimeter_width / 2. + ext_min_spacing / 2. - 1),
+ float(ext_min_spacing / 2. - 1)) :
offset_ex(last, - float(ext_perimeter_width / 2.));
// look for thin walls
if (this->config->thin_walls) {
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = scale_(this->ext_perimeter_flow.nozzle_diameter / 3);
coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3));
ExPolygons expp = offset2_ex(
// medial axis requires non-overlapping geometry
diff_ex(to_polygons(last),
offset(offsets, ext_perimeter_width / 2),
offset(offsets, float(ext_perimeter_width / 2.)),
true),
- min_width / 2, min_width / 2);
- float(min_width / 2.), float(min_width / 2.));
// the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop
for (ExPolygon &ex : expp)
ex.medial_axis(ext_perimeter_width + ext_perimeter_spacing2, min_width, &thin_walls);
@ -100,19 +319,19 @@ void PerimeterGenerator::process()
// Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than
// the original.
offset2_ex(last,
- (distance + min_spacing / 2 - 1),
min_spacing / 2 - 1) :
- float(distance + min_spacing / 2. - 1.),
float(min_spacing / 2. - 1.)) :
// If "detect thin walls" is not enabled, this paths will be entered, which
// leads to overflows, as in prusa3d/Slic3r GH #32
offset_ex(last, - distance);
offset_ex(last, - float(distance));
// look for gaps
if (has_gap_fill)
// not using safety offset here would "detect" very narrow gaps
// (but still long enough to escape the area threshold) that gap fill
// won't be able to fill but we'd still remove from infill area
append(gaps, diff_ex(
offset(last, -0.5 * distance),
offset(offsets, 0.5 * distance + 10))); // safety offset
offset(last, - float(0.5 * distance)),
offset(offsets, float(0.5 * distance + 10)))); // safety offset
}
if (offsets.empty()) {
// Store the number of loops actually generated.
@ -125,6 +344,11 @@ void PerimeterGenerator::process()
break;
}
for (const ExPolygon &expolygon : offsets) {
// Outer contour may overlap with an inner contour,
// inner contour may overlap with another inner contour,
// outer contour may overlap with itself.
//FIXME evaluate the overlaps, annotate each point with an overlap depth,
// compensate for the depth of intersection.
contours[i].emplace_back(PerimeterGeneratorLoop(expolygon.contour, i, true));
if (! expolygon.holes.empty()) {
holes[i].reserve(holes[i].size() + expolygon.holes.size());
@ -195,7 +419,7 @@ void PerimeterGenerator::process()
}
}
// at this point, all loops should be in contours[0]
ExtrusionEntityCollection entities = this->_traverse_loops(contours.front(), thin_walls);
ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls);
// if brim will be printed, reverse the order of perimeters so that
// we continue inwards after having finished the brim
// TODO: add test for perimeter order
@ -214,15 +438,14 @@ void PerimeterGenerator::process()
double max = 2. * perimeter_spacing;
ExPolygons gaps_ex = diff_ex(
//FIXME offset2 would be enough and cheaper.
offset2_ex(gaps, -min/2, +min/2),
offset2_ex(gaps, -max/2, +max/2),
offset2_ex(gaps, - float(min / 2.), float(min / 2.)),
offset2_ex(gaps, - float(max / 2.), float(max / 2.)),
true);
ThickPolylines polylines;
for (const ExPolygon &ex : gaps_ex)
ex.medial_axis(max, min, &polylines);
if (! polylines.empty()) {
ExtrusionEntityCollection gap_fill = this->_variable_width(polylines,
erGapFill, this->solid_infill_flow);
ExtrusionEntityCollection gap_fill = variable_width(polylines, erGapFill, this->solid_infill_flow);
this->gap_fill->append(gap_fill.entities);
/* 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).
@ -249,229 +472,23 @@ void PerimeterGenerator::process()
perimeter_spacing / 2;
// only apply infill overlap if we actually have one perimeter
if (inset > 0)
inset -= scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2)));
inset -= coord_t(scale_(this->config->get_abs_value("infill_overlap", unscale<double>(inset + solid_infill_spacing / 2))));
// simplify infill contours according to resolution
Polygons pp;
for (ExPolygon &ex : last)
ex.simplify_p(SCALED_RESOLUTION, &pp);
// collapse too narrow infill areas
coord_t min_perimeter_infill_spacing = solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE);
coord_t min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
// append infill areas to fill_surfaces
this->fill_surfaces->append(
offset2_ex(
union_ex(pp),
- inset - min_perimeter_infill_spacing / 2,
min_perimeter_infill_spacing / 2),
float(- inset - min_perimeter_infill_spacing / 2.),
float(min_perimeter_infill_spacing / 2.)),
stInternal);
} // for each island
}
ExtrusionEntityCollection PerimeterGenerator::_traverse_loops(
const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const
{
// loops is an arrayref of ::Loop objects
// turn each one into an ExtrusionLoop object
ExtrusionEntityCollection coll;
for (PerimeterGeneratorLoops::const_iterator loop = loops.begin();
loop != loops.end(); ++loop) {
bool is_external = loop->is_external();
ExtrusionRole role;
ExtrusionLoopRole loop_role;
role = is_external ? erExternalPerimeter : erPerimeter;
if (loop->is_internal_contour()) {
// Note that we set loop role to ContourInternalPerimeter
// also when loop is both internal and external (i.e.
// there's only one contour loop).
loop_role = elrContourInternalPerimeter;
} else {
loop_role = elrDefault;
}
// detect overhanging/bridging perimeters
ExtrusionPaths paths;
if (this->config->overhangs && this->layer_id > 0
&& !(this->object_config->support_material && this->object_config->support_material_contact_distance.value == 0)) {
// get non-overhang paths by intersecting this loop with the grown lower slices
extrusion_paths_append(
paths,
intersection_pl(loop->polygon, this->_lower_slices_p),
role,
is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm,
is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width,
this->layer_height);
// get overhang paths by checking what parts of this loop fall
// outside the grown lower slices (thus where the distance between
// the loop centerline and original lower slices is >= half nozzle diameter
extrusion_paths_append(
paths,
diff_pl(loop->polygon, this->_lower_slices_p),
erOverhangPerimeter,
this->_mm3_per_mm_overhang,
this->overhang_flow.width,
this->overhang_flow.height);
// reapply the nearest point search for starting point
// We allow polyline reversal because Clipper may have randomly
// reversed polylines during clipping.
paths = (ExtrusionPaths)ExtrusionEntityCollection(paths).chained_path();
} else {
ExtrusionPath path(role);
path.polyline = loop->polygon.split_at_first_point();
path.mm3_per_mm = is_external ? this->_ext_mm3_per_mm : this->_mm3_per_mm;
path.width = is_external ? this->ext_perimeter_flow.width : this->perimeter_flow.width;
path.height = this->layer_height;
paths.push_back(path);
}
coll.append(ExtrusionLoop(paths, loop_role));
}
// append thin walls to the nearest-neighbor search (only for first iteration)
if (!thin_walls.empty()) {
ExtrusionEntityCollection tw = this->_variable_width
(thin_walls, erExternalPerimeter, this->ext_perimeter_flow);
coll.append(tw.entities);
thin_walls.clear();
}
// sort entities into a new collection using a nearest-neighbor search,
// preserving the original indices which are useful for detecting thin walls
ExtrusionEntityCollection sorted_coll;
coll.chained_path(&sorted_coll, false, erMixed, &sorted_coll.orig_indices);
// traverse children and build the final collection
ExtrusionEntityCollection entities;
for (std::vector<size_t>::const_iterator idx = sorted_coll.orig_indices.begin();
idx != sorted_coll.orig_indices.end();
++idx) {
if (*idx >= loops.size()) {
// this is a thin wall
// let's get it from the sorted collection as it might have been reversed
size_t i = idx - sorted_coll.orig_indices.begin();
entities.append(*sorted_coll.entities[i]);
} else {
const PerimeterGeneratorLoop &loop = loops[*idx];
ExtrusionLoop eloop = *dynamic_cast<ExtrusionLoop*>(coll.entities[*idx]);
ExtrusionEntityCollection children = this->_traverse_loops(loop.children, thin_walls);
if (loop.is_contour) {
eloop.make_counter_clockwise();
entities.append(children.entities);
entities.append(eloop);
} else {
eloop.make_clockwise();
entities.append(eloop);
entities.append(children.entities);
}
}
}
return entities;
}
static inline ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
{
ExtrusionPaths paths;
ExtrusionPath path(role);
ThickLines lines = thick_polyline.thicklines();
for (int i = 0; i < (int)lines.size(); ++i) {
const ThickLine& line = lines[i];
const coordf_t line_len = line.length();
if (line_len < SCALED_EPSILON) continue;
double thickness_delta = fabs(line.a_width - line.b_width);
if (thickness_delta > tolerance) {
const unsigned short segments = ceil(thickness_delta / tolerance);
const coordf_t seg_len = line_len / segments;
Points pp;
std::vector<coordf_t> width;
{
pp.push_back(line.a);
width.push_back(line.a_width);
for (size_t j = 1; j < segments; ++j) {
pp.push_back((line.a.cast<double>() + (line.b - line.a).cast<double>().normalized() * (j * seg_len)).cast<coord_t>());
coordf_t w = line.a_width + (j*seg_len) * (line.b_width-line.a_width) / line_len;
width.push_back(w);
width.push_back(w);
}
pp.push_back(line.b);
width.push_back(line.b_width);
assert(pp.size() == segments + 1u);
assert(width.size() == segments*2);
}
// delete this line and insert new ones
lines.erase(lines.begin() + i);
for (size_t j = 0; j < segments; ++j) {
ThickLine new_line(pp[j], pp[j+1]);
new_line.a_width = width[2*j];
new_line.b_width = width[2*j+1];
lines.insert(lines.begin() + i + j, new_line);
}
-- i;
continue;
}
const double w = fmax(line.a_width, line.b_width);
if (path.polyline.points.empty()) {
path.polyline.append(line.a);
path.polyline.append(line.b);
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
flow.width = unscale<float>(w) + flow.height * (1. - 0.25 * PI);
#ifdef SLIC3R_DEBUG
printf(" filling %f gap\n", flow.width);
#endif
path.mm3_per_mm = flow.mm3_per_mm();
path.width = flow.width;
path.height = flow.height;
} else {
thickness_delta = fabs(scale_(flow.width) - w);
if (thickness_delta <= tolerance) {
// the width difference between this line and the current flow width is
// within the accepted tolerance
path.polyline.append(line.b);
} else {
// we need to initialize a new line
paths.emplace_back(std::move(path));
path = ExtrusionPath(role);
-- i;
}
}
}
if (path.polyline.is_valid())
paths.emplace_back(std::move(path));
return paths;
}
ExtrusionEntityCollection PerimeterGenerator::_variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const
{
// 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
// of segments, and any pruning shall be performed before we apply this tolerance.
ExtrusionEntityCollection coll;
const double tolerance = scale_(0.05);
for (const ThickPolyline &p : polylines) {
ExtrusionPaths paths = thick_polyline_to_extrusion_paths(p, role, flow, tolerance);
// Append paths to collection.
if (! paths.empty()) {
if (paths.front().first_point() == paths.back().last_point())
coll.append(ExtrusionLoop(std::move(paths)));
else
coll.append(std::move(paths));
}
}
return coll;
}
bool PerimeterGeneratorLoop::is_internal_contour() const
{
// An internal contour is a contour containing no other contours

View file

@ -11,29 +11,6 @@
namespace Slic3r {
// Hierarchy of perimeters.
class PerimeterGeneratorLoop {
public:
// Polygon of this contour.
Polygon polygon;
// Is it a contour or a hole?
// Contours are CCW oriented, holes are CW oriented.
bool is_contour;
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
unsigned short depth;
// Children contour, may be both CCW and CW oriented (outer contours or holes).
std::vector<PerimeterGeneratorLoop> children;
PerimeterGeneratorLoop(Polygon polygon, unsigned short depth, bool is_contour) :
polygon(polygon), is_contour(is_contour), depth(depth) {}
// External perimeter. It may be CCW or CW oriented (outer contour or hole contour).
bool is_external() const { return this->depth == 0; }
// An island, which may have holes, but it does not have another internal island.
bool is_internal_contour() const;
};
typedef std::vector<PerimeterGeneratorLoop> PerimeterGeneratorLoops;
class PerimeterGenerator {
public:
// Inputs:
@ -73,18 +50,21 @@ public:
overhang_flow(flow), solid_infill_flow(flow),
config(config), object_config(object_config), print_config(print_config),
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
_ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1)
m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1)
{}
void process();
void process();
double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; }
double mm3_per_mm() const { return m_mm3_per_mm; }
double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; }
Polygons lower_slices_polygons() const { return m_lower_slices_polygons; }
private:
double _ext_mm3_per_mm;
double _mm3_per_mm;
double _mm3_per_mm_overhang;
Polygons _lower_slices_p;
ExtrusionEntityCollection _traverse_loops(const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls) const;
ExtrusionEntityCollection _variable_width(const ThickPolylines &polylines, ExtrusionRole role, Flow flow) const;
double m_ext_mm3_per_mm;
double m_mm3_per_mm;
double m_mm3_per_mm_overhang;
Polygons m_lower_slices_polygons;
};
}

View file

@ -1,3 +1,5 @@
#include "clipper/clipper_z.hpp"
#include "Print.hpp"
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
@ -1639,9 +1641,7 @@ void Print::_make_skirt()
// Initial offset of the brim inner edge from the object (possible with a support & raft).
// The skirt will touch the brim if the brim is extruded.
Flow brim_flow = this->brim_flow();
double actual_brim_width = brim_flow.spacing() * floor(m_config.brim_width.value / brim_flow.spacing());
auto distance = float(scale_(std::max(m_config.skirt_distance.value, actual_brim_width) - spacing/2.));
auto distance = float(scale_(m_config.skirt_distance.value) - spacing/2.);
// Draw outlines from outside to inside.
// Loop while we have less skirts than required or any extruder hasn't reached the min length if any.
std::vector<coordf_t> extruded_length(extruders.size(), 0.);
@ -1723,12 +1723,134 @@ void Print::_make_brim()
}
polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing())));
}
loops = union_pt_chained(loops, false);
// The function above produces ordering well suited for concentric infill (from outside to inside).
// For Brim, the ordering should be reversed (from inside to outside).
std::reverse(loops.begin(), loops.end());
extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
// If there is a possibility that brim intersects skirt, go through loops and split those extrusions
// The result is either the original Polygon or a list of Polylines
if (! m_skirt.empty() && m_config.skirt_distance.value < m_config.brim_width)
{
// Find the bounding polygons of the skirt
const Polygons skirt_inners = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.back())->polygon(),
-float(scale_(this->skirt_flow().spacing()))/2.f,
ClipperLib::jtRound,
float(scale_(0.1)));
const Polygons skirt_outers = offset(dynamic_cast<ExtrusionLoop*>(m_skirt.entities.front())->polygon(),
float(scale_(this->skirt_flow().spacing()))/2.f,
ClipperLib::jtRound,
float(scale_(0.1)));
// First calculate the trimming region.
ClipperLib_Z::Paths trimming;
{
ClipperLib_Z::Paths input_subject;
ClipperLib_Z::Paths input_clip;
for (const Polygon &poly : skirt_outers) {
input_subject.emplace_back();
ClipperLib_Z::Path &out = input_subject.back();
out.reserve(poly.points.size());
for (const Point &pt : poly.points)
out.emplace_back(pt.x(), pt.y(), 0);
}
for (const Polygon &poly : skirt_inners) {
input_clip.emplace_back();
ClipperLib_Z::Path &out = input_clip.back();
out.reserve(poly.points.size());
for (const Point &pt : poly.points)
out.emplace_back(pt.x(), pt.y(), 0);
}
// init Clipper
ClipperLib_Z::Clipper clipper;
// add polygons
clipper.AddPaths(input_subject, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(input_clip, ClipperLib_Z::ptClip, true);
// perform operation
clipper.Execute(ClipperLib_Z::ctDifference, trimming, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
}
// Second, trim the extrusion loops with the trimming regions.
ClipperLib_Z::Paths loops_trimmed;
{
// Produce a closed polyline (repeat the first point at the end).
ClipperLib_Z::Paths input_clip;
for (const Polygon &loop : loops) {
input_clip.emplace_back();
ClipperLib_Z::Path& out = input_clip.back();
out.reserve(loop.points.size());
int64_t loop_idx = &loop - &loops.front();
for (const Point& pt : loop.points)
// The Z coordinate carries index of the source loop.
out.emplace_back(pt.x(), pt.y(), loop_idx + 1);
out.emplace_back(out.front());
}
// init Clipper
ClipperLib_Z::Clipper clipper;
clipper.ZFillFunction([](const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top, const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top, ClipperLib_Z::IntPoint& pt) {
// Assign a valid input loop identifier. Such an identifier is strictly positive, the next line is safe even in case one side of a segment
// hat the Z coordinate not set to the contour coordinate.
pt.Z = std::max(std::max(e1bot.Z, e1top.Z), std::max(e2bot.Z, e2top.Z));
});
// add polygons
clipper.AddPaths(input_clip, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(trimming, ClipperLib_Z::ptClip, true);
// perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftEvenOdd, ClipperLib_Z::pftEvenOdd);
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
}
// Third, produce the extrusions, sorted by the source loop indices.
{
std::vector<std::pair<const ClipperLib_Z::Path*, size_t>> loops_trimmed_order;
loops_trimmed_order.reserve(loops_trimmed.size());
for (const ClipperLib_Z::Path &path : loops_trimmed) {
size_t input_idx = 0;
for (const ClipperLib_Z::IntPoint &pt : path)
if (pt.Z > 0) {
input_idx = (size_t)pt.Z;
break;
}
assert(input_idx != 0);
loops_trimmed_order.emplace_back(&path, input_idx);
}
std::stable_sort(loops_trimmed_order.begin(), loops_trimmed_order.end(),
[](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;
});
Vec3f last_pt(0.f, 0.f, 0.f);
for (size_t i = 0; i < loops_trimmed_order.size();) {
// Find all pieces that the initial loop was split into.
size_t j = i + 1;
for (; j < loops_trimmed_order.size() && loops_trimmed_order[i].first == loops_trimmed_order[j].first; ++ j) ;
const ClipperLib_Z::Path &first_path = *loops_trimmed_order[i].first;
if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) {
auto *loop = new ExtrusionLoop();
m_brim.entities.emplace_back(loop);
loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
Points &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
i = j;
} else {
//FIXME this is not optimal as the G-code generator will follow the sequence of paths verbatim without respect to minimum travel distance.
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())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = static_cast<ExtrusionPath*>(m_brim.entities.back())->polyline.points;
points.reserve(path.size());
for (const ClipperLib_Z::IntPoint &pt : path)
points.emplace_back(coord_t(pt.X), coord_t(pt.Y));
}
}
}
}
} else {
extrusion_entities_append_loops(m_brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(this->skirt_first_layer_height()));
}
}
// Wipe tower support.

View file

@ -105,6 +105,8 @@
#include <cereal/access.hpp>
#include <cereal/types/base_class.hpp>
#include <clipper/clipper_z.hpp>
#include <clipper/clipper.hpp>
#include "BoundingBox.hpp"
#include "ClipperUtils.hpp"
#include "Config.hpp"

View file

@ -85,6 +85,8 @@ set(SLIC3R_GUI_SOURCES
GUI/GUI_ObjectLayers.hpp
GUI/LambdaObjectDialog.cpp
GUI/LambdaObjectDialog.hpp
GUI/MeshUtils.cpp
GUI/MeshUtils.hpp
GUI/Tab.cpp
GUI/Tab.hpp
GUI/ConfigManipulation.cpp

View file

@ -12,6 +12,7 @@
#include "Selection.hpp"
#include "Gizmos/GLGizmosManager.hpp"
#include "GUI_ObjectLayers.hpp"
#include "MeshUtils.hpp"
#include <float.h>
@ -67,36 +68,6 @@ public:
};
class ClippingPlane
{
double m_data[4];
public:
ClippingPlane()
{
m_data[0] = 0.0;
m_data[1] = 0.0;
m_data[2] = 1.0;
m_data[3] = 0.0;
}
ClippingPlane(const Vec3d& direction, double offset)
{
Vec3d norm_dir = direction.normalized();
m_data[0] = norm_dir(0);
m_data[1] = norm_dir(1);
m_data[2] = norm_dir(2);
m_data[3] = offset;
}
bool is_active() const { return m_data[3] != DBL_MAX; }
static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
const double* get_data() const { return m_data; }
};
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
using Vec2dEvent = Event<Vec2d>;

View file

@ -1,6 +1,7 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmos.hpp"
#include <GL/glew.h>
@ -12,10 +13,10 @@
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/MeshUtils.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Tesselate.hpp"
namespace Slic3r {
@ -26,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& ic
, m_quadric(nullptr)
, m_its(nullptr)
{
m_clipping_plane.reset(new ClippingPlane(Vec3d::Zero(), 0.));
m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr)
// using GLU_FILL does not work when the instance's transformation
@ -137,127 +139,82 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
if (m_clipping_plane_distance == 0.f)
return;
if (m_clipping_plane_normal == Vec3d::Zero())
reset_clipping_plane_normal();
const Vec3d& direction_to_camera = m_clipping_plane_normal;
// First cache instance transformation to be used later.
// Get transformation of the instance
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast<float>();
Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast<float>();
Vec3f scaling = vol->get_instance_scaling_factor().cast<float>();
Vec3d instance_offset = vol->get_instance_offset();
Geometry::Transformation trafo = vol->get_instance_transformation();
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
// Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast<float>();
Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm());
// Get transformation of supports
Geometry::Transformation supports_trafo;
supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), vol->get_sla_shift_z()));
supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2)));
// I don't know why, but following seems to be correct.
supports_trafo.set_mirror(Vec3d(trafo.get_mirror()(0) * trafo.get_mirror()(1) * trafo.get_mirror()(2),
1,
1.));
// Get transformation of the supports and calculate how far from its origin the clipping plane is.
Transform3d supports_trafo = Transform3d::Identity();
supports_trafo = supports_trafo.rotate(Eigen::AngleAxisd(vol->get_instance_rotation()(2), Vec3d::UnitZ()));
Vec3f up_supports = (supports_trafo.inverse() * direction_to_camera).cast<float>();
supports_trafo = supports_trafo.pretranslate(Vec3d(instance_offset(0), instance_offset(1), vol->get_sla_shift_z()));
// Instance and supports origin do not coincide, so the following is quite messy:
float height_supports = height_mesh * (up.norm() / up_supports.norm()) + instance_offset(2) * (direction_to_camera(2) / direction_to_camera.norm());
// Now initialize the TMS for the object, perform the cut and save the result.
if (! m_object_clipper) {
m_object_clipper.reset(new MeshClipper);
m_object_clipper->set_mesh(*m_mesh);
}
m_object_clipper->set_plane(*m_clipping_plane);
m_object_clipper->set_transformation(trafo);
// In case either of these was recently changed, the cached triangulated ExPolygons are invalid now.
// We are gonna recalculate them both for the object and for the support structures.
if (m_clipping_plane_distance != m_old_clipping_plane_distance
|| m_old_clipping_plane_normal != direction_to_camera) {
m_old_clipping_plane_normal = direction_to_camera;
m_old_clipping_plane_distance = m_clipping_plane_distance;
// Now initialize the TMS for the object, perform the cut and save the result.
if (! m_tms) {
m_tms.reset(new TriangleMeshSlicer);
m_tms->init(m_mesh, [](){});
// Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
// First we need a pointer to the respective SLAPrintObject. The index into objects vector is
// cached so we don't have todo it on each render. We only search for the po if needed:
if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
m_print_objects_count = m_parent.sla_print()->objects().size();
m_print_object_idx = -1;
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
++m_print_object_idx;
if (po->model_object()->id() == m_model_object->id())
break;
}
std::vector<ExPolygons> list_of_expolys;
m_tms->set_up_direction(up);
m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
m_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
}
if (m_print_object_idx >= 0) {
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
if (print_object->is_step_done(slaposSupportTree)) {
// If the supports are already calculated, save the timestamp of the respective step
// so we can later tell they were recalculated.
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
// Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
// First we need a pointer to the respective SLAPrintObject. The index into objects vector is
// cached so we don't have todo it on each render. We only search for the po if needed:
if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
m_print_objects_count = m_parent.sla_print()->objects().size();
m_print_object_idx = -1;
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
++m_print_object_idx;
if (po->model_object()->id() == m_model_object->id())
break;
}
}
if (m_print_object_idx >= 0) {
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
if (print_object->is_step_done(slaposSupportTree)) {
// If the supports are already calculated, save the timestamp of the respective step
// so we can later tell they were recalculated.
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
if (!m_supports_tms || (int)timestamp != m_old_timestamp) {
// The timestamp has changed - stash the mesh and initialize the TMS.
m_supports_mesh = &print_object->support_mesh();
m_supports_tms.reset(new TriangleMeshSlicer);
// The mesh should already have the shared vertices calculated.
m_supports_tms->init(m_supports_mesh, [](){});
m_old_timestamp = timestamp;
}
// The TMS is initialized - let's do the cutting:
list_of_expolys.clear();
m_supports_tms->set_up_direction(up_supports);
m_supports_tms->slice(std::vector<float>{height_supports}, 0.f, &list_of_expolys, [](){});
m_supports_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
}
else {
// The supports are not valid. We better dump the cached data.
m_supports_tms.reset();
m_supports_triangles.clear();
if (! m_supports_clipper || (int)timestamp != m_old_timestamp) {
// The timestamp has changed.
m_supports_clipper.reset(new MeshClipper);
// The mesh should already have the shared vertices calculated.
m_supports_clipper->set_mesh(print_object->support_mesh());
m_old_timestamp = timestamp;
}
m_supports_clipper->set_plane(*m_clipping_plane);
m_supports_clipper->set_transformation(supports_trafo);
}
else
// The supports are not valid. We better dump the cached data.
m_supports_clipper.reset();
}
// At this point we have the triangulated cuts for both the object and supports - let's render.
if (! m_triangles.empty()) {
if (! m_object_clipper->get_triangles().empty()) {
::glPushMatrix();
::glTranslated(0.0, 0.0, m_z_shift);
::glMultMatrixf(instance_matrix.data());
Eigen::Quaternionf q;
q.setFromTwoVectors(Vec3f::UnitZ(), up);
Eigen::AngleAxisf aa(q);
::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
::glTranslatef(0.f, 0.f, 0.01f); // to make sure the cut does not intersect the structure itself
::glColor3f(1.0f, 0.37f, 0.0f);
::glBegin(GL_TRIANGLES);
for (const Vec2f& point : m_triangles)
::glVertex3f(point(0), point(1), height_mesh);
for (const Vec3f& point : m_object_clipper->get_triangles())
::glVertex3f(point(0), point(1), point(2));
::glEnd();
::glPopMatrix();
}
if (! m_supports_triangles.empty() && !m_editing_mode) {
if (m_supports_clipper && ! m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
// The supports are hidden in the editing mode, so it makes no sense to render the cuts.
::glPushMatrix();
::glMultMatrixd(supports_trafo.data());
Eigen::Quaternionf q;
q.setFromTwoVectors(Vec3f::UnitZ(), up_supports);
Eigen::AngleAxisf aa(q);
::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
::glTranslatef(0.f, 0.f, 0.01f);
::glPushMatrix();
::glColor3f(1.0f, 0.f, 0.37f);
::glBegin(GL_TRIANGLES);
for (const Vec2f& point : m_supports_triangles)
::glVertex3f(point(0), point(1), height_supports);
for (const Vec3f& point : m_supports_clipper->get_triangles())
::glVertex3f(point(0), point(1), point(2));
::glEnd();
::glPopMatrix();
}
@ -379,15 +336,12 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const
{
const Vec3d& direction_to_camera = m_clipping_plane_normal;
if (m_clipping_plane_distance == 0.f)
return false;
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
transformed_point(2) += m_z_shift;
return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius
- m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point);
return m_clipping_plane->distance(transformed_point) < 0.;
}
@ -693,18 +647,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
if (action == SLAGizmoEventType::MouseWheelUp && control_down) {
m_clipping_plane_distance = std::min(1.f, m_clipping_plane_distance + 0.01f);
m_parent.set_as_dirty();
update_clipping_plane(true);
return true;
}
if (action == SLAGizmoEventType::MouseWheelDown && control_down) {
m_clipping_plane_distance = std::max(0.f, m_clipping_plane_distance - 0.01f);
m_parent.set_as_dirty();
update_clipping_plane(true);
return true;
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
reset_clipping_plane_normal();
update_clipping_plane();
return true;
}
@ -796,18 +750,10 @@ void GLGizmoSlaSupports::update_cache_entry_normal(size_t i) const
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
{
if (!m_model_object || m_state == Off)
if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
return ClippingPlane::ClipsNothing();
//Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix;
//::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
// we'll recover current look direction from the modelview matrix (in world coords):
//Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
const Vec3d& direction_to_camera = m_clipping_plane_normal;
float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift));
return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
else
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
}
@ -1019,14 +965,15 @@ RENDER_AGAIN:
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
reset_clipping_plane_normal();
update_clipping_plane();
});
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f");
if (ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f"))
update_clipping_plane(true);
if (m_imgui->button("?")) {
@ -1156,8 +1103,8 @@ void GLGizmoSlaSupports::on_set_state()
// Release triangle mesh slicer and the AABB spatial search structure.
m_AABB.deinit();
m_its = nullptr;
m_tms.reset();
m_supports_tms.reset();
m_object_clipper.reset();
m_supports_clipper.reset();
}
}
m_old_state = m_state;
@ -1198,7 +1145,7 @@ void GLGizmoSlaSupports::on_stop_dragging()
void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
{
ar(m_clipping_plane_distance,
m_clipping_plane_normal,
*m_clipping_plane,
m_model_object_id,
m_new_point_head_diameter,
m_normal_cache,
@ -1212,7 +1159,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
{
ar(m_clipping_plane_distance,
m_clipping_plane_normal,
*m_clipping_plane,
m_model_object_id,
m_new_point_head_diameter,
m_normal_cache,
@ -1401,17 +1348,19 @@ bool GLGizmoSlaSupports::unsaved_changes() const
}
void GLGizmoSlaSupports::reset_clipping_plane_normal() const
void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
{
Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
m_clipping_plane_normal = Vec3d(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
const Vec3d& center = m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
float dist = normal.dot(center);
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
m_parent.set_as_dirty();
}
SlaGizmoHelpDialog::SlaGizmoHelpDialog()
: wxDialog(NULL, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
const wxString ctrl = GUI::shortkey_ctrl_prefix();

View file

@ -2,7 +2,6 @@
#define slic3r_GLGizmoSlaSupports_hpp_
#include "GLGizmoBase.hpp"
#include "GLGizmos.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
// There is an L function in igl that would be overridden by our localization macro - let's undefine it...
@ -19,9 +18,9 @@
namespace Slic3r {
namespace GUI {
class ClippingPlane;
class MeshClipper;
enum class SLAGizmoEventType : unsigned char;
class GLGizmoSlaSupports : public GLGizmoBase
{
@ -114,9 +113,7 @@ private:
std::vector<sla::SupportPoint> m_normal_cache; // to restore after discarding changes or undo/redo
float m_clipping_plane_distance = 0.f;
mutable float m_old_clipping_plane_distance = 0.f;
mutable Vec3d m_old_clipping_plane_normal;
mutable Vec3d m_clipping_plane_normal = Vec3d::Zero();
std::unique_ptr<ClippingPlane> m_clipping_plane;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
@ -128,8 +125,8 @@ private:
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
mutable std::unique_ptr<TriangleMeshSlicer> m_tms;
mutable std::unique_ptr<TriangleMeshSlicer> m_supports_tms;
mutable std::unique_ptr<MeshClipper> m_object_clipper;
mutable std::unique_ptr<MeshClipper> m_supports_clipper;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_point_clipped(const Vec3d& point) const;
@ -151,6 +148,7 @@ private:
void switch_to_editing_mode();
void disable_editing_mode();
void reset_clipping_plane_normal() const;
void update_clipping_plane(bool keep_normal = false) const;
protected:
void on_set_state() override;

View file

@ -2,7 +2,10 @@
#define slic3r_GLGizmos_hpp_
// this describes events being passed from GLCanvas3D to SlaSupport gizmo
enum class SLAGizmoEventType {
namespace Slic3r {
namespace GUI {
enum class SLAGizmoEventType : unsigned char {
LeftDown = 1,
LeftUp,
RightDown,
@ -20,6 +23,9 @@ enum class SLAGizmoEventType {
ResetClippingPlane
};
} // namespace GUI
} // namespace Slic3r
#include "slic3r/GUI/Gizmos/GLGizmoMove.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoScale.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoRotate.hpp"

View file

@ -0,0 +1,95 @@
#include "MeshUtils.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/TriangleMesh.hpp"
namespace Slic3r {
namespace GUI {
void MeshClipper::set_plane(const ClippingPlane& plane)
{
if (m_plane != plane) {
m_plane = plane;
m_triangles_valid = false;
}
}
void MeshClipper::set_mesh(const TriangleMesh& mesh)
{
if (m_mesh != &mesh) {
m_mesh = &mesh;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_triangles3d.resize(0);
m_tms.reset(nullptr);
}
}
void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
{
if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
m_trafo = trafo;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_triangles3d.resize(0);
}
}
const std::vector<Vec3f>& MeshClipper::get_triangles()
{
if (! m_triangles_valid)
recalculate_triangles();
return m_triangles3d;
}
void MeshClipper::recalculate_triangles()
{
if (! m_tms) {
m_tms.reset(new TriangleMeshSlicer);
m_tms->init(m_mesh, [](){});
}
const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
const Vec3f& scaling = m_trafo.get_scaling_factor().cast<float>();
// Calculate clipping plane normal in mesh coordinates.
Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
Vec3f up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
// Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
// Now do the cutting
std::vector<ExPolygons> list_of_expolys;
m_tms->set_up_direction(up);
m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.);
// Rotate the cut into world coords:
Eigen::Quaternionf q;
q.setFromTwoVectors(Vec3f::UnitZ(), up);
Transform3f tr = Transform3f::Identity();
tr.rotate(q);
tr = m_trafo.get_matrix().cast<float>() * tr;
m_triangles3d.clear();
m_triangles3d.reserve(m_triangles2d.size());
for (const Vec2f& pt : m_triangles2d) {
m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f));
m_triangles3d.back() = tr * m_triangles3d.back();
}
m_triangles_valid = true;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,94 @@
#ifndef slic3r_MeshUtils_hpp_
#define slic3r_MeshUtils_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/Geometry.hpp"
#include <cfloat>
namespace Slic3r {
class TriangleMesh;
class TriangleMeshSlicer;
namespace GUI {
class ClippingPlane
{
double m_data[4];
public:
ClippingPlane()
{
m_data[0] = 0.0;
m_data[1] = 0.0;
m_data[2] = 1.0;
m_data[3] = 0.0;
}
ClippingPlane(const Vec3d& direction, double offset)
{
Vec3d norm_dir = direction.normalized();
m_data[0] = norm_dir(0);
m_data[1] = norm_dir(1);
m_data[2] = norm_dir(2);
m_data[3] = offset;
}
bool operator==(const ClippingPlane& cp) const {
return m_data[0]==cp.m_data[0] && m_data[1]==cp.m_data[1] && m_data[2]==cp.m_data[2] && m_data[3]==cp.m_data[3];
}
bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); }
double distance(const Vec3d& pt) const {
assert(is_approx(get_normal().norm(), 1.));
return (-get_normal().dot(pt) + m_data[3]);
}
void set_normal(const Vec3d& normal) { for (size_t i=0; i<3; ++i) m_data[i] = normal(i); }
void set_offset(double offset) { m_data[3] = offset; }
Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); }
bool is_active() const { return m_data[3] != DBL_MAX; }
static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
const double* get_data() const { return m_data; }
// Serialization through cereal library
template <class Archive>
void serialize( Archive & ar )
{
ar( m_data[0], m_data[1], m_data[2], m_data[3] );
}
};
class MeshClipper {
public:
void set_plane(const ClippingPlane& plane);
void set_mesh(const TriangleMesh& mesh);
void set_transformation(const Geometry::Transformation& trafo);
const std::vector<Vec3f>& get_triangles();
private:
void recalculate_triangles();
Geometry::Transformation m_trafo;
const TriangleMesh* m_mesh = nullptr;
ClippingPlane m_plane;
std::vector<Vec2f> m_triangles2d;
std::vector<Vec3f> m_triangles3d;
bool m_triangles_valid = false;
std::unique_ptr<TriangleMeshSlicer> m_tms;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_MeshUtils_hpp_