Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_gcode_window

This commit is contained in:
enricoturri1966 2021-03-04 13:24:08 +01:00
commit 252aa9b229
18 changed files with 560 additions and 213 deletions

View File

@ -1120,6 +1120,7 @@ void GCodeProcessor::process_gcode_line(const GCodeReader::GCodeLine& line)
case 21: { process_G21(line); break; } // Set Units to Millimeters case 21: { process_G21(line); break; } // Set Units to Millimeters
case 22: { process_G22(line); break; } // Firmware controlled retract case 22: { process_G22(line); break; } // Firmware controlled retract
case 23: { process_G23(line); break; } // Firmware controlled unretract case 23: { process_G23(line); break; } // Firmware controlled unretract
case 28: { process_G28(line); break; } // Move to origin
case 90: { process_G90(line); break; } // Set to Absolute Positioning case 90: { process_G90(line); break; } // Set to Absolute Positioning
case 91: { process_G91(line); break; } // Set to Relative Positioning case 91: { process_G91(line); break; } // Set to Relative Positioning
case 92: { process_G92(line); break; } // Set Position case 92: { process_G92(line); break; } // Set Position
@ -2153,6 +2154,32 @@ void GCodeProcessor::process_G23(const GCodeReader::GCodeLine& line)
store_move_vertex(EMoveType::Unretract); store_move_vertex(EMoveType::Unretract);
} }
void GCodeProcessor::process_G28(const GCodeReader::GCodeLine& line)
{
std::string_view cmd = line.cmd();
std::string new_line_raw = { cmd.data(), cmd.size() };
bool found = false;
if (line.has_x()) {
new_line_raw += " X0";
found = true;
}
if (line.has_y()) {
new_line_raw += " Y0";
found = true;
}
if (line.has_z()) {
new_line_raw += " Z0";
found = true;
}
if (!found)
new_line_raw += " X0 Y0 Z0";
GCodeReader::GCodeLine new_gline;
GCodeReader reader;
reader.parse_line(new_line_raw, [&](GCodeReader& reader, const GCodeReader::GCodeLine& gline) { new_gline = gline; });
process_G1(new_gline);
}
void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_G90(const GCodeReader::GCodeLine& line)
{ {
m_global_positioning_type = EPositioningType::Absolute; m_global_positioning_type = EPositioningType::Absolute;

View File

@ -574,6 +574,9 @@ namespace Slic3r {
// Firmware controlled Unretract // Firmware controlled Unretract
void process_G23(const GCodeReader::GCodeLine& line); void process_G23(const GCodeReader::GCodeLine& line);
// Move to origin
void process_G28(const GCodeReader::GCodeLine& line);
// Set to Absolute Positioning // Set to Absolute Positioning
void process_G90(const GCodeReader::GCodeLine& line); void process_G90(const GCodeReader::GCodeLine& line);

View File

@ -1,5 +1,6 @@
#include "MutablePolygon.hpp" #include "MutablePolygon.hpp"
#include "Line.hpp" #include "Line.hpp"
#include "libslic3r.h"
namespace Slic3r { namespace Slic3r {
@ -36,207 +37,295 @@ void remove_duplicates(MutablePolygon &polygon, double eps)
} }
} }
// Sample a point on line (a, b) at distance "dist" from ref_pt. // Adapted from Cura ConstPolygonRef::smooth_corner_complex() by Tim Kuipers.
// If two points fulfill the condition, then the first one (closer to point a) is taken. // A concave corner at it1 with position p1 has been removed by the caller between it0 and it2, where |p2 - p0| < shortcut_length.
// If none of the two points falls on line (a, b), return false. // Now try to close a concave crack by walking left from it0 and right from it2 as long as the new clipping edge is smaller than shortcut_length
template<typename VectorType> // and the new clipping edge is still inside the polygon (it is a diagonal, it does not intersect polygon boundary).
static inline VectorType point_on_line_at_dist(const VectorType &a, const VectorType &b, const VectorType &ref_pt, const double dist) // Once the traversal stops (always at a clipping edge shorter than shortcut_length), the final trapezoid is clipped with a new clipping edge of shortcut_length.
// Return true if a hole was completely closed (degenerated to an empty polygon) or a single CCW triangle was left, which is not to be simplified any further.
// it0, it2 are updated to the final clipping edge.
static bool clip_narrow_corner(
const Vec2i64 p1,
MutablePolygon::iterator &it0,
MutablePolygon::iterator &it2,
MutablePolygon::range &unprocessed_range,
int64_t dist2_current,
const int64_t shortcut_length)
{ {
using T = typename VectorType::Scalar; MutablePolygon &polygon = it0.polygon();
auto v = b - a; assert(polygon.size() >= 2);
auto l2 = v.squaredNorm();
assert(l2 > T(0));
auto vpt = ref_pt - a;
// Parameter of the foot point of ref_pt on line (a, b).
auto t = v.dot(vpt) / l2;
// Foot point of ref_pt on line (a, b).
auto foot_pt = a + t * v;
auto dfoot2 = vpt.squaredNorm() - (foot_pt - ref_pt).squaredNorm();
// Distance of the result point from the foot point, normalized to length of (a, b).
auto dfoot = dfoot2 > T(0) ? sqrt(dfoot2) / sqrt(l2) : T(0);
auto t_result = t - dfoot;
if (t_result < T(0))
t_result = t + dfoot;
t_result = Slic3r::clamp(0., 1., t_result);
return a + v * t;
}
static bool smooth_corner_complex(const Vec2d p1, MutablePolygon::iterator &it0, MutablePolygon::iterator &it2, const double shortcut_length) const int64_t shortcut_length2 = sqr(shortcut_length);
{
// walk away from the corner until the shortcut > shortcut_length or it would smooth a piece inward
// - walk in both directions untill shortcut > shortcut_length
// - stop walking in one direction if it would otherwise cut off a corner in that direction
// - same in the other direction
// - stop if both are cut off
// walk by updating p0_it and p2_it
double shortcut_length2 = shortcut_length * shortcut_length;
bool forward_is_blocked = false;
bool forward_is_too_far = false;
bool backward_is_blocked = false;
bool backward_is_too_far = false;
for (;;) {
const bool forward_has_converged = forward_is_blocked || forward_is_too_far;
const bool backward_has_converged = backward_is_blocked || backward_is_too_far;
if (forward_has_converged && backward_has_converged) {
if (forward_is_too_far && backward_is_too_far && (*it0.prev() - *it2.next()).cast<double>().squaredNorm() < shortcut_length2) {
// Trim the narrowing region.
-- it0;
++ it2;
forward_is_too_far = false;
backward_is_too_far = false;
continue;
} else
break;
}
const Vec2d p0 = it0->cast<double>(); enum Status {
const Vec2d p2 = it2->cast<double>(); Free,
if (! forward_has_converged && (backward_has_converged || (p2 - p1).squaredNorm() < (p0 - p1).squaredNorm())) { Blocked,
// walk forward Far,
const auto it2_2 = it2.next(); };
const Vec2d p2_2 = it2_2->cast<double>(); Status forward = Free;
if (cross2(p2 - p0, p2_2 - p0) > 0) { Status backward = Free;
forward_is_blocked = true;
} else if ((p2_2 - p0).squaredNorm() > shortcut_length2) { Vec2i64 p0 = it0->cast<int64_t>();
forward_is_too_far = true; Vec2i64 p2 = it2->cast<int64_t>();
Vec2i64 p02;
Vec2i64 p22;
int64_t dist2_next;
// As long as there is at least a single triangle left in the polygon.
while (polygon.size() >= 3) {
assert(dist2_current <= shortcut_length2);
if (forward == Far && backward == Far) {
p02 = it0.prev()->cast<int64_t>();
p22 = it2.next()->cast<int64_t>();
auto d2 = (p22 - p02).squaredNorm();
if (d2 <= shortcut_length2) {
// The region was narrow until now and it is still narrow. Trim at both sides.
it0 = unprocessed_range.remove_back(it0).prev();
it2 = unprocessed_range.remove_front(it2);
if (polygon.size() <= 2)
// A hole degenerated to an empty polygon.
return true;
forward = Free;
backward = Free;
dist2_current = d2;
p0 = p02;
p2 = p22;
} else { } else {
it2 = it2_2; // make one step in the forward direction // The region is widening. Stop traversal and trim the final trapezoid.
backward_is_blocked = false; // invalidate data about backward walking dist2_next = d2;
backward_is_too_far = false; break;
}
} else if (forward != Free && backward != Free)
// One of the corners is blocked, the other is blocked or too far. Stop traversal.
break;
// Try to proceed by flipping a diagonal.
// Progress by keeping the distance of the clipping edge end points equal to initial p1.
//FIXME This is an arbitrary condition, maybe a more local condition will be better (take a shorter diagonal?).
if (forward == Free && (backward != Free || (p2 - p1).squaredNorm() < (p0 - p1).cast<int64_t>().squaredNorm())) {
p22 = it2.next()->cast<int64_t>();
if (cross2(p2 - p0, p22 - p0) > 0)
forward = Blocked;
else {
// New clipping edge lenght.
auto d2 = (p22 - p0).squaredNorm();
if (d2 > shortcut_length2) {
forward = Far;
dist2_next = d2;
} else {
forward = Free;
// Make one step in the forward direction.
it2 = unprocessed_range.remove_front(it2);
p2 = p22;
dist2_current = d2;
}
} }
} else { } else {
// walk backward assert(backward == Free);
const auto it0_2 = it0.prev(); p02 = it0.prev()->cast<int64_t>();
const Vec2d p0_2 = it0_2->cast<double>(); if (cross2(p0 - p2, p02 - p2) > 0)
if (cross2(p0_2 - p0, p2 - p0_2) > 0) { backward = Blocked;
backward_is_blocked = true; else {
} else if ((p2 - p0_2).squaredNorm() > shortcut_length2) { // New clipping edge lenght.
backward_is_too_far = true; auto d2 = (p2 - p02).squaredNorm();
} else { if (d2 > shortcut_length2) {
it0 = it0_2; // make one step in the backward direction backward = Far;
forward_is_blocked = false; // invalidate data about forward walking dist2_next = d2;
forward_is_too_far = false; } else {
} backward = Free;
} // Make one step in the backward direction.
it0 = unprocessed_range.remove_back(it0).prev();
if (it0.prev() == it2 || it0 == it2) { p0 = p02;
// stop if we went all the way around the polygon dist2_current = d2;
// this should only be the case for hole polygons (?) }
if (forward_is_too_far && backward_is_too_far) {
// in case p0_it.prev() == p2_it :
// / .
// / /|
// | becomes | |
// \ \|
// \ .
// in case p0_it == p2_it :
// / .
// / becomes /|
// \ \|
// \ .
break;
} else {
// this whole polygon can be removed
return true;
} }
} }
} }
const Vec2d p0 = it0->cast<double>(); if (polygon.size() <= 3) {
const Vec2d p2 = it2->cast<double>(); // A hole degenerated to an empty polygon, or a tiny triangle remained.
const Vec2d v02 = p2 - p0; assert(polygon.size() < 3 || (forward == Blocked && backward == Blocked) || (forward == Far && backward == Far));
const int64_t l2_v02 = v02.squaredNorm(); if (polygon.size() < 3 || forward == Far) {
if (std::abs(l2_v02 - shortcut_length2) < shortcut_length * 10) // i.e. if (size2 < l * (l+10) && size2 > l * (l-10)) assert(polygon.size() < 3 || dist2_current <= shortcut_length2);
{ // v02 is approximately shortcut length polygon.clear();
// handle this separately to avoid rounding problems below in the getPointOnLineWithDist function } else {
// p0_it and p2_it are already correct // The remaining triangle is CCW oriented, keep it.
} else if (! backward_is_blocked && ! forward_is_blocked) { }
const auto l_v02 = sqrt(l2_v02); return true;
const Vec2d p0_2 = it0.prev()->cast<double>(); }
const Vec2d p2_2 = it2.next()->cast<double>();
double t = Slic3r::clamp(0., 1., (shortcut_length - l_v02) / ((p2_2 - p0_2).norm() - l_v02)); assert(dist2_current <= shortcut_length2);
it0 = it0.prev().insert((p0 + (p0_2 - p0) * t).cast<coord_t>()); if ((forward == Blocked && backward == Blocked) || dist2_current > sqr(shortcut_length - int64_t(SCALED_EPSILON))) {
it2 = it2.insert((p2 + (p2_2 - p2) * t).cast<coord_t>()); // The crack is filled, keep the last clipping edge.
} else if (! backward_is_blocked) { } else if (dist2_next < sqr(shortcut_length - int64_t(SCALED_EPSILON))) {
it0 = it0.prev().insert(point_on_line_at_dist(p0, Vec2d(it0.prev()->cast<double>()), p2, shortcut_length).cast<coord_t>()); // To avoid creating tiny edges.
} else if (! forward_is_blocked) { if (forward == Far)
it2 = it2.insert(point_on_line_at_dist(p2, Vec2d(it2.next()->cast<double>()), p0, shortcut_length).cast<coord_t>()); it0 = unprocessed_range.remove_back(it0).prev();
if (backward == Far)
it2 = unprocessed_range.remove_front(it2);
if (polygon.size() <= 2)
// A hole degenerated to an empty polygon.
return true;
} else if (forward == Blocked || backward == Blocked) {
// One side is far, the other blocked.
assert(forward == Far || backward == Far);
if (backward == Far) {
// Sort, so we will clip the 1st edge.
std::swap(p0, p2);
std::swap(p02, p22);
}
// Find point on (p0, p02) at distance shortcut_length from p2.
// Circle intersects a line at two points, however because |p2 - p0| < shortcut_length,
// only the second intersection is valid. Because |p2 - p02| > shortcut_length, such
// intersection should always be found on (p0, p02).
const Vec2d v = (p02 - p0).cast<double>();
const Vec2d d = (p0 - p2).cast<double>();
const double a = v.squaredNorm();
const double b = 2. * double(d.dot(v));
double u = b * b - 4. * a * (d.squaredNorm() - shortcut_length2);
assert(u > 0.);
u = sqrt(u);
double t = (- b + u) / (2. * a);
assert(t > 0. && t < 1.);
(backward == Far ? *it2 : *it0) += (v.cast<double>() * t).cast<coord_t>();
} else { } else {
// | // The trapezoid (it0.prev(), it0, it2, it2.next()) is widening. Trim it.
// __|2 assert(forward == Far && backward == Far);
// | / > shortcut cannot be of the desired length assert(dist2_next > shortcut_length2);
// ___|/ . const double dcurrent = sqrt(double(dist2_current));
// 0 double t = (shortcut_length - dcurrent) / (sqrt(double(dist2_next)) - dcurrent);
// both are blocked and p0_it and p2_it are already correct assert(t > 0. && t < 1.);
*it0 += ((p02 - p0).cast<double>() * t).cast<coord_t>();
*it2 += ((p22 - p2).cast<double>() * t).cast<coord_t>();
} }
// Delete all the points between it0 and it2.
while (it0.next() != it2)
it0.next().remove();
return false; return false;
} }
void smooth_outward(MutablePolygon &polygon, double shortcut_length) // adapted from Cura ConstPolygonRef::smooth_outward() by Tim Kuipers.
void smooth_outward(MutablePolygon &polygon, coord_t clip_dist_scaled)
{ {
remove_duplicates(polygon, scaled<double>(0.01)); remove_duplicates(polygon, scaled<double>(0.01));
const int shortcut_length2 = shortcut_length * shortcut_length; const auto clip_dist_scaled2 = sqr<int64_t>(clip_dist_scaled);
static constexpr const double cos_min_angle = -0.70710678118654752440084436210485; // cos(135 degrees) const auto clip_dist_scaled2eps = sqr(clip_dist_scaled + int64_t(SCALED_EPSILON));
const auto foot_dist_min2 = sqr(SCALED_EPSILON);
MutablePolygon::iterator it1 = polygon.begin(); // Each source point will be visited exactly once.
do { MutablePolygon::range unprocessed_range(polygon);
const Vec2d p1 = it1->cast<double>(); while (! unprocessed_range.empty() && polygon.size() > 2) {
auto it0 = it1.prev(); auto it1 = unprocessed_range.process_next();
auto it2 = it1.next(); auto it0 = it1.prev();
const Vec2d p0 = it0->cast<double>(); auto it2 = it1.next();
const Vec2d p2 = it2->cast<double>(); const Point p0 = *it0;
const Vec2d v1 = p0 - p1; const Point p1 = *it1;
const Vec2d v2 = p2 - p1; const Point p2 = *it2;
const double cos_angle = v1.dot(v2); const Vec2i64 v1 = (p0 - p1).cast<int64_t>();
if (cos_angle < cos_min_angle && cross2(v1, v2) < 0) { const Vec2i64 v2 = (p2 - p1).cast<int64_t>();
// Simplify the sharp angle. if (cross2(v1, v2) > 0) {
const Vec2d v02 = p2 - p0; // Concave corner.
const double l2_v02 = v02.squaredNorm(); int64_t dot = v1.dot(v2);
if (l2_v02 >= shortcut_length2) { auto l2v1 = double(v1.squaredNorm());
// Trim an obtuse corner. auto l2v2 = double(v2.squaredNorm());
if (dot > 0 || Slic3r::sqr(double(dot)) * 2. < l2v1 * l2v2) {
// Angle between v1 and v2 bigger than 135 degrees.
// Simplify the sharp angle.
Vec2i64 v02 = (p2 - p0).cast<int64_t>();
int64_t l2v02 = v02.squaredNorm();
it1.remove(); it1.remove();
if (l2_v02 > Slic3r::sqr(shortcut_length + SCALED_EPSILON)) { if (l2v02 < clip_dist_scaled2) {
double l2_1 = v1.squaredNorm(); // (p0, p2) is short.
double l2_2 = v2.squaredNorm(); // Clip a sharp concave corner by possibly expanding the trimming region left of it0 and right of it2.
bool trim = true; // Updates it0, it2 and num_to_process.
if (cos_angle > 0.9999) { if (clip_narrow_corner(p1.cast<int64_t>(), it0, it2, unprocessed_range, l2v02, clip_dist_scaled))
// The triangle p0, p1, p2 is likely degenerate. // Trimmed down to an empty polygon or to a single CCW triangle.
// Measure height of the triangle. return;
double d2 = l2_1 > l2_2 ? line_alg::distance_to_squared(Linef{ p0, p1 }, p2) : line_alg::distance_to_squared(Linef{ p2, p1 }, p0); } else {
if (d2 < Slic3r::sqr(scaled<double>(0.02))) // Clip an obtuse corner.
trim = false; if (l2v02 > clip_dist_scaled2eps) {
} Vec2d v1d = v1.cast<double>();
if (trim) { Vec2d v2d = v2.cast<double>();
Vec2d bisector = v1 / l2_1 + v2 / l2_2; // Sort v1d, v2d, shorter first.
double d1 = v1.dot(bisector) / l2_1; bool swap = l2v1 > l2v2;
double d2 = v2.dot(bisector) / l2_2; if (swap) {
double lbisector = bisector.norm(); std::swap(v1d, v2d);
if (d1 < shortcut_length && d2 < shortcut_length) { std::swap(l2v1, l2v2);
it0.insert((p1 + v1 * (shortcut_length / d1)).cast<coord_t>()) }
.insert((p1 + v2 * (shortcut_length / d2)).cast<coord_t>()); double lv1 = sqrt(l2v1);
} else if (v1.squaredNorm() < v2.squaredNorm()) double lv2 = sqrt(l2v2);
it0.insert(point_on_line_at_dist(p1, p2, p0, shortcut_length).cast<coord_t>()); // Bisector between v1 and v2.
else Vec2d bisector = v1d / lv1 + v2d / lv2;
it0.insert(point_on_line_at_dist(p1, p0, p2, shortcut_length).cast<coord_t>()); double l2bisector = bisector.squaredNorm();
// Squared distance of the end point of v1 to the bisector.
double d2 = l2v1 - sqr(v1d.dot(bisector)) / l2bisector;
if (d2 < foot_dist_min2) {
// Height of the p1, p0, p2 triangle is tiny. Just remove p1.
} else if (d2 < 0.25 * clip_dist_scaled2 + SCALED_EPSILON) {
// The shorter vector is too close to the bisector. Trim the shorter vector fully,
// trim the longer vector partially.
// Intersection of a circle at p2 of radius = clip_dist_scaled
// with a ray (p1, p0), take the intersection after the foot point.
// The intersection shall always exist because |p2 - p1| > clip_dist_scaled.
const double b = - 2. * v1d.cast<double>().dot(v2d);
double u = b * b - 4. * l2v2 * (double(l2v1) - clip_dist_scaled2);
assert(u > 0.);
// Take the second intersection along v2.
double t = (- b + sqrt(u)) / (2. * l2v2);
assert(t > 0. && t < 1.);
Point pt_new = p1 + (t * v2d).cast<coord_t>();
#ifndef NDEBUG
double d2new = (pt_new - (swap ? p2 : p0)).cast<double>().squaredNorm();
assert(std::abs(d2new - clip_dist_scaled2) < sqr(10. * SCALED_EPSILON));
#endif // NDEBUG
it2.insert(pt_new);
} else {
// Cut the corner with a line perpendicular to the bisector.
double t = sqrt(0.25 * clip_dist_scaled2 / d2);
assert(t > 0. && t < 1.);
Point p0 = p1 + (v1d * t).cast<coord_t>();
Point p2 = p1 + (v2d * (t * lv2 / lv1)).cast<coord_t>();
if (swap)
std::swap(p0, p2);
it2.insert(p2).insert(p0);
}
} else {
// Just remove p1.
assert(l2v02 >= clip_dist_scaled2 && l2v02 <= clip_dist_scaled2eps);
} }
} }
} else { it1 = it2;
bool remove_poly = smooth_corner_complex(p1, it0, it2, shortcut_length); // edits p0_it and p2_it! } else
if (remove_poly) { ++ it1;
// don't convert ListPolygon into result } else
return;
}
}
// update:
it1 = it2; // next point to consider for whether it's an internal corner
}
else
++ it1; ++ it1;
} while (it1 != polygon.begin()); }
if (polygon.size() == 3) {
// Check whether the last triangle is clockwise oriented (it is a hole) and its height is below clip_dist_scaled.
// If so, fill in the hole.
const Point p0 = *polygon.begin().prev();
const Point p1 = *polygon.begin();
const Point p2 = *polygon.begin().next();
Vec2i64 v1 = (p0 - p1).cast<int64_t>();
Vec2i64 v2 = (p2 - p1).cast<int64_t>();
if (cross2(v1, v2) > 0) {
// CW triangle. Measure its height.
const Vec2i64 v3 = (p2 - p0).cast<int64_t>();
int64_t l12 = v1.squaredNorm();
int64_t l22 = v2.squaredNorm();
int64_t l32 = v3.squaredNorm();
if (l22 > l12 && l22 > l32) {
std::swap(v1, v2);
std::swap(l12, l22);
} else if (l32 > l12 && l32 > l22) {
v1 = v3;
l12 = l32;
}
auto h2 = l22 - sqr(double(v1.dot(v2))) / double(l12);
if (h2 < clip_dist_scaled2)
// CW triangle with a low height. Close the hole.
polygon.clear();
}
} else if (polygon.size() < 3)
polygon.clear();
} }
} // namespace Slic3r } // namespace Slic3r

View File

@ -6,6 +6,10 @@
namespace Slic3r { namespace Slic3r {
// Polygon implemented as a loop of double linked elements.
// All elements are allocated in a single std::vector<>, thus integer indices are used for
// referencing the previous and next element and inside iterators to survive reallocation
// of the vector.
class MutablePolygon class MutablePolygon
{ {
public: public:
@ -55,6 +59,69 @@ public:
friend class MutablePolygon; friend class MutablePolygon;
MutablePolygon *m_data; MutablePolygon *m_data;
IndexType m_idx; IndexType m_idx;
friend class range;
};
// Iterator range for maintaining a range of unprocessed items, see smooth_outward().
class range
{
public:
range(MutablePolygon& poly) : range(poly.begin(), poly.end()) {}
range(MutablePolygon::iterator begin, MutablePolygon::iterator end) : m_begin(begin), m_end(end) {}
// Start of a range, inclusive. If range is empty, then ! begin().valid().
MutablePolygon::iterator begin() const { return m_begin; }
// End of a range, inclusive. If range is empty, then ! end().valid().
MutablePolygon::iterator end() const { return m_end; }
// Is the range empty?
bool empty() const { return !m_begin.valid(); }
// Return begin() and shorten the range by advancing front.
MutablePolygon::iterator process_next() {
assert(!this->empty());
MutablePolygon::iterator out = m_begin;
this->advance_front();
return out;
}
void advance_front() {
assert(!this->empty());
if (m_begin == m_end)
this->make_empty();
else
++ m_begin;
}
void retract_back() {
assert(!this->empty());
if (m_begin == m_end)
this->make_empty();
else
-- m_end;
}
MutablePolygon::iterator remove_front(MutablePolygon::iterator it) {
if (m_begin == it)
this->advance_front();
return it.remove();
}
MutablePolygon::iterator remove_back(MutablePolygon::iterator it) {
if (m_end == it)
this->retract_back();
return it.remove();
}
private:
// Range from begin to end, inclusive.
// If the range is valid, then both m_begin and m_end are invalid.
MutablePolygon::iterator m_begin;
MutablePolygon::iterator m_end;
void make_empty() {
m_begin.m_idx = -1;
m_end.m_idx = -1;
}
}; };
MutablePolygon() = default; MutablePolygon() = default;
@ -63,26 +130,35 @@ public:
template<typename IT> template<typename IT>
MutablePolygon(IT begin, IT end, size_t reserve = 0) { MutablePolygon(IT begin, IT end, size_t reserve = 0) {
m_size = IndexType(end - begin); this->assign_inner(begin, end, reserve);
if (m_size > 0) { };
m_head = 0;
m_data.reserve(std::max<size_t>(m_size, reserve)); template<typename IT>
auto i = IndexType(-1); void assign(IT begin, IT end, size_t reserve = 0) {
auto j = IndexType(1); m_data.clear();
for (auto it = begin; it != end; ++ it) m_head = IndexType(-1);
m_data.push_back({ *it, i ++, j ++ }); m_head_free = { IndexType(-1) };
m_data.front().prev = m_size - 1; this->assign_inner(begin, end, reserve);
m_data.back ().next = 0; };
void assign(const Polygon &rhs, size_t reserve = 0) {
assign(rhs.points.begin(), rhs.points.end(), reserve);
}
void polygon(Polygon &out) const {
out.points.clear();
if (this->valid()) {
out.points.reserve(this->size());
auto it = this->cbegin();
out.points.emplace_back(*it);
for (++ it; it != this->cbegin(); ++ it)
out.points.emplace_back(*it);
} }
}; };
Polygon polygon() const { Polygon polygon() const {
Polygon out; Polygon out;
if (this->valid()) { this->polygon(out);
out.points.reserve(this->size());
for (auto it = this->cbegin(); it != this->cend(); ++ it)
out.points.emplace_back(*it);
}
return out; return out;
}; };
@ -90,6 +166,7 @@ public:
size_t size() const { return this->m_size; } size_t size() const { return this->m_size; }
size_t capacity() const { return this->m_data.capacity(); } size_t capacity() const { return this->m_data.capacity(); }
bool valid() const { return this->m_size >= 3; } bool valid() const { return this->m_size >= 3; }
void clear() { m_data.clear(); m_size = 0; m_head = IndexType(-1); m_head_free = IndexType(-1); }
iterator begin() { return { this, m_head }; } iterator begin() { return { this, m_head }; }
const_iterator cbegin() const { return { this, m_head }; } const_iterator cbegin() const { return { this, m_head }; }
@ -108,8 +185,11 @@ public:
private: private:
struct LinkedPoint { struct LinkedPoint {
// 8 bytes
PointType point; PointType point;
// 4 bytes
IndexType prev; IndexType prev;
// 4 bytes
IndexType next; IndexType next;
}; };
std::vector<LinkedPoint> m_data; std::vector<LinkedPoint> m_data;
@ -122,6 +202,21 @@ private:
LinkedPoint& at(IndexType i) { return m_data[i]; } LinkedPoint& at(IndexType i) { return m_data[i]; }
const LinkedPoint& at(IndexType i) const { return m_data[i]; } const LinkedPoint& at(IndexType i) const { return m_data[i]; }
template<typename IT>
void assign_inner(IT begin, IT end, size_t reserve) {
m_size = IndexType(end - begin);
if (m_size > 0) {
m_head = 0;
m_data.reserve(std::max<size_t>(m_size, reserve));
auto i = IndexType(-1);
auto j = IndexType(1);
for (auto it = begin; it != end; ++ it)
m_data.push_back({ *it, i ++, j ++ });
m_data.front().prev = m_size - 1;
m_data.back ().next = 0;
}
};
IndexType remove(const IndexType i) { IndexType remove(const IndexType i) {
assert(i >= 0); assert(i >= 0);
assert(m_size > 0); assert(m_size > 0);
@ -213,13 +308,26 @@ inline bool operator!=(const MutablePolygon &p1, const MutablePolygon &p2) { ret
void remove_duplicates(MutablePolygon &polygon); void remove_duplicates(MutablePolygon &polygon);
void remove_duplicates(MutablePolygon &polygon, double eps); void remove_duplicates(MutablePolygon &polygon, double eps);
void smooth_outward(MutablePolygon &polygon, double shortcut_length); void smooth_outward(MutablePolygon &polygon, coord_t clip_dist_scaled);
inline Polygon smooth_outward(const Polygon &polygon, double shortcut_length) inline Polygon smooth_outward(Polygon polygon, coord_t clip_dist_scaled)
{ {
MutablePolygon mp(polygon, polygon.size() * 2); MutablePolygon mp(polygon, polygon.size() * 2);
smooth_outward(mp, shortcut_length); smooth_outward(mp, clip_dist_scaled);
return mp.polygon(); mp.polygon(polygon);
return polygon;
}
inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled)
{
MutablePolygon mp;
for (Polygon &polygon : polygons) {
mp.assign(polygon, polygon.size() * 2);
smooth_outward(mp, clip_dist_scaled);
mp.polygon(polygon);
}
polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const auto &p){ return p.empty(); }), polygons.end());
return polygons;
} }
} }

View File

@ -1245,7 +1245,7 @@ static inline bool sequential_print_vertical_clearance_valid(const Print &print)
} }
// Precondition: Print::validate() requires the Print::apply() to be called its invocation. // Precondition: Print::validate() requires the Print::apply() to be called its invocation.
std::string Print::validate() const std::string Print::validate(std::string* warning) const
{ {
if (m_objects.empty()) if (m_objects.empty())
return L("All objects are outside of the print volume."); return L("All objects are outside of the print volume.");
@ -1440,7 +1440,22 @@ std::string Print::validate() const
} }
} }
} }
// Do we have custom support data that would not be used?
// Notify the user in that case.
if (! object->has_support() && warning) {
for (const ModelVolume* mv : object->model_object()->volumes) {
bool has_enforcers = mv->is_support_enforcer()
|| (mv->is_model_part()
&& ! mv->supported_facets.empty()
&& ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty());
if (has_enforcers) {
*warning = "_SUPPORTS_OFF";
break;
}
}
}
// validate first_layer_height // validate first_layer_height
double first_layer_height = object->config().get_abs_value("first_layer_height"); double first_layer_height = object->config().get_abs_value("first_layer_height");
double first_layer_min_nozzle_diameter; double first_layer_min_nozzle_diameter;

View File

@ -444,7 +444,7 @@ public:
bool has_brim() const; bool has_brim() const;
// Returns an empty string if valid, otherwise returns an error message. // Returns an empty string if valid, otherwise returns an error message.
std::string validate() const override; std::string validate(std::string* warning = nullptr) const override;
double skirt_first_layer_height() const; double skirt_first_layer_height() const;
Flow brim_flow() const; Flow brim_flow() const;
Flow skirt_flow() const; Flow skirt_flow() const;

View File

@ -366,7 +366,7 @@ public:
virtual std::vector<ObjectID> print_object_ids() const = 0; virtual std::vector<ObjectID> print_object_ids() const = 0;
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started. // Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
virtual std::string validate() const { return std::string(); } virtual std::string validate(std::string* warning = nullptr) const { return std::string(); }
enum ApplyStatus { enum ApplyStatus {
// No change after the Print::apply() call. // No change after the Print::apply() call.

View File

@ -430,6 +430,24 @@ void PrintObject::generate_support_material()
if (layer->empty()) if (layer->empty())
throw Slic3r::SlicingError("Levitating objects cannot be printed without supports."); throw Slic3r::SlicingError("Levitating objects cannot be printed without supports.");
#endif #endif
// Do we have custom support data that would not be used?
// Notify the user in that case.
if (! this->has_support()) {
for (const ModelVolume* mv : this->model_object()->volumes) {
bool has_enforcers = mv->is_support_enforcer()
|| (mv->is_model_part()
&& ! mv->supported_facets.empty()
&& ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty());
if (has_enforcers) {
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
L("An object has custom support enforcers which will not be used "
"because supports are off. Consider turning them on.") + "\n" +
(L("Object name")) + ": " + this->model_object()->name);
break;
}
}
}
} }
this->set_done(posSupportMaterial); this->set_done(posSupportMaterial);
} }

View File

@ -122,7 +122,7 @@ IndexedMesh::hit_result
IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
{ {
assert(is_approx(dir.norm(), 1.)); assert(is_approx(dir.norm(), 1.));
igl::Hit hit; igl::Hit hit{-1, -1, 0.f, 0.f, 0.f};
hit.t = std::numeric_limits<float>::infinity(); hit.t = std::numeric_limits<float>::infinity();
#ifdef SLIC3R_HOLE_RAYCASTER #ifdef SLIC3R_HOLE_RAYCASTER

View File

@ -617,7 +617,7 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const
return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config); return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config);
} }
std::string SLAPrint::validate() const std::string SLAPrint::validate(std::string*) const
{ {
for(SLAPrintObject * po : m_objects) { for(SLAPrintObject * po : m_objects) {

View File

@ -458,7 +458,7 @@ public:
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; } const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
std::string validate() const override; std::string validate(std::string* warning = nullptr) const override;
// An aggregation of SliceRecord-s from all the print objects for each // An aggregation of SliceRecord-s from all the print objects for each
// occupied layer. Slice record levels dont have to match exactly. // occupied layer. Slice record levels dont have to match exactly.

View File

@ -430,10 +430,10 @@ bool BackgroundSlicingProcess::empty() const
return m_print->empty(); return m_print->empty();
} }
std::string BackgroundSlicingProcess::validate() std::string BackgroundSlicingProcess::validate(std::string* warning)
{ {
assert(m_print != nullptr); assert(m_print != nullptr);
return m_print->validate(); return m_print->validate(warning);
} }
// Apply config over the print. Returns false, if the new config values caused any of the already // Apply config over the print. Returns false, if the new config values caused any of the already

View File

@ -131,7 +131,7 @@ public:
bool empty() const; bool empty() const;
// Validate the print. Returns an empty string if valid, returns an error message if invalid. // Validate the print. Returns an empty string if valid, returns an error message if invalid.
// Call validate before calling start(). // Call validate before calling start().
std::string validate(); std::string validate(std::string* warning = nullptr);
// Set the export path of the G-code. // Set the export path of the G-code.
// Once the path is set, the G-code // Once the path is set, the G-code

View File

@ -16,6 +16,7 @@
#include "slic3r/GUI/GUI_ObjectSettings.hpp" #include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/NotificationManager.hpp"
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "libslic3r/SLAPrint.hpp" #include "libslic3r/SLAPrint.hpp"
@ -1161,6 +1162,7 @@ void GLGizmoSlaSupports::disable_editing_mode()
m_c->instances_hider()->show_supports(true); m_c->instances_hider()->show_supports(true);
m_parent.set_as_dirty(); m_parent.set_as_dirty();
} }
wxGetApp().plater()->get_notification_manager()->close_notification_of_type(NotificationType::QuitSLAManualMode);
} }

View File

@ -1147,9 +1147,11 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const
return false; return false;
if (error_notification) if (error_notification)
wxGetApp().plater()->get_notification_manager()->push_slicing_error_notification( wxGetApp().plater()->get_notification_manager()->push_notification(
_u8L("You are currently editing SLA support points. Please, apply or discard " NotificationType::QuitSLAManualMode,
"your changes first.")); NotificationManager::NotificationLevel::ErrorNotification,
_u8L("You are currently editing SLA support points. Please, "
"apply or discard your changes first."));
return true; return true;
} }

View File

@ -71,7 +71,11 @@ enum class NotificationType
// Notification that custom supports/seams were deleted after mesh repair. // Notification that custom supports/seams were deleted after mesh repair.
CustomSupportsAndSeamRemovedAfterRepair, CustomSupportsAndSeamRemovedAfterRepair,
// Notification that auto adding of color changes is impossible // Notification that auto adding of color changes is impossible
EmptyAutoColorChange EmptyAutoColorChange,
// Notification emitted by Print::validate
PrintValidateWarning,
// Notification telling user to quit SLA supports manual editing
QuitSLAManualMode
}; };
class NotificationManager class NotificationManager

View File

@ -1599,6 +1599,8 @@ struct Plater::priv
void suppress_snapshots() { this->m_prevent_snapshots++; } void suppress_snapshots() { this->m_prevent_snapshots++; }
void allow_snapshots() { this->m_prevent_snapshots--; } void allow_snapshots() { this->m_prevent_snapshots--; }
void process_validation_warning(const std::string& warning) const;
bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; } bool background_processing_enabled() const { return this->get_config("background_processing") == "1"; }
void update_print_volume_state(); void update_print_volume_state();
void schedule_background_process(); void schedule_background_process();
@ -2787,6 +2789,41 @@ void Plater::priv::update_print_volume_state()
this->q->model().update_print_volume_state(print_volume); this->q->model().update_print_volume_state(print_volume);
} }
void Plater::priv::process_validation_warning(const std::string& warning) const
{
if (warning.empty())
notification_manager->close_notification_of_type(NotificationType::PrintValidateWarning);
else {
std::string text = warning;
std::string hypertext = "";
std::function<bool(wxEvtHandler*)> action_fn = [](wxEvtHandler*){ return false; };
if (text == "_SUPPORTS_OFF") {
text = _u8L("An object has custom support enforcers which will not be used "
"because supports are disabled.")+"\n";
hypertext = _u8L("Enable supports for enforcers only");
action_fn = [](wxEvtHandler*) {
Tab* print_tab = wxGetApp().get_tab(Preset::TYPE_PRINT);
assert(print_tab);
DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
config.set_key_value("support_material", new ConfigOptionBool(true));
config.set_key_value("support_material_auto", new ConfigOptionBool(false));
print_tab->on_value_change("support_material", config.opt_bool("support_material"));
print_tab->on_value_change("support_material_auto", config.opt_bool("support_material_auto"));
return true;
};
}
notification_manager->push_notification(
NotificationType::PrintValidateWarning,
NotificationManager::NotificationLevel::ImportantNotification,
text, hypertext, action_fn
);
}
}
// Update background processing thread from the current config and Model. // Update background processing thread from the current config and Model.
// Returns a bitmask of UpdateBackgroundProcessReturnState. // Returns a bitmask of UpdateBackgroundProcessReturnState.
unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages) unsigned int Plater::priv::update_background_process(bool force_validation, bool postpone_error_messages)
@ -2829,17 +2866,23 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// The delayed error message is no more valid. // The delayed error message is no more valid.
this->delayed_error_message.clear(); this->delayed_error_message.clear();
// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors. // The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
std::string err = this->background_process.validate(); std::string warning;
std::string err = this->background_process.validate(&warning);
if (err.empty()) { if (err.empty()) {
notification_manager->set_all_slicing_errors_gray(true); notification_manager->set_all_slicing_errors_gray(true);
if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled()) if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART; return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
// Pass a warning from validation and either show a notification,
// or hide the old one.
process_validation_warning(warning);
} else { } else {
// The print is not valid. // The print is not valid.
// Show error as notification. // Show error as notification.
notification_manager->push_slicing_error_notification(err); notification_manager->push_slicing_error_notification(err);
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
} }
} else if (! this->delayed_error_message.empty()) { } else if (! this->delayed_error_message.empty()) {
// Reusing the old state. // Reusing the old state.
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID; return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
@ -5385,7 +5428,9 @@ void Plater::export_amf()
void Plater::export_3mf(const boost::filesystem::path& output_path) void Plater::export_3mf(const boost::filesystem::path& output_path)
{ {
if (p->model.objects.empty()) { return; } if (p->model.objects.empty()
|| canvas3D()->get_gizmos_manager().is_in_editing_mode(true))
return;
wxString path; wxString path;
bool export_config = true; bool export_config = true;

View File

@ -1,5 +1,6 @@
#include <catch2/catch.hpp> #include <catch2/catch.hpp>
#include "libslic3r/Point.hpp"
#include "libslic3r/MutablePolygon.hpp" #include "libslic3r/MutablePolygon.hpp"
using namespace Slic3r; using namespace Slic3r;
@ -143,3 +144,36 @@ SCENARIO("Remove degenerate points from MutablePolygon", "[MutablePolygon]") {
} }
} }
} }
SCENARIO("smooth_outward", "[MutablePolygon]") {
GIVEN("Convex polygon") {
MutablePolygon p{ { 0, 0 }, { scaled<coord_t>(10.), 0 }, { 0, scaled<coord_t>(10.) } };
WHEN("smooth_outward") {
MutablePolygon p2{ p };
smooth_outward(p2, scaled<double>(10.));
THEN("Polygon is unmodified") {
REQUIRE(p == p2);
}
}
}
GIVEN("Sharp tiny concave polygon (hole)") {
MutablePolygon p{ { 0, 0 }, { 0, scaled<coord_t>(5.) }, { scaled<coord_t>(10.), 0 } };
WHEN("smooth_outward") {
MutablePolygon p2{ p };
smooth_outward(p2, scaled<double>(10.));
THEN("Hole is closed") {
REQUIRE(p2.empty());
}
}
}
GIVEN("Two polygons") {
Polygons p{ { { 0, 0 }, { scaled<coord_t>(10.), 0 }, { 0, scaled<coord_t>(10.) } },
{ { 0, 0 }, { 0, scaled<coord_t>(5.) }, { scaled<coord_t>(10.), 0 } } };
WHEN("smooth_outward") {
p = smooth_outward(p, scaled<double>(10.));
THEN("CCW contour unmodified, CW contour removed.") {
REQUIRE(p == Polygons{ { { 0, 0 }, { scaled<coord_t>(10.), 0 }, { 0, scaled<coord_t>(10.) } } });
}
}
}
}