mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-26 10:11:10 -06:00
ExtruderSequenceDialog :
Fixed layouts after the second opening of the dialog. (Removing any extruder from the sequence does not cause an incorrect layout) Validation of entered values added (0 is not a valid value)
This commit is contained in:
commit
bc68b8eaf2
85 changed files with 5505 additions and 554 deletions
|
|
@ -167,6 +167,7 @@ int CLI::run(int argc, char **argv)
|
|||
// sla_print_config.apply(m_print_config, true);
|
||||
|
||||
// Loop through transform options.
|
||||
bool user_center_specified = false;
|
||||
for (auto const &opt_key : m_transforms) {
|
||||
if (opt_key == "merge") {
|
||||
Model m;
|
||||
|
|
@ -209,6 +210,7 @@ int CLI::run(int argc, char **argv)
|
|||
for (auto &model : m_models)
|
||||
model.duplicate_objects_grid(x, y, (distance > 0) ? distance : 6); // TODO: this is not the right place for setting a default
|
||||
} else if (opt_key == "center") {
|
||||
user_center_specified = true;
|
||||
for (auto &model : m_models) {
|
||||
model.add_default_instances();
|
||||
// this affects instances:
|
||||
|
|
@ -403,7 +405,9 @@ int CLI::run(int argc, char **argv)
|
|||
if (! m_config.opt_bool("dont_arrange")) {
|
||||
//FIXME make the min_object_distance configurable.
|
||||
model.arrange_objects(fff_print.config().min_object_distance());
|
||||
model.center_instances_around_point(m_config.option<ConfigOptionPoint>("center")->value);
|
||||
model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ?
|
||||
BoundingBoxf(m_print_config.opt<ConfigOptionPoints>("bed_shape")->values).center() :
|
||||
m_config.option<ConfigOptionPoint>("center")->value);
|
||||
}
|
||||
if (printer_technology == ptFFF) {
|
||||
for (auto* mo : model.objects)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ add_library(libslic3r STATIC
|
|||
Format/STL.hpp
|
||||
GCode/Analyzer.cpp
|
||||
GCode/Analyzer.hpp
|
||||
GCode/ThumbnailData.cpp
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
GCode/CoolingBuffer.hpp
|
||||
GCode/PostProcessor.cpp
|
||||
|
|
|
|||
|
|
@ -951,6 +951,7 @@ ClipperLib::Paths fix_after_inner_offset(const ClipperLib::Path &input, ClipperL
|
|||
ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::vector<float> &deltas, double miter_limit)
|
||||
{
|
||||
assert(contour.size() == deltas.size());
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify that the deltas are either all positive, or all negative.
|
||||
bool positive = false;
|
||||
|
|
@ -986,7 +987,10 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
|
|||
double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
|
||||
double l2min = lmin * lmin;
|
||||
// Minimum angle to consider two edges to be parallel.
|
||||
double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE);
|
||||
// Vojtech's estimate.
|
||||
// const double sin_min_parallel = EPSILON + 1. / double(CLIPPER_OFFSET_SCALE);
|
||||
// Implementation equal to Clipper.
|
||||
const double sin_min_parallel = 1.;
|
||||
|
||||
// Find the last point further from pt by l2min.
|
||||
Vec2d pt = contour.front().cast<double>();
|
||||
|
|
@ -1012,8 +1016,12 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
|
|||
if (l2 > l2min)
|
||||
break;
|
||||
}
|
||||
if (j > ilast)
|
||||
if (j > ilast) {
|
||||
assert(i <= ilast);
|
||||
// If the last edge is too short, merge it with the previous edge.
|
||||
i = ilast;
|
||||
ptnext = contour.front().cast<double>();
|
||||
}
|
||||
|
||||
// Normal to the (ptnext - pt) segment.
|
||||
Vec2d nnext = perp(ptnext - pt).normalized();
|
||||
|
|
@ -1026,27 +1034,29 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
|
|||
add_offset_point(pt + nprev * delta);
|
||||
add_offset_point(pt);
|
||||
add_offset_point(pt + nnext * delta);
|
||||
} else if (convex < sin_min_parallel) {
|
||||
// Nearly parallel.
|
||||
add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt);
|
||||
} else {
|
||||
// Convex corner
|
||||
double dot = nprev.dot(nnext);
|
||||
double r = 1. + dot;
|
||||
if (r >= miter_limit)
|
||||
add_offset_point(pt + (nprev + nnext) * (delta / r));
|
||||
else {
|
||||
double dx = std::tan(std::atan2(sin_a, dot) / 4.);
|
||||
Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta;
|
||||
Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta;
|
||||
if (convex < sin_min_parallel && dot > 0.) {
|
||||
// Nearly parallel.
|
||||
add_offset_point((nprev.dot(nnext) > 0.) ? (pt + nprev * delta) : pt);
|
||||
} else {
|
||||
// Convex corner, possibly extremely sharp if convex < sin_min_parallel.
|
||||
double r = 1. + dot;
|
||||
if (r >= miter_limit)
|
||||
add_offset_point(pt + (nprev + nnext) * (delta / r));
|
||||
else {
|
||||
double dx = std::tan(std::atan2(sin_a, dot) / 4.);
|
||||
Vec2d newpt1 = pt + (nprev - perp(nprev) * dx) * delta;
|
||||
Vec2d newpt2 = pt + (nnext + perp(nnext) * dx) * delta;
|
||||
#ifndef NDEBUG
|
||||
Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt;
|
||||
double dist_norm = vedge.norm();
|
||||
assert(std::abs(dist_norm - delta) < EPSILON);
|
||||
Vec2d vedge = 0.5 * (newpt1 + newpt2) - pt;
|
||||
double dist_norm = vedge.norm();
|
||||
assert(std::abs(dist_norm - std::abs(delta)) < SCALED_EPSILON);
|
||||
#endif /* NDEBUG */
|
||||
add_offset_point(newpt1);
|
||||
add_offset_point(newpt2);
|
||||
}
|
||||
add_offset_point(newpt1);
|
||||
add_offset_point(newpt2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == ilast)
|
||||
|
|
@ -1195,7 +1205,7 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<s
|
|||
{
|
||||
#ifndef NDEBUG
|
||||
// Verify that the deltas are all non positive.
|
||||
for (const std::vector<float>& ds : deltas)
|
||||
for (const std::vector<float>& ds : deltas)
|
||||
for (float delta : ds)
|
||||
assert(delta <= 0.);
|
||||
assert(expoly.holes.size() + 1 == deltas.size());
|
||||
|
|
|
|||
|
|
@ -46,11 +46,29 @@ void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution)
|
|||
++ ncontours;
|
||||
|
||||
// Collect the contours.
|
||||
m_contours.assign(ncontours, NULL);
|
||||
m_contours.assign(ncontours, nullptr);
|
||||
ncontours = 0;
|
||||
for (size_t j = 0; j < polygons.size(); ++ j)
|
||||
if (! polygons[j].points.empty())
|
||||
m_contours[ncontours++] = &polygons[j].points;
|
||||
m_contours[ncontours ++] = &polygons[j].points;
|
||||
|
||||
create_from_m_contours(resolution);
|
||||
}
|
||||
|
||||
void EdgeGrid::Grid::create(const std::vector<Points> &polygons, coord_t resolution)
|
||||
{
|
||||
// Count the contours.
|
||||
size_t ncontours = 0;
|
||||
for (size_t j = 0; j < polygons.size(); ++ j)
|
||||
if (! polygons[j].empty())
|
||||
++ ncontours;
|
||||
|
||||
// Collect the contours.
|
||||
m_contours.assign(ncontours, nullptr);
|
||||
ncontours = 0;
|
||||
for (size_t j = 0; j < polygons.size(); ++ j)
|
||||
if (! polygons[j].empty())
|
||||
m_contours[ncontours ++] = &polygons[j];
|
||||
|
||||
create_from_m_contours(resolution);
|
||||
}
|
||||
|
|
@ -66,7 +84,7 @@ void EdgeGrid::Grid::create(const ExPolygon &expoly, coord_t resolution)
|
|||
++ ncontours;
|
||||
|
||||
// Collect the contours.
|
||||
m_contours.assign(ncontours, NULL);
|
||||
m_contours.assign(ncontours, nullptr);
|
||||
ncontours = 0;
|
||||
if (! expoly.contour.points.empty())
|
||||
m_contours[ncontours++] = &expoly.contour.points;
|
||||
|
|
@ -91,7 +109,7 @@ void EdgeGrid::Grid::create(const ExPolygons &expolygons, coord_t resolution)
|
|||
}
|
||||
|
||||
// Collect the contours.
|
||||
m_contours.assign(ncontours, NULL);
|
||||
m_contours.assign(ncontours, nullptr);
|
||||
ncontours = 0;
|
||||
for (size_t i = 0; i < expolygons.size(); ++ i) {
|
||||
const ExPolygon &expoly = expolygons[i];
|
||||
|
|
@ -1122,7 +1140,7 @@ EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt
|
|||
Vec2d vfoot = foot - pt.cast<double>();
|
||||
double dist_foot = vfoot.norm();
|
||||
double dist_foot_err = dist_foot - d_min;
|
||||
assert(std::abs(dist_foot_err) < 1e-7 * d_min);
|
||||
assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * d_min);
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
}
|
||||
|
|
@ -1145,7 +1163,7 @@ EdgeGrid::Grid::ClosestPointResult EdgeGrid::Grid::closest_point(const Point &pt
|
|||
vfoot = p1.cast<double>() * (1. - result.t) + p2.cast<double>() * result.t - pt.cast<double>();
|
||||
double dist_foot = vfoot.norm();
|
||||
double dist_foot_err = dist_foot - std::abs(result.distance);
|
||||
assert(std::abs(dist_foot_err) < 1e-7 * std::abs(result.distance));
|
||||
assert(std::abs(dist_foot_err) < 1e-7 || std::abs(dist_foot_err) < 1e-7 * std::abs(result.distance));
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
} else
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public:
|
|||
void set_bbox(const BoundingBox &bbox) { m_bbox = bbox; }
|
||||
|
||||
void create(const Polygons &polygons, coord_t resolution);
|
||||
void create(const std::vector<Points> &polygons, coord_t resolution);
|
||||
void create(const ExPolygon &expoly, coord_t resolution);
|
||||
void create(const ExPolygons &expolygons, coord_t resolution);
|
||||
void create(const ExPolygonCollection &expolygons, coord_t resolution);
|
||||
|
|
@ -208,6 +209,25 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
template<typename VISITOR> void visit_cells_intersecting_box(BoundingBox bbox, VISITOR &visitor) const
|
||||
{
|
||||
// End points of the line segment.
|
||||
bbox.min -= m_bbox.min;
|
||||
bbox.max -= m_bbox.min + Point(1, 1);
|
||||
// Get the cells of the end points.
|
||||
bbox.min /= m_resolution;
|
||||
bbox.max /= m_resolution;
|
||||
// Trim with the cells.
|
||||
bbox.min.x() = std::max(bbox.min.x(), 0);
|
||||
bbox.min.y() = std::max(bbox.min.y(), 0);
|
||||
bbox.max.x() = std::min(bbox.max.x(), (coord_t)m_cols - 1);
|
||||
bbox.max.y() = std::min(bbox.max.y(), (coord_t)m_rows - 1);
|
||||
for (coord_t iy = bbox.min.y(); iy <= bbox.max.y(); ++ iy)
|
||||
for (coord_t ix = bbox.min.x(); ix <= bbox.max.x(); ++ ix)
|
||||
if (! visitor(iy, ix))
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<std::vector<std::pair<size_t, size_t>>::const_iterator, std::vector<std::pair<size_t, size_t>>::const_iterator> cell_data_range(coord_t row, coord_t col) const
|
||||
{
|
||||
const EdgeGrid::Grid::Cell &cell = m_cells[row * m_cols + col];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "Flow.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cassert>
|
||||
|
|
@ -26,6 +27,10 @@ struct ResampledPoint {
|
|||
double curve_parameter;
|
||||
};
|
||||
|
||||
// Distance calculated using SDF (Shape Diameter Function).
|
||||
// The distance is calculated by casting a fan of rays and measuring the intersection distance.
|
||||
// Thus the calculation is relatively slow. For the Elephant foot compensation purpose, this distance metric does not avoid
|
||||
// pinching off small pieces of a contour, thus this function has been superseded by contour_distance2().
|
||||
std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double search_radius)
|
||||
{
|
||||
assert(! contour.empty());
|
||||
|
|
@ -60,9 +65,9 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
for (size_t axis = 0; axis < 2; ++ axis) {
|
||||
double dx = std::abs(dir(axis));
|
||||
if (dx >= EPSILON) {
|
||||
double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - EPSILON);
|
||||
double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - SCALED_EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - SCALED_EPSILON);
|
||||
if (tedge < dx)
|
||||
t = tedge / dx;
|
||||
t = std::min(t, tedge / dx);
|
||||
}
|
||||
}
|
||||
this->dir = dir;
|
||||
|
|
@ -70,6 +75,7 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
dir *= t;
|
||||
this->pt_end = (this->pt + dir).cast<coord_t>();
|
||||
this->t_min = 1.;
|
||||
assert(this->grid.bbox().contains(this->pt_start) && this->grid.bbox().contains(this->pt_end));
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
|
|
@ -88,7 +94,7 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
|
||||
if (std::abs(denom) >= EPSILON) {
|
||||
double t = cross2(dir2, vptpt2) / denom;
|
||||
assert(t > 0. && t <= 1.);
|
||||
assert(t > - EPSILON && t < 1. + EPSILON);
|
||||
bool this_valid = true;
|
||||
if (it_contour_and_segment->first == idx_contour) {
|
||||
// The intersected segment originates from the same contour as the starting point.
|
||||
|
|
@ -105,7 +111,7 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
|
||||
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
|
||||
double t2 = cross2(dir, vptpt2) / denom;
|
||||
assert(t2 >= 0. && t2 <= 1.);
|
||||
assert(t2 > - EPSILON && t2 < 1. + EPSILON);
|
||||
if (++ ipt == ipts.size())
|
||||
param_hi = t2 * dir2.norm();
|
||||
else
|
||||
|
|
@ -166,7 +172,7 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
svg.draw(*pt_this, "red", scale_(0.1));
|
||||
svg.draw(*pt_this, "red", coord_t(scale_(0.1)));
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
|
||||
for (int i = - num_rays + 1; i < num_rays; ++ i) {
|
||||
|
|
@ -181,7 +187,7 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01));
|
||||
if (visitor.t_min < 1.) {
|
||||
Vec2d pt = visitor.pt + visitor.dir * visitor.t_min;
|
||||
svg.draw(Point(pt), "red", scale_(0.1));
|
||||
svg.draw(Point(pt), "red", coord_t(scale_(0.1)));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
|
|
@ -208,7 +214,7 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
out.emplace_back(float(distances.front() * search_radius));
|
||||
#endif
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, &pt_next - contour.data(), unscale<double>(out.back()));
|
||||
printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, int(&pt_next - contour.data()), unscale<double>(out.back()));
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
pt_this = &pt_next;
|
||||
idx_pt_this = &pt_next - contour.data();
|
||||
|
|
@ -222,6 +228,188 @@ std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx
|
|||
return out;
|
||||
}
|
||||
|
||||
// Contour distance by measuring the closest point of an ExPolygon stored inside the EdgeGrid, while filtering out points of the same contour
|
||||
// at concave regions, or convex regions with low curvature (curvature is estimated as a ratio between contour length and chordal distance crossing the contour ends).
|
||||
std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double compensation, double search_radius)
|
||||
{
|
||||
assert(! contour.empty());
|
||||
assert(contour.size() >= 2);
|
||||
|
||||
std::vector<float> out;
|
||||
|
||||
if (contour.size() > 2)
|
||||
{
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(contour);
|
||||
bbox.merge(grid.bbox());
|
||||
ExPolygon expoly_grid;
|
||||
expoly_grid.contour = Polygon(*grid.contours().front());
|
||||
for (size_t i = 1; i < grid.contours().size(); ++ i)
|
||||
expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
|
||||
#endif
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_accept, double dist_same_contour_reject) :
|
||||
grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {}
|
||||
|
||||
void init(const Points &contour, const Point &apoint) {
|
||||
this->idx_point = &apoint - contour.data();
|
||||
this->point = apoint;
|
||||
this->found = false;
|
||||
this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = this->grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
std::pair<const Point&, const Point&> segment = this->grid.segment(*it_contour_and_segment);
|
||||
const Vec2d v = (segment.second - segment.first).cast<double>();
|
||||
const Vec2d va = (this->point - segment.first).cast<double>();
|
||||
const double l2 = v.squaredNorm(); // avoid a sqrt
|
||||
const double t = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2);
|
||||
// Closest point from this->point to the segment.
|
||||
const Vec2d foot = segment.first.cast<double>() + t * v;
|
||||
const Vec2d bisector = foot - this->point.cast<double>();
|
||||
const double dist = bisector.norm();
|
||||
if ((! this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) {
|
||||
bool accept = true;
|
||||
if (it_contour_and_segment->first == idx_contour) {
|
||||
// Complex case: The closest segment originates from the same contour as the starting point.
|
||||
// Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector (this->pt, foot).
|
||||
double param_lo = resampled_point_parameters[this->idx_point].curve_parameter;
|
||||
double param_hi;
|
||||
double param_end = resampled_point_parameters.back().curve_parameter;
|
||||
const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first];
|
||||
const size_t ipt = it_contour_and_segment->second;
|
||||
{
|
||||
ResampledPoint key(ipt, false, 0.);
|
||||
auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
|
||||
auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
|
||||
assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
|
||||
param_hi = t * sqrt(l2);
|
||||
if (ipt + 1 < ipts.size())
|
||||
param_hi += it->curve_parameter;
|
||||
}
|
||||
if (param_lo > param_hi)
|
||||
std::swap(param_lo, param_hi);
|
||||
assert(param_lo > - SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON);
|
||||
assert(param_hi > - SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON);
|
||||
double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi);
|
||||
if (dist_along_contour < dist_same_contour_accept)
|
||||
accept = false;
|
||||
else if (dist < dist_same_contour_reject + SCALED_EPSILON) {
|
||||
// this->point is close to foot. This point will only be accepted if the path along the contour is significantly
|
||||
// longer than the bisector. That is, the path shall not bulge away from the bisector too much.
|
||||
// Bulge is estimated by 0.6 of the circle circumference drawn around the bisector.
|
||||
// Test whether the contour is convex or concave.
|
||||
bool inside =
|
||||
(t == 0.) ? this->inside_corner(ipts, ipt, this->point) :
|
||||
(t == 1.) ? this->inside_corner(ipts, ipt + 1 == ipts.size() ? 0 : ipt + 1, this->point) :
|
||||
this->left_of_segment(ipts, ipt, this->point);
|
||||
accept = inside && dist_along_contour > 0.6 * M_PI * dist;
|
||||
}
|
||||
}
|
||||
if (accept && (! this->found || dist < this->distance)) {
|
||||
// Simple case: Just measure the shortest distance.
|
||||
this->distance = dist;
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
this->closest_point = foot.cast<coord_t>();
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
this->found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const size_t idx_contour;
|
||||
const Points &contour;
|
||||
const std::vector<ResampledPoint> &resampled_point_parameters;
|
||||
const double dist_same_contour_accept;
|
||||
const double dist_same_contour_reject;
|
||||
|
||||
size_t idx_point;
|
||||
Point point;
|
||||
// Direction inside the contour from idx_point, not normalized.
|
||||
Vec2d dir_inside;
|
||||
bool found;
|
||||
double distance;
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
Point closest_point;
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
|
||||
private:
|
||||
static Vec2d dir_inside_at_point(const Points &contour, size_t i) {
|
||||
size_t iprev = prev_idx_modulo(i, contour);
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
|
||||
Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
|
||||
return Vec2d(- v1.y() - v2.y(), v1.x() + v2.x());
|
||||
}
|
||||
static Vec2d dir_inside_at_segment(const Points& contour, size_t i) {
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v = (contour[inext] - contour[i]).cast<double>();
|
||||
return Vec2d(- v.y(), v.x());
|
||||
}
|
||||
|
||||
static bool inside_corner(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) {
|
||||
const Vec2d pt = pt_oposite.cast<double>();
|
||||
size_t iprev = prev_idx_modulo(i, contour);
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
|
||||
Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
|
||||
bool left_of_v1 = cross2(v1, pt - contour[iprev].cast<double>()) > 0.;
|
||||
bool left_of_v2 = cross2(v2, pt - contour[i ].cast<double>()) > 0.;
|
||||
return cross2(v1, v2) > 0 ?
|
||||
left_of_v1 && left_of_v2 : // convex corner
|
||||
left_of_v1 || left_of_v2; // concave corner
|
||||
}
|
||||
static bool left_of_segment(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) {
|
||||
const Vec2d pt = pt_oposite.cast<double>();
|
||||
size_t inext = next_idx_modulo(i, contour);
|
||||
Vec2d v = (contour[inext] - contour[i]).cast<double>();
|
||||
return cross2(v, pt - contour[i].cast<double>()) > 0.;
|
||||
}
|
||||
} visitor(grid, idx_contour, resampled_point_parameters, 0.5 * compensation * M_PI, search_radius);
|
||||
|
||||
out.reserve(contour.size());
|
||||
Point radius_vector(search_radius, search_radius);
|
||||
for (const Point &pt : contour) {
|
||||
visitor.init(contour, pt);
|
||||
grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor);
|
||||
out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius));
|
||||
|
||||
#if 0
|
||||
//#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
if (out.back() < search_radius) {
|
||||
SVG svg(debug_out_path("contour_distance_filtered-%d-%d.svg", iRun, int(&pt - contour.data())).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
svg.draw(pt, "green", coord_t(scale_(0.1)));
|
||||
svg.draw(visitor.closest_point, "red", coord_t(scale_(0.1)));
|
||||
printf("contour_distance_filtered-%d-%d.svg - distance %lf\n", iRun, int(&pt - contour.data()), unscale<double>(out.back()));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
|
||||
if (out.back() < search_radius) {
|
||||
SVG svg(debug_out_path("contour_distance_filtered-final-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(expoly_grid);
|
||||
svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
|
||||
for (size_t i = 0; i < contour.size(); ++ i)
|
||||
svg.draw(contour[i], out[i] < float(search_radius - SCALED_EPSILON) ? "red" : "green", coord_t(scale_(0.1)));
|
||||
}
|
||||
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
Points resample_polygon(const Points &contour, double dist, std::vector<ResampledPoint> &resampled_point_parameters)
|
||||
{
|
||||
Points out;
|
||||
|
|
@ -257,8 +445,77 @@ static inline void smooth_compensation(std::vector<float> &compensation, float s
|
|||
std::vector<float> out(compensation);
|
||||
for (size_t iter = 0; iter < num_iterations; ++ iter) {
|
||||
for (size_t i = 0; i < compensation.size(); ++ i) {
|
||||
float prev = (i == 0) ? compensation.back() : compensation[i - 1];
|
||||
float next = (i + 1 == compensation.size()) ? compensation.front() : compensation[i + 1];
|
||||
float prev = prev_value_modulo(i, compensation);
|
||||
float next = next_value_modulo(i, compensation);
|
||||
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
|
||||
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
|
||||
out[i] = std::max(laplacian, compensation[i]);
|
||||
}
|
||||
out.swap(compensation);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void smooth_compensation_banded(const Points &contour, float band, std::vector<float> &compensation, float strength, size_t num_iterations)
|
||||
{
|
||||
assert(contour.size() == compensation.size());
|
||||
assert(contour.size() > 2);
|
||||
std::vector<float> out(compensation);
|
||||
float dist_min2 = band * band;
|
||||
static constexpr bool use_min = false;
|
||||
for (size_t iter = 0; iter < num_iterations; ++ iter) {
|
||||
for (int i = 0; i < int(compensation.size()); ++ i) {
|
||||
const Vec2f pthis = contour[i].cast<float>();
|
||||
|
||||
int j = prev_idx_modulo(i, contour);
|
||||
Vec2f pprev = contour[j].cast<float>();
|
||||
float prev = compensation[j];
|
||||
float l2 = (pthis - pprev).squaredNorm();
|
||||
if (l2 < dist_min2) {
|
||||
float l = sqrt(l2);
|
||||
int jprev = exchange(j, prev_idx_modulo(j, contour));
|
||||
while (j != i) {
|
||||
const Vec2f pp = contour[j].cast<float>();
|
||||
const float lthis = (pp - pprev).norm();
|
||||
const float lnext = l + lthis;
|
||||
if (lnext > band) {
|
||||
// Interpolate the compensation value.
|
||||
prev = use_min ?
|
||||
std::min(prev, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
|
||||
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
|
||||
break;
|
||||
}
|
||||
prev = use_min ? std::min(prev, compensation[j]) : compensation[j];
|
||||
pprev = pp;
|
||||
l = lnext;
|
||||
jprev = exchange(j, prev_idx_modulo(j, contour));
|
||||
}
|
||||
}
|
||||
|
||||
j = next_idx_modulo(i, contour);
|
||||
pprev = contour[j].cast<float>();
|
||||
float next = compensation[j];
|
||||
l2 = (pprev - pthis).squaredNorm();
|
||||
if (l2 < dist_min2) {
|
||||
float l = sqrt(l2);
|
||||
int jprev = exchange(j, next_idx_modulo(j, contour));
|
||||
while (j != i) {
|
||||
const Vec2f pp = contour[j].cast<float>();
|
||||
const float lthis = (pp - pprev).norm();
|
||||
const float lnext = l + lthis;
|
||||
if (lnext > band) {
|
||||
// Interpolate the compensation value.
|
||||
next = use_min ?
|
||||
std::min(next, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
|
||||
lerp(compensation[jprev], compensation[j], (band - l) / lthis);
|
||||
break;
|
||||
}
|
||||
next = use_min ? std::min(next, compensation[j]) : compensation[j];
|
||||
pprev = pp;
|
||||
l = lnext;
|
||||
jprev = exchange(j, next_idx_modulo(j, contour));
|
||||
}
|
||||
}
|
||||
|
||||
float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
|
||||
// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
|
||||
out[i] = std::max(laplacian, compensation[i]);
|
||||
|
|
@ -268,7 +525,7 @@ static inline void smooth_compensation(std::vector<float> &compensation, float s
|
|||
}
|
||||
|
||||
ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, const Flow &external_perimeter_flow, const double compensation)
|
||||
{
|
||||
{
|
||||
// The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
|
||||
double min_contour_width = double(external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing());
|
||||
double scaled_compensation = scale_(compensation);
|
||||
|
|
@ -276,37 +533,68 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, const Flow &
|
|||
// Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work.
|
||||
double search_radius = min_contour_width_compensated + min_contour_width * 0.5;
|
||||
|
||||
EdgeGrid::Grid grid;
|
||||
ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front();
|
||||
BoundingBox bbox = get_extents(simplified.contour);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(simplified, coord_t(0.7 * search_radius));
|
||||
std::vector<std::vector<float>> deltas;
|
||||
deltas.reserve(simplified.holes.size() + 1);
|
||||
ExPolygon resampled(simplified);
|
||||
for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) {
|
||||
Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1];
|
||||
std::vector<ResampledPoint> resampled_point_parameters;
|
||||
poly.points = resample_polygon(poly.points, scale_(0.5), resampled_point_parameters);
|
||||
std::vector<float> dists = contour_distance(grid, idx_contour, poly.points, resampled_point_parameters, search_radius);
|
||||
for (float &d : dists) {
|
||||
// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale<double>(d));
|
||||
// Convert contour width to available compensation distance.
|
||||
if (d < min_contour_width)
|
||||
d = 0.f;
|
||||
else if (d > min_contour_width_compensated)
|
||||
d = - float(scaled_compensation);
|
||||
else
|
||||
d = - (d - float(min_contour_width)) / 2.f;
|
||||
assert(d >= - float(scaled_compensation) && d <= 0.f);
|
||||
BoundingBox bbox = get_extents(input_expoly.contour);
|
||||
Point bbox_size = bbox.size();
|
||||
ExPolygon out;
|
||||
if (bbox_size.x() < min_contour_width_compensated + SCALED_EPSILON ||
|
||||
bbox_size.y() < min_contour_width_compensated + SCALED_EPSILON ||
|
||||
input_expoly.area() < min_contour_width_compensated * min_contour_width_compensated * 5.)
|
||||
{
|
||||
// The contour is tiny. Don't correct it.
|
||||
out = input_expoly;
|
||||
}
|
||||
else
|
||||
{
|
||||
EdgeGrid::Grid grid;
|
||||
ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front();
|
||||
BoundingBox bbox = get_extents(simplified.contour);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(simplified, coord_t(0.7 * search_radius));
|
||||
std::vector<std::vector<float>> deltas;
|
||||
deltas.reserve(simplified.holes.size() + 1);
|
||||
ExPolygon resampled(simplified);
|
||||
double resample_interval = scale_(0.5);
|
||||
for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) {
|
||||
Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1];
|
||||
std::vector<ResampledPoint> resampled_point_parameters;
|
||||
poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters);
|
||||
std::vector<float> dists = contour_distance2(grid, idx_contour, poly.points, resampled_point_parameters, scaled_compensation, search_radius);
|
||||
for (float &d : dists) {
|
||||
// printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale<double>(d));
|
||||
// Convert contour width to available compensation distance.
|
||||
if (d < min_contour_width)
|
||||
d = 0.f;
|
||||
else if (d > min_contour_width_compensated)
|
||||
d = - float(scaled_compensation);
|
||||
else
|
||||
d = - (d - float(min_contour_width)) / 2.f;
|
||||
assert(d >= - float(scaled_compensation) && d <= 0.f);
|
||||
}
|
||||
// smooth_compensation(dists, 0.4f, 10);
|
||||
smooth_compensation_banded(poly.points, float(0.8 * resample_interval), dists, 0.3f, 3);
|
||||
deltas.emplace_back(dists);
|
||||
}
|
||||
|
||||
ExPolygons out_vec = variable_offset_inner_ex(resampled, deltas, 2.);
|
||||
if (out_vec.size() == 1)
|
||||
out = std::move(out_vec.front());
|
||||
else {
|
||||
// Something went wrong, don't compensate.
|
||||
out = input_expoly;
|
||||
#ifdef TESTS_EXPORT_SVGS
|
||||
if (out_vec.size() > 1) {
|
||||
static int iRun = 0;
|
||||
SVG::export_expolygons(debug_out_path("elephant_foot_compensation-many_contours-%d.svg", iRun ++).c_str(),
|
||||
{ { { input_expoly }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
|
||||
{ { out_vec }, { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
|
||||
}
|
||||
#endif /* TESTS_EXPORT_SVGS */
|
||||
assert(out_vec.size() == 1);
|
||||
}
|
||||
smooth_compensation(dists, 0.4f, 10);
|
||||
deltas.emplace_back(dists);
|
||||
}
|
||||
|
||||
ExPolygons out = variable_offset_inner_ex(resampled, deltas, 2.);
|
||||
return out.front();
|
||||
return out;
|
||||
}
|
||||
|
||||
ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation)
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ public:
|
|||
void triangulate_pp(Points *triangles) const;
|
||||
void triangulate_p2t(Polygons* polygons) const;
|
||||
Lines lines() const;
|
||||
|
||||
// Number of contours (outer contour with holes).
|
||||
size_t num_contours() const { return this->holes.size() + 1; }
|
||||
Polygon& contour_or_hole(size_t idx) { return (idx == 0) ? this->contour : this->holes[idx - 1]; }
|
||||
const Polygon& contour_or_hole(size_t idx) const { return (idx == 0) ? this->contour : this->holes[idx - 1]; }
|
||||
};
|
||||
|
||||
inline bool operator==(const ExPolygon &lhs, const ExPolygon &rhs) { return lhs.contour == rhs.contour && lhs.holes == rhs.holes; }
|
||||
|
|
|
|||
|
|
@ -267,6 +267,15 @@ public:
|
|||
|
||||
//static inline std::string role_to_string(ExtrusionLoopRole role);
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate() const {
|
||||
assert(this->first_point() == this->paths.back().polyline.points.back());
|
||||
for (size_t i = 1; i < paths.size(); ++ i)
|
||||
assert(this->paths[i - 1].polyline.points.back() == this->paths[i].polyline.points.front());
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
private:
|
||||
ExtrusionLoopRole m_loop_role;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -158,43 +158,18 @@ void Fill3DHoneycomb::_fill_surface_single(
|
|||
((this->layer_id/thickness_layers) % 2) + 1);
|
||||
|
||||
// move pattern in place
|
||||
for (Polylines::iterator it = polylines.begin(); it != polylines.end(); ++ it)
|
||||
it->translate(bb.min(0), bb.min(1));
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(bb.min);
|
||||
|
||||
// clip pattern to boundaries
|
||||
polylines = intersection_pl(polylines, (Polygons)expolygon);
|
||||
// clip pattern to boundaries, chain the clipped polylines
|
||||
Polylines polylines_chained = chain_polylines(intersection_pl(polylines, to_polygons(expolygon)));
|
||||
|
||||
// connect lines
|
||||
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
|
||||
ExPolygon expolygon_off;
|
||||
{
|
||||
ExPolygons expolygons_off = offset_ex(expolygon, SCALED_EPSILON);
|
||||
if (! expolygons_off.empty()) {
|
||||
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
|
||||
assert(expolygons_off.size() == 1);
|
||||
std::swap(expolygon_off, expolygons_off.front());
|
||||
}
|
||||
}
|
||||
bool first = true;
|
||||
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
|
||||
if (! first) {
|
||||
// Try to connect the lines.
|
||||
Points &pts_end = polylines_out.back().points;
|
||||
const Point &first_point = polyline.points.front();
|
||||
const Point &last_point = pts_end.back();
|
||||
// TODO: we should also check that both points are on a fill_boundary to avoid
|
||||
// connecting paths on the boundaries of internal regions
|
||||
if ((last_point - first_point).cast<double>().norm() <= 1.5 * distance &&
|
||||
expolygon_off.contains(Line(last_point, first_point))) {
|
||||
// Append the polyline.
|
||||
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
polylines_out.emplace_back(std::move(polyline));
|
||||
first = false;
|
||||
}
|
||||
// connect lines if needed
|
||||
if (! polylines_chained.empty()) {
|
||||
if (params.dont_connect)
|
||||
append(polylines_out, std::move(polylines_chained));
|
||||
else
|
||||
this->connect_infill(std::move(polylines_chained), expolygon, polylines_out, params);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "FillBase.hpp"
|
||||
#include "FillConcentric.hpp"
|
||||
|
|
@ -148,4 +150,838 @@ std::pair<float, Point> Fill::_infill_direction(const Surface *surface) const
|
|||
return std::pair<float, Point>(out_angle, out_shift);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// From pull request "Gyroid improvements" #2730 by @supermerill
|
||||
|
||||
/// cut poly between poly.point[idx_1] & poly.point[idx_1+1]
|
||||
/// add p1+-width to one part and p2+-width to the other one.
|
||||
/// add the "new" polyline to polylines (to part cut from poly)
|
||||
/// p1 & p2 have to be between poly.point[idx_1] & poly.point[idx_1+1]
|
||||
/// if idx_1 is ==0 or == size-1, then we don't need to create a new polyline.
|
||||
static void cut_polyline(Polyline &poly, Polylines &polylines, size_t idx_1, Point p1, Point p2) {
|
||||
//reorder points
|
||||
if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) {
|
||||
Point temp = p2;
|
||||
p2 = p1;
|
||||
p1 = temp;
|
||||
}
|
||||
if (idx_1 == poly.points.size() - 1) {
|
||||
//shouldn't be possible.
|
||||
poly.points.erase(poly.points.end() - 1);
|
||||
} else {
|
||||
// create new polyline
|
||||
Polyline new_poly;
|
||||
//put points in new_poly
|
||||
new_poly.points.push_back(p2);
|
||||
new_poly.points.insert(new_poly.points.end(), poly.points.begin() + idx_1 + 1, poly.points.end());
|
||||
//erase&put points in poly
|
||||
poly.points.erase(poly.points.begin() + idx_1 + 1, poly.points.end());
|
||||
poly.points.push_back(p1);
|
||||
//safe test
|
||||
if (poly.length() == 0)
|
||||
poly.points = new_poly.points;
|
||||
else
|
||||
polylines.emplace_back(new_poly);
|
||||
}
|
||||
}
|
||||
|
||||
/// the poly is like a polygon but with first_point != last_point (already removed)
|
||||
static void cut_polygon(Polyline &poly, size_t idx_1, Point p1, Point p2) {
|
||||
//reorder points
|
||||
if (p1.distance_to_square(poly.points[idx_1]) > p2.distance_to_square(poly.points[idx_1])) {
|
||||
Point temp = p2;
|
||||
p2 = p1;
|
||||
p1 = temp;
|
||||
}
|
||||
//check if we need to rotate before cutting
|
||||
if (idx_1 != poly.size() - 1) {
|
||||
//put points in new_poly
|
||||
poly.points.insert(poly.points.end(), poly.points.begin(), poly.points.begin() + idx_1 + 1);
|
||||
poly.points.erase(poly.points.begin(), poly.points.begin() + idx_1 + 1);
|
||||
}
|
||||
//put points in poly
|
||||
poly.points.push_back(p1);
|
||||
poly.points.insert(poly.points.begin(), p2);
|
||||
}
|
||||
|
||||
/// check if the polyline from pts_to_check may be at 'width' distance of a point in polylines_blocker
|
||||
/// it use equally_spaced_points with width/2 precision, so don't worry with pts_to_check number of points.
|
||||
/// it use the given polylines_blocker points, be sure to put enough of them to be reliable.
|
||||
/// complexity : N(pts_to_check.equally_spaced_points(width / 2)) x N(polylines_blocker.points)
|
||||
static bool collision(const Points &pts_to_check, const Polylines &polylines_blocker, const coordf_t width) {
|
||||
//check if it's not too close to a polyline
|
||||
coordf_t min_dist_square = width * width * 0.9 - SCALED_EPSILON;
|
||||
Polyline better_polylines(pts_to_check);
|
||||
Points better_pts = better_polylines.equally_spaced_points(width / 2);
|
||||
for (const Point &p : better_pts) {
|
||||
for (const Polyline &poly2 : polylines_blocker) {
|
||||
for (const Point &p2 : poly2.points) {
|
||||
if (p.distance_to_square(p2) < min_dist_square) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Try to find a path inside polylines that allow to go from p1 to p2.
|
||||
/// width if the width of the extrusion
|
||||
/// polylines_blockers are the array of polylines to check if the path isn't blocked by something.
|
||||
/// complexity: N(polylines.points) + a collision check after that if we finded a path: N(2(p2-p1)/width) x N(polylines_blocker.points)
|
||||
static Points get_frontier(Polylines &polylines, const Point& p1, const Point& p2, const coord_t width, const Polylines &polylines_blockers, coord_t max_size = -1) {
|
||||
for (size_t idx_poly = 0; idx_poly < polylines.size(); ++idx_poly) {
|
||||
Polyline &poly = polylines[idx_poly];
|
||||
if (poly.size() <= 1) continue;
|
||||
|
||||
//loop?
|
||||
if (poly.first_point() == poly.last_point()) {
|
||||
//polygon : try to find a line for p1 & p2.
|
||||
size_t idx_11, idx_12, idx_21, idx_22;
|
||||
idx_11 = poly.closest_point_index(p1);
|
||||
idx_12 = idx_11;
|
||||
if (Line(poly.points[idx_11], poly.points[(idx_11 + 1) % (poly.points.size() - 1)]).distance_to(p1) < SCALED_EPSILON) {
|
||||
idx_12 = (idx_11 + 1) % (poly.points.size() - 1);
|
||||
} else if (Line(poly.points[(idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2)], poly.points[idx_11]).distance_to(p1) < SCALED_EPSILON) {
|
||||
idx_11 = (idx_11 > 0) ? (idx_11 - 1) : (poly.points.size() - 2);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
idx_21 = poly.closest_point_index(p2);
|
||||
idx_22 = idx_21;
|
||||
if (Line(poly.points[idx_21], poly.points[(idx_21 + 1) % (poly.points.size() - 1)]).distance_to(p2) < SCALED_EPSILON) {
|
||||
idx_22 = (idx_21 + 1) % (poly.points.size() - 1);
|
||||
} else if (Line(poly.points[(idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2)], poly.points[idx_21]).distance_to(p2) < SCALED_EPSILON) {
|
||||
idx_21 = (idx_21 > 0) ? (idx_21 - 1) : (poly.points.size() - 2);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
//edge case: on the same line
|
||||
if (idx_11 == idx_21 && idx_12 == idx_22) {
|
||||
if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points();
|
||||
//break loop
|
||||
poly.points.erase(poly.points.end() - 1);
|
||||
cut_polygon(poly, idx_11, p1, p2);
|
||||
return Points() = { Line(p1, p2).midpoint() };
|
||||
}
|
||||
|
||||
//compute distance & array for the ++ path
|
||||
Points ret_1_to_2;
|
||||
double dist_1_to_2 = p1.distance_to(poly.points[idx_12]);
|
||||
ret_1_to_2.push_back(poly.points[idx_12]);
|
||||
size_t max = idx_12 <= idx_21 ? idx_21+1 : poly.points.size();
|
||||
for (size_t i = idx_12 + 1; i < max; i++) {
|
||||
dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]);
|
||||
ret_1_to_2.push_back(poly.points[i]);
|
||||
}
|
||||
if (idx_12 > idx_21) {
|
||||
dist_1_to_2 += poly.points.back().distance_to(poly.points.front());
|
||||
ret_1_to_2.push_back(poly.points[0]);
|
||||
for (size_t i = 1; i <= idx_21; i++) {
|
||||
dist_1_to_2 += poly.points[i - 1].distance_to(poly.points[i]);
|
||||
ret_1_to_2.push_back(poly.points[i]);
|
||||
}
|
||||
}
|
||||
dist_1_to_2 += p2.distance_to(poly.points[idx_21]);
|
||||
|
||||
//compute distance & array for the -- path
|
||||
Points ret_2_to_1;
|
||||
double dist_2_to_1 = p1.distance_to(poly.points[idx_11]);
|
||||
ret_2_to_1.push_back(poly.points[idx_11]);
|
||||
size_t min = idx_22 <= idx_11 ? idx_22 : 0;
|
||||
for (size_t i = idx_11; i > min; i--) {
|
||||
dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]);
|
||||
ret_2_to_1.push_back(poly.points[i - 1]);
|
||||
}
|
||||
if (idx_22 > idx_11) {
|
||||
dist_2_to_1 += poly.points.back().distance_to(poly.points.front());
|
||||
ret_2_to_1.push_back(poly.points[poly.points.size() - 1]);
|
||||
for (size_t i = poly.points.size() - 1; i > idx_22; i--) {
|
||||
dist_2_to_1 += poly.points[i - 1].distance_to(poly.points[i]);
|
||||
ret_2_to_1.push_back(poly.points[i - 1]);
|
||||
}
|
||||
}
|
||||
dist_2_to_1 += p2.distance_to(poly.points[idx_22]);
|
||||
|
||||
if (max_size < dist_2_to_1 && max_size < dist_1_to_2) {
|
||||
return Points();
|
||||
}
|
||||
|
||||
//choose between the two direction (keep the short one)
|
||||
if (dist_1_to_2 < dist_2_to_1) {
|
||||
if (collision(ret_1_to_2, polylines_blockers, width)) return Points();
|
||||
//break loop
|
||||
poly.points.erase(poly.points.end() - 1);
|
||||
//remove points
|
||||
if (idx_12 <= idx_21) {
|
||||
poly.points.erase(poly.points.begin() + idx_12, poly.points.begin() + idx_21 + 1);
|
||||
if (idx_12 != 0) {
|
||||
cut_polygon(poly, idx_11, p1, p2);
|
||||
} //else : already cut at the good place
|
||||
} else {
|
||||
poly.points.erase(poly.points.begin() + idx_12, poly.points.end());
|
||||
poly.points.erase(poly.points.begin(), poly.points.begin() + idx_21);
|
||||
cut_polygon(poly, poly.points.size() - 1, p1, p2);
|
||||
}
|
||||
return ret_1_to_2;
|
||||
} else {
|
||||
if (collision(ret_2_to_1, polylines_blockers, width)) return Points();
|
||||
//break loop
|
||||
poly.points.erase(poly.points.end() - 1);
|
||||
//remove points
|
||||
if (idx_22 <= idx_11) {
|
||||
poly.points.erase(poly.points.begin() + idx_22, poly.points.begin() + idx_11 + 1);
|
||||
if (idx_22 != 0) {
|
||||
cut_polygon(poly, idx_21, p1, p2);
|
||||
} //else : already cut at the good place
|
||||
} else {
|
||||
poly.points.erase(poly.points.begin() + idx_22, poly.points.end());
|
||||
poly.points.erase(poly.points.begin(), poly.points.begin() + idx_11);
|
||||
cut_polygon(poly, poly.points.size() - 1, p1, p2);
|
||||
}
|
||||
return ret_2_to_1;
|
||||
}
|
||||
} else {
|
||||
//polyline : try to find a line for p1 & p2.
|
||||
size_t idx_1, idx_2;
|
||||
idx_1 = poly.closest_point_index(p1);
|
||||
if (idx_1 < poly.points.size() - 1 && Line(poly.points[idx_1], poly.points[idx_1 + 1]).distance_to(p1) < SCALED_EPSILON) {
|
||||
} else if (idx_1 > 0 && Line(poly.points[idx_1 - 1], poly.points[idx_1]).distance_to(p1) < SCALED_EPSILON) {
|
||||
idx_1 = idx_1 - 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
idx_2 = poly.closest_point_index(p2);
|
||||
if (idx_2 < poly.points.size() - 1 && Line(poly.points[idx_2], poly.points[idx_2 + 1]).distance_to(p2) < SCALED_EPSILON) {
|
||||
} else if (idx_2 > 0 && Line(poly.points[idx_2 - 1], poly.points[idx_2]).distance_to(p2) < SCALED_EPSILON) {
|
||||
idx_2 = idx_2 - 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
//edge case: on the same line
|
||||
if (idx_1 == idx_2) {
|
||||
if (collision(Points() = { p1, p2 }, polylines_blockers, width)) return Points();
|
||||
cut_polyline(poly, polylines, idx_1, p1, p2);
|
||||
return Points() = { Line(p1, p2).midpoint() };
|
||||
}
|
||||
|
||||
//create ret array
|
||||
size_t first_idx = idx_1;
|
||||
size_t last_idx = idx_2 + 1;
|
||||
if (idx_1 > idx_2) {
|
||||
first_idx = idx_2;
|
||||
last_idx = idx_1 + 1;
|
||||
}
|
||||
Points p_ret;
|
||||
p_ret.insert(p_ret.end(), poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx);
|
||||
|
||||
coordf_t length = 0;
|
||||
for (size_t i = 1; i < p_ret.size(); i++) length += p_ret[i - 1].distance_to(p_ret[i]);
|
||||
|
||||
if (max_size < length) {
|
||||
return Points();
|
||||
}
|
||||
|
||||
if (collision(p_ret, polylines_blockers, width)) return Points();
|
||||
//cut polyline
|
||||
poly.points.erase(poly.points.begin() + first_idx + 1, poly.points.begin() + last_idx);
|
||||
cut_polyline(poly, polylines, first_idx, p1, p2);
|
||||
//order the returned array to be p1->p2
|
||||
if (idx_1 > idx_2) {
|
||||
std::reverse(p_ret.begin(), p_ret.end());
|
||||
}
|
||||
return p_ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return Points();
|
||||
}
|
||||
|
||||
/// Connect the infill_ordered polylines, in this order, from the back point to the next front point.
|
||||
/// It uses only the boundary polygons to do so, and can't pass two times at the same place.
|
||||
/// It avoid passing over the infill_ordered's polylines (preventing local over-extrusion).
|
||||
/// return the connected polylines in polylines_out. Can output polygons (stored as polylines with first_point = last_point).
|
||||
/// complexity: worst: N(infill_ordered.points) x N(boundary.points)
|
||||
/// typical: N(infill_ordered) x ( N(boundary.points) + N(infill_ordered.points) )
|
||||
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms) {
|
||||
|
||||
//TODO: fallback to the quick & dirty old algorithm when n(points) is too high.
|
||||
Polylines polylines_frontier = to_polylines(((Polygons)boundary));
|
||||
|
||||
Polylines polylines_blocker;
|
||||
coord_t clip_size = scale_(this->spacing) * 2;
|
||||
for (const Polyline &polyline : infill_ordered) {
|
||||
if (polyline.length() > 2.01 * clip_size) {
|
||||
polylines_blocker.push_back(polyline);
|
||||
polylines_blocker.back().clip_end(clip_size);
|
||||
polylines_blocker.back().clip_start(clip_size);
|
||||
}
|
||||
}
|
||||
|
||||
//length between two lines
|
||||
coordf_t ideal_length = (1 / params.density) * this->spacing;
|
||||
|
||||
Polylines polylines_connected_first;
|
||||
bool first = true;
|
||||
for (const Polyline &polyline : infill_ordered) {
|
||||
if (!first) {
|
||||
// Try to connect the lines.
|
||||
Points &pts_end = polylines_connected_first.back().points;
|
||||
const Point &last_point = pts_end.back();
|
||||
const Point &first_point = polyline.points.front();
|
||||
if (last_point.distance_to(first_point) < scale_(this->spacing) * 10) {
|
||||
Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker, (coord_t)scale_(ideal_length) * 2);
|
||||
if (!pts_frontier.empty()) {
|
||||
// The lines can be connected.
|
||||
pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end());
|
||||
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
polylines_connected_first.emplace_back(std::move(polyline));
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
Polylines polylines_connected;
|
||||
first = true;
|
||||
for (const Polyline &polyline : polylines_connected_first) {
|
||||
if (!first) {
|
||||
// Try to connect the lines.
|
||||
Points &pts_end = polylines_connected.back().points;
|
||||
const Point &last_point = pts_end.back();
|
||||
const Point &first_point = polyline.points.front();
|
||||
|
||||
Polylines before = polylines_frontier;
|
||||
Points pts_frontier = get_frontier(polylines_frontier, last_point, first_point, scale_(this->spacing), polylines_blocker);
|
||||
if (!pts_frontier.empty()) {
|
||||
// The lines can be connected.
|
||||
pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end());
|
||||
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
polylines_connected.emplace_back(std::move(polyline));
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
//try to link to nearest point if possible
|
||||
for (size_t idx1 = 0; idx1 < polylines_connected.size(); idx1++) {
|
||||
size_t min_idx = 0;
|
||||
coordf_t min_length = 0;
|
||||
bool switch_id1 = false;
|
||||
bool switch_id2 = false;
|
||||
for (size_t idx2 = idx1 + 1; idx2 < polylines_connected.size(); idx2++) {
|
||||
double last_first = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].first_point());
|
||||
double first_first = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].first_point());
|
||||
double first_last = polylines_connected[idx1].first_point().distance_to_square(polylines_connected[idx2].last_point());
|
||||
double last_last = polylines_connected[idx1].last_point().distance_to_square(polylines_connected[idx2].last_point());
|
||||
double min = std::min(std::min(last_first, last_last), std::min(first_first, first_last));
|
||||
if (min < min_length || min_length == 0) {
|
||||
min_idx = idx2;
|
||||
switch_id1 = (std::min(last_first, last_last) > std::min(first_first, first_last));
|
||||
switch_id2 = (std::min(last_first, first_first) > std::min(last_last, first_last));
|
||||
min_length = min;
|
||||
}
|
||||
}
|
||||
if (min_idx > idx1 && min_idx < polylines_connected.size()){
|
||||
Points pts_frontier = get_frontier(polylines_frontier,
|
||||
switch_id1 ? polylines_connected[idx1].first_point() : polylines_connected[idx1].last_point(),
|
||||
switch_id2 ? polylines_connected[min_idx].last_point() : polylines_connected[min_idx].first_point(),
|
||||
scale_(this->spacing), polylines_blocker);
|
||||
if (!pts_frontier.empty()) {
|
||||
if (switch_id1) polylines_connected[idx1].reverse();
|
||||
if (switch_id2) polylines_connected[min_idx].reverse();
|
||||
Points &pts_end = polylines_connected[idx1].points;
|
||||
pts_end.insert(pts_end.end(), pts_frontier.begin(), pts_frontier.end());
|
||||
pts_end.insert(pts_end.end(), polylines_connected[min_idx].points.begin(), polylines_connected[min_idx].points.end());
|
||||
polylines_connected.erase(polylines_connected.begin() + min_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//try to create some loops if possible
|
||||
for (Polyline &polyline : polylines_connected) {
|
||||
Points pts_frontier = get_frontier(polylines_frontier, polyline.last_point(), polyline.first_point(), scale_(this->spacing), polylines_blocker);
|
||||
if (!pts_frontier.empty()) {
|
||||
polyline.points.insert(polyline.points.end(), pts_frontier.begin(), pts_frontier.end());
|
||||
polyline.points.insert(polyline.points.begin(), polyline.points.back());
|
||||
}
|
||||
polylines_out.emplace_back(polyline);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
struct ContourPointData {
|
||||
ContourPointData(float param) : param(param) {}
|
||||
// Eucleidean position of the contour point along the contour.
|
||||
float param = 0.f;
|
||||
// Was the segment starting with this contour point extruded?
|
||||
bool segment_consumed = false;
|
||||
// Was this point extruded over?
|
||||
bool point_consumed = false;
|
||||
};
|
||||
|
||||
// Verify whether the contour from point idx_start to point idx_end could be taken (whether all segments along the contour were not yet extruded).
|
||||
static bool could_take(const std::vector<ContourPointData> &contour_data, size_t idx_start, size_t idx_end)
|
||||
{
|
||||
assert(idx_start != idx_end);
|
||||
for (size_t i = idx_start; i != idx_end; ) {
|
||||
if (contour_data[i].segment_consumed || contour_data[i].point_consumed)
|
||||
return false;
|
||||
if (++ i == contour_data.size())
|
||||
i = 0;
|
||||
}
|
||||
return ! contour_data[idx_end].point_consumed;
|
||||
}
|
||||
|
||||
// Connect end of pl1 to the start of pl2 using the perimeter contour.
|
||||
// The idx_start and idx_end are ordered so that the connecting polyline points will be taken with increasing indices.
|
||||
static void take(Polyline &pl1, Polyline &&pl2, const Points &contour, std::vector<ContourPointData> &contour_data, size_t idx_start, size_t idx_end, bool reversed)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
size_t num_points_initial = pl1.points.size();
|
||||
assert(idx_start != idx_end);
|
||||
#endif /* NDEBUG */
|
||||
|
||||
{
|
||||
// Reserve memory at pl1 for the connecting contour and pl2.
|
||||
int new_points = int(idx_end) - int(idx_start) - 1;
|
||||
if (new_points < 0)
|
||||
new_points += int(contour.size());
|
||||
pl1.points.reserve(pl1.points.size() + size_t(new_points) + pl2.points.size());
|
||||
}
|
||||
|
||||
contour_data[idx_start].point_consumed = true;
|
||||
contour_data[idx_start].segment_consumed = true;
|
||||
contour_data[idx_end ].point_consumed = true;
|
||||
|
||||
if (reversed) {
|
||||
size_t i = (idx_end == 0) ? contour_data.size() - 1 : idx_end - 1;
|
||||
while (i != idx_start) {
|
||||
contour_data[i].point_consumed = true;
|
||||
contour_data[i].segment_consumed = true;
|
||||
pl1.points.emplace_back(contour[i]);
|
||||
if (i == 0)
|
||||
i = contour_data.size();
|
||||
-- i;
|
||||
}
|
||||
} else {
|
||||
size_t i = idx_start;
|
||||
if (++ i == contour_data.size())
|
||||
i = 0;
|
||||
while (i != idx_end) {
|
||||
contour_data[i].point_consumed = true;
|
||||
contour_data[i].segment_consumed = true;
|
||||
pl1.points.emplace_back(contour[i]);
|
||||
if (++ i == contour_data.size())
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
|
||||
append(pl1.points, std::move(pl2.points));
|
||||
}
|
||||
|
||||
// Return an index of start of a segment and a point of the clipping point at distance from the end of polyline.
|
||||
struct SegmentPoint {
|
||||
// Segment index, defining a line <idx_segment, idx_segment + 1).
|
||||
size_t idx_segment = std::numeric_limits<size_t>::max();
|
||||
// Parameter of point in <0, 1) along the line <idx_segment, idx_segment + 1)
|
||||
double t;
|
||||
Vec2d point;
|
||||
|
||||
bool valid() const { return idx_segment != std::numeric_limits<size_t>::max(); }
|
||||
};
|
||||
|
||||
static inline SegmentPoint clip_start_segment_and_point(const Points &polyline, double distance)
|
||||
{
|
||||
assert(polyline.size() >= 2);
|
||||
assert(distance > 0.);
|
||||
// Initialized to "invalid".
|
||||
SegmentPoint out;
|
||||
if (polyline.size() >= 2) {
|
||||
const double d2 = distance * distance;
|
||||
Vec2d pt_prev = polyline.front().cast<double>();
|
||||
for (int i = 1; i < polyline.size(); ++ i) {
|
||||
Vec2d pt = polyline[i].cast<double>();
|
||||
Vec2d v = pt - pt_prev;
|
||||
double l2 = v.squaredNorm();
|
||||
if (l2 > d2) {
|
||||
out.idx_segment = i;
|
||||
out.t = distance / sqrt(l2);
|
||||
out.point = pt + out.t * v;
|
||||
break;
|
||||
}
|
||||
distance -= sqrt(l2);
|
||||
pt_prev = pt;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, double distance)
|
||||
{
|
||||
assert(polyline.size() >= 2);
|
||||
assert(distance > 0.);
|
||||
// Initialized to "invalid".
|
||||
SegmentPoint out;
|
||||
if (polyline.size() >= 2) {
|
||||
const double d2 = distance * distance;
|
||||
Vec2d pt_next = polyline.back().cast<double>();
|
||||
for (int i = int(polyline.size()) - 2; i >= 0; -- i) {
|
||||
Vec2d pt = polyline[i].cast<double>();
|
||||
Vec2d v = pt - pt_next;
|
||||
double l2 = v.squaredNorm();
|
||||
if (l2 > d2) {
|
||||
out.idx_segment = i;
|
||||
out.t = distance / sqrt(l2);
|
||||
out.point = pt + out.t * v;
|
||||
break;
|
||||
}
|
||||
distance -= sqrt(l2);
|
||||
pt_next = pt;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static inline double segment_point_distance_squared(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2)
|
||||
{
|
||||
const Vec2d v = p1b - p1a;
|
||||
const Vec2d va = p2 - p1a;
|
||||
const double l2 = v.squaredNorm();
|
||||
if (l2 < EPSILON)
|
||||
// p1a == p1b
|
||||
return va.squaredNorm();
|
||||
// Project p2 onto the (p1a, p1b) segment.
|
||||
const double t = va.dot(v);
|
||||
if (t < 0.)
|
||||
return va.squaredNorm();
|
||||
else if (t > l2)
|
||||
return (p2 - p1b).squaredNorm();
|
||||
return ((t / l2) * v - va).squaredNorm();
|
||||
}
|
||||
|
||||
// Distance to the closest point of line.
|
||||
static inline double min_distance_of_segments(const Vec2d &p1a, const Vec2d &p1b, const Vec2d &p2a, const Vec2d &p2b)
|
||||
{
|
||||
Vec2d v1 = p1b - p1a;
|
||||
double l1_2 = v1.squaredNorm();
|
||||
if (l1_2 < EPSILON)
|
||||
// p1a == p1b: Return distance of p1a from the (p2a, p2b) segment.
|
||||
return segment_point_distance_squared(p2a, p2b, p1a);
|
||||
|
||||
Vec2d v2 = p2b - p2a;
|
||||
double l2_2 = v2.squaredNorm();
|
||||
if (l2_2 < EPSILON)
|
||||
// p2a == p2b: Return distance of p2a from the (p1a, p1b) segment.
|
||||
return segment_point_distance_squared(p1a, p1b, p2a);
|
||||
|
||||
// Project p2a, p2b onto the (p1a, p1b) segment.
|
||||
auto project_p2a_p2b_onto_seg_p1a_p1b = [](const Vec2d& p1a, const Vec2d& p1b, const Vec2d& p2a, const Vec2d& p2b, const Vec2d& v1, const double l1_2) {
|
||||
Vec2d v1a2a = p2a - p1a;
|
||||
Vec2d v1a2b = p2b - p1a;
|
||||
double t1 = v1a2a.dot(v1);
|
||||
double t2 = v1a2b.dot(v1);
|
||||
if (t1 <= 0.) {
|
||||
if (t2 <= 0.)
|
||||
// Both p2a and p2b are left of v1.
|
||||
return (((t1 < t2) ? p2b : p2a) - p1a).squaredNorm();
|
||||
else if (t2 < l1_2)
|
||||
// Project p2b onto the (p1a, p1b) segment.
|
||||
return ((t2 / l1_2) * v1 - v1a2b).squaredNorm();
|
||||
}
|
||||
else if (t1 >= l1_2) {
|
||||
if (t2 >= l1_2)
|
||||
// Both p2a and p2b are right of v1.
|
||||
return (((t1 < t2) ? p2a : p2b) - p1b).squaredNorm();
|
||||
else if (t2 < l1_2)
|
||||
// Project p2b onto the (p1a, p1b) segment.
|
||||
return ((t2 / l1_2) * v1 - v1a2b).squaredNorm();
|
||||
}
|
||||
else {
|
||||
// Project p1b onto the (p1a, p1b) segment.
|
||||
double dist_min = ((t2 / l1_2) * v1 - v1a2a).squaredNorm();
|
||||
if (t2 > 0. && t2 < l1_2)
|
||||
dist_min = std::min(dist_min, ((t2 / l1_2) * v1 - v1a2b).squaredNorm());
|
||||
return dist_min;
|
||||
}
|
||||
return std::numeric_limits<double>::max();
|
||||
};
|
||||
|
||||
return std::min(
|
||||
project_p2a_p2b_onto_seg_p1a_p1b(p1a, p1b, p2a, p2b, v1, l1_2),
|
||||
project_p2a_p2b_onto_seg_p1a_p1b(p2a, p2b, p1a, p1b, v2, l2_2));
|
||||
}
|
||||
|
||||
// Mark the segments of split boundary as consumed if they are very close to some of the infill line.
|
||||
void mark_boundary_segments_touching_infill(
|
||||
const std::vector<Points> &boundary,
|
||||
std::vector<std::vector<ContourPointData>> &boundary_data,
|
||||
const BoundingBox &boundary_bbox,
|
||||
const Polylines &infill,
|
||||
const double clip_distance,
|
||||
const double distance_colliding)
|
||||
{
|
||||
EdgeGrid::Grid grid;
|
||||
grid.set_bbox(boundary_bbox);
|
||||
// Inflate the bounding box by a thick line width.
|
||||
grid.create(boundary, clip_distance + scale_(10.));
|
||||
|
||||
struct Visitor {
|
||||
Visitor(const EdgeGrid::Grid &grid, const std::vector<Points> &boundary, std::vector<std::vector<ContourPointData>> &boundary_data, const double dist2_max) :
|
||||
grid(grid), boundary(boundary), boundary_data(boundary_data), dist2_max(dist2_max) {}
|
||||
|
||||
void init(const Vec2d &pt1, const Vec2d &pt2) {
|
||||
this->pt1 = &pt1;
|
||||
this->pt2 = &pt2;
|
||||
}
|
||||
|
||||
bool operator()(coord_t iy, coord_t ix) {
|
||||
// Called with a row and colum of the grid cell, which is intersected by a line.
|
||||
auto cell_data_range = this->grid.cell_data_range(iy, ix);
|
||||
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
|
||||
// End points of the line segment and their vector.
|
||||
auto segment = this->grid.segment(*it_contour_and_segment);
|
||||
const Vec2d seg_pt1 = segment.first.cast<double>();
|
||||
const Vec2d seg_pt2 = segment.second.cast<double>();
|
||||
if (min_distance_of_segments(seg_pt1, seg_pt2, *this->pt1, *this->pt2) < this->dist2_max) {
|
||||
// Mark this boundary segment as touching the infill line.
|
||||
ContourPointData&bdp = boundary_data[it_contour_and_segment->first][it_contour_and_segment->second];
|
||||
bdp.segment_consumed = true;
|
||||
// There is no need for checking seg_pt2 as it will be checked the next time.
|
||||
if (segment_point_distance_squared(*this->pt1, *this->pt2, seg_pt1) < this->dist2_max)
|
||||
bdp.point_consumed = true;
|
||||
}
|
||||
}
|
||||
// Continue traversing the grid along the edge.
|
||||
return true;
|
||||
}
|
||||
|
||||
const EdgeGrid::Grid &grid;
|
||||
const std::vector<Points> &boundary;
|
||||
std::vector<std::vector<ContourPointData>> &boundary_data;
|
||||
// Maximum distance between the boundary and the infill line allowed to consider the boundary not touching the infill line.
|
||||
const double dist2_max;
|
||||
|
||||
const Vec2d *pt1;
|
||||
const Vec2d *pt2;
|
||||
} visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding);
|
||||
|
||||
for (const Polyline &polyline : infill) {
|
||||
// Clip the infill polyline by the Eucledian distance along the polyline.
|
||||
SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance);
|
||||
SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance);
|
||||
if (start_point.valid() && end_point.valid() &&
|
||||
(start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) {
|
||||
// The clipped polyline is non-empty.
|
||||
for (size_t point_idx = start_point.idx_segment; point_idx <= end_point.idx_segment; ++ point_idx) {
|
||||
//FIXME extend the EdgeGrid to suport tracing a thick line.
|
||||
#if 0
|
||||
Point pt1, pt2;
|
||||
Vec2d pt1d, pt2d;
|
||||
if (point_idx == start_point.idx_segment) {
|
||||
pt1d = start_point.point;
|
||||
pt1 = pt1d.cast<coord_t>();
|
||||
} else {
|
||||
pt1 = polyline.points[point_idx];
|
||||
pt1d = pt1.cast<double>();
|
||||
}
|
||||
if (point_idx == start_point.idx_segment) {
|
||||
pt2d = end_point.point;
|
||||
pt2 = pt1d.cast<coord_t>();
|
||||
} else {
|
||||
pt2 = polyline.points[point_idx];
|
||||
pt2d = pt2.cast<double>();
|
||||
}
|
||||
visitor.init(pt1d, pt2d);
|
||||
grid.visit_cells_intersecting_thick_line(pt1, pt2, distance_colliding, visitor);
|
||||
#else
|
||||
Vec2d pt1 = (point_idx == start_point.idx_segment) ? start_point.point : polyline.points[point_idx].cast<double>();
|
||||
Vec2d pt2 = (point_idx == end_point .idx_segment) ? end_point .point : polyline.points[point_idx].cast<double>();
|
||||
visitor.init(pt1, pt2);
|
||||
// Simulate tracing of a thick line. This only works reliably if distance_colliding <= grid cell size.
|
||||
Vec2d v = (pt2 - pt1).normalized() * distance_colliding;
|
||||
Vec2d vperp(-v.y(), v.x());
|
||||
Vec2d a = pt1 - v - vperp;
|
||||
Vec2d b = pt1 + v - vperp;
|
||||
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
|
||||
a = pt1 - v + vperp;
|
||||
b = pt1 + v + vperp;
|
||||
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_src, Polylines &polylines_out, const FillParams ¶ms)
|
||||
{
|
||||
assert(! infill_ordered.empty());
|
||||
assert(! boundary_src.contour.points.empty());
|
||||
|
||||
BoundingBox bbox = get_extents(boundary_src.contour);
|
||||
bbox.offset(SCALED_EPSILON);
|
||||
|
||||
// 1) Add the end points of infill_ordered to boundary_src.
|
||||
std::vector<Points> boundary;
|
||||
std::vector<std::vector<ContourPointData>> boundary_data;
|
||||
boundary.assign(boundary_src.holes.size() + 1, Points());
|
||||
boundary_data.assign(boundary_src.holes.size() + 1, std::vector<ContourPointData>());
|
||||
// Mapping the infill_ordered end point to a (contour, point) of boundary.
|
||||
std::vector<std::pair<size_t, size_t>> map_infill_end_point_to_boundary;
|
||||
map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair<size_t, size_t>(std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()));
|
||||
{
|
||||
// Project the infill_ordered end points onto boundary_src.
|
||||
std::vector<std::pair<EdgeGrid::Grid::ClosestPointResult, size_t>> intersection_points;
|
||||
{
|
||||
EdgeGrid::Grid grid;
|
||||
grid.set_bbox(bbox);
|
||||
grid.create(boundary_src, scale_(10.));
|
||||
intersection_points.reserve(infill_ordered.size() * 2);
|
||||
for (const Polyline &pl : infill_ordered)
|
||||
for (const Point *pt : { &pl.points.front(), &pl.points.back() }) {
|
||||
EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point(*pt, SCALED_EPSILON);
|
||||
if (cp.valid()) {
|
||||
// The infill end point shall lie on the contour.
|
||||
assert(cp.distance < 2.);
|
||||
intersection_points.emplace_back(cp, (&pl - infill_ordered.data()) * 2 + (pt == &pl.points.front() ? 0 : 1));
|
||||
}
|
||||
}
|
||||
std::sort(intersection_points.begin(), intersection_points.end(), [](const std::pair<EdgeGrid::Grid::ClosestPointResult, size_t> &cp1, const std::pair<EdgeGrid::Grid::ClosestPointResult, size_t> &cp2) {
|
||||
return cp1.first.contour_idx < cp2.first.contour_idx ||
|
||||
(cp1.first.contour_idx == cp2.first.contour_idx &&
|
||||
(cp1.first.start_point_idx < cp2.first.start_point_idx ||
|
||||
(cp1.first.start_point_idx == cp2.first.start_point_idx && cp1.first.t < cp2.first.t)));
|
||||
});
|
||||
}
|
||||
auto it = intersection_points.begin();
|
||||
auto it_end = intersection_points.end();
|
||||
for (size_t idx_contour = 0; idx_contour <= boundary_src.holes.size(); ++ idx_contour) {
|
||||
const Polygon &contour_src = (idx_contour == 0) ? boundary_src.contour : boundary_src.holes[idx_contour - 1];
|
||||
Points &contour_dst = boundary[idx_contour];
|
||||
for (size_t idx_point = 0; idx_point < contour_src.points.size(); ++ idx_point) {
|
||||
contour_dst.emplace_back(contour_src.points[idx_point]);
|
||||
for (; it != it_end && it->first.contour_idx == idx_contour && it->first.start_point_idx == idx_point; ++ it) {
|
||||
// Add these points to the destination contour.
|
||||
const Vec2d pt1 = contour_src[idx_point].cast<double>();
|
||||
const Vec2d pt2 = (idx_point + 1 == contour_src.size() ? contour_src.points.front() : contour_src.points[idx_point + 1]).cast<double>();
|
||||
const Vec2d pt = lerp(pt1, pt2, it->first.t);
|
||||
map_infill_end_point_to_boundary[it->second] = std::make_pair(idx_contour, contour_dst.size());
|
||||
contour_dst.emplace_back(pt.cast<coord_t>());
|
||||
}
|
||||
}
|
||||
// Parametrize the curve.
|
||||
std::vector<ContourPointData> &contour_data = boundary_data[idx_contour];
|
||||
contour_data.reserve(contour_dst.size());
|
||||
contour_data.emplace_back(ContourPointData(0.f));
|
||||
for (size_t i = 1; i < contour_dst.size(); ++ i)
|
||||
contour_data.emplace_back(contour_data.back().param + (contour_dst[i].cast<float>() - contour_dst[i - 1].cast<float>()).norm());
|
||||
contour_data.front().param = contour_data.back().param + (contour_dst.back().cast<float>() - contour_dst.front().cast<float>()).norm();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
assert(boundary.size() == boundary_src.num_contours());
|
||||
assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(),
|
||||
[&boundary](const std::pair<size_t, size_t> &contour_point) {
|
||||
return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size();
|
||||
}));
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
|
||||
// Mark the points and segments of split boundary as consumed if they are very close to some of the infill line.
|
||||
{
|
||||
//const double clip_distance = scale_(this->spacing);
|
||||
const double clip_distance = 3. * scale_(this->spacing);
|
||||
const double distance_colliding = scale_(this->spacing);
|
||||
mark_boundary_segments_touching_infill(boundary, boundary_data, bbox, infill_ordered, clip_distance, distance_colliding);
|
||||
}
|
||||
|
||||
// Connection from end of one infill line to the start of another infill line.
|
||||
//const float length_max = scale_(this->spacing);
|
||||
// const float length_max = scale_((2. / params.density) * this->spacing);
|
||||
const float length_max = scale_((1000. / params.density) * this->spacing);
|
||||
std::vector<size_t> merged_with(infill_ordered.size());
|
||||
for (size_t i = 0; i < merged_with.size(); ++ i)
|
||||
merged_with[i] = i;
|
||||
struct ConnectionCost {
|
||||
ConnectionCost(size_t idx_first, double cost, bool reversed) : idx_first(idx_first), cost(cost), reversed(reversed) {}
|
||||
size_t idx_first;
|
||||
double cost;
|
||||
bool reversed;
|
||||
};
|
||||
std::vector<ConnectionCost> connections_sorted;
|
||||
connections_sorted.reserve(infill_ordered.size() * 2 - 2);
|
||||
for (size_t idx_chain = 1; idx_chain < infill_ordered.size(); ++ idx_chain) {
|
||||
const Polyline &pl1 = infill_ordered[idx_chain - 1];
|
||||
const Polyline &pl2 = infill_ordered[idx_chain];
|
||||
const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1];
|
||||
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2];
|
||||
const std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
|
||||
if (cp1->first == cp2->first) {
|
||||
// End points on the same contour. Try to connect them.
|
||||
float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param;
|
||||
float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param;
|
||||
float param_end = contour_data.front().param;
|
||||
bool reversed = false;
|
||||
if (param_lo > param_hi) {
|
||||
std::swap(param_lo, param_hi);
|
||||
reversed = true;
|
||||
}
|
||||
assert(param_lo >= 0.f && param_lo <= param_end);
|
||||
assert(param_hi >= 0.f && param_hi <= param_end);
|
||||
double len = param_hi - param_lo;
|
||||
if (len < length_max)
|
||||
connections_sorted.emplace_back(idx_chain - 1, len, reversed);
|
||||
len = param_lo + param_end - param_hi;
|
||||
if (len < length_max)
|
||||
connections_sorted.emplace_back(idx_chain - 1, len, ! reversed);
|
||||
}
|
||||
}
|
||||
std::sort(connections_sorted.begin(), connections_sorted.end(), [](const ConnectionCost& l, const ConnectionCost& r) { return l.cost < r.cost; });
|
||||
|
||||
size_t idx_chain_last = 0;
|
||||
for (ConnectionCost &connection_cost : connections_sorted) {
|
||||
const std::pair<size_t, size_t> *cp1 = &map_infill_end_point_to_boundary[connection_cost.idx_first * 2 + 1];
|
||||
const std::pair<size_t, size_t> *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2];
|
||||
assert(cp1->first == cp2->first);
|
||||
std::vector<ContourPointData> &contour_data = boundary_data[cp1->first];
|
||||
if (connection_cost.reversed)
|
||||
std::swap(cp1, cp2);
|
||||
if (could_take(contour_data, cp1->second, cp2->second)) {
|
||||
// Indices of the polygons to be connected.
|
||||
size_t idx_first = connection_cost.idx_first;
|
||||
size_t idx_second = idx_first + 1;
|
||||
for (size_t last = idx_first;;) {
|
||||
size_t lower = merged_with[last];
|
||||
if (lower == last) {
|
||||
merged_with[idx_first] = lower;
|
||||
idx_first = lower;
|
||||
break;
|
||||
}
|
||||
last = lower;
|
||||
}
|
||||
// Connect the two polygons using the boundary contour.
|
||||
take(infill_ordered[idx_first], std::move(infill_ordered[idx_second]), boundary[cp1->first], contour_data, cp1->second, cp2->second, connection_cost.reversed);
|
||||
// Mark the second polygon as merged with the first one.
|
||||
merged_with[idx_second] = merged_with[idx_first];
|
||||
}
|
||||
}
|
||||
polylines_out.reserve(polylines_out.size() + std::count_if(infill_ordered.begin(), infill_ordered.end(), [](const Polyline &pl) { return ! pl.empty(); }));
|
||||
for (Polyline &pl : infill_ordered)
|
||||
if (! pl.empty())
|
||||
polylines_out.emplace_back(std::move(pl));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ protected:
|
|||
|
||||
virtual std::pair<float, Point> _infill_direction(const Surface *surface) const;
|
||||
|
||||
void connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary, Polylines &polylines_out, const FillParams ¶ms);
|
||||
|
||||
public:
|
||||
static coord_t _adjust_solid_spacing(const coord_t width, const coord_t distance);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,19 +31,26 @@ static inline double f(double x, double z_sin, double z_cos, bool vertical, bool
|
|||
|
||||
static inline Polyline make_wave(
|
||||
const std::vector<Vec2d>& one_period, double width, double height, double offset, double scaleFactor,
|
||||
double z_cos, double z_sin, bool vertical)
|
||||
double z_cos, double z_sin, bool vertical, bool flip)
|
||||
{
|
||||
std::vector<Vec2d> points = one_period;
|
||||
double period = points.back()(0);
|
||||
points.pop_back();
|
||||
int n = points.size();
|
||||
do {
|
||||
points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1)));
|
||||
} while (points.back()(0) < width);
|
||||
points.back()(0) = width;
|
||||
if (width != period) // do not extend if already truncated
|
||||
{
|
||||
points.reserve(one_period.size() * floor(width / period));
|
||||
points.pop_back();
|
||||
|
||||
int n = points.size();
|
||||
do {
|
||||
points.emplace_back(Vec2d(points[points.size()-n](0) + period, points[points.size()-n](1)));
|
||||
} while (points.back()(0) < width - EPSILON);
|
||||
|
||||
points.emplace_back(Vec2d(width, f(width, z_sin, z_cos, vertical, flip)));
|
||||
}
|
||||
|
||||
// and construct the final polyline to return:
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(points.size());
|
||||
for (auto& point : points) {
|
||||
point(1) += offset;
|
||||
point(1) = clamp(0., height, double(point(1)));
|
||||
|
|
@ -55,45 +62,56 @@ static inline Polyline make_wave(
|
|||
return polyline;
|
||||
}
|
||||
|
||||
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip)
|
||||
static std::vector<Vec2d> make_one_period(double width, double scaleFactor, double z_cos, double z_sin, bool vertical, bool flip, double tolerance)
|
||||
{
|
||||
std::vector<Vec2d> points;
|
||||
double dx = M_PI_4; // very coarse spacing to begin with
|
||||
double dx = M_PI_2; // exact coordinates on main inflexion lobes
|
||||
double limit = std::min(2*M_PI, width);
|
||||
for (double x = 0.; x < limit + EPSILON; x += dx) { // so the last point is there too
|
||||
x = std::min(x, limit);
|
||||
points.emplace_back(Vec2d(x,f(x, z_sin,z_cos, vertical, flip)));
|
||||
}
|
||||
points.reserve(ceil(limit / tolerance / 3));
|
||||
|
||||
// now we will check all internal points and in case some are too far from the line connecting its neighbours,
|
||||
// we will add one more point on each side:
|
||||
const double tolerance = .1;
|
||||
for (unsigned int i=1;i<points.size()-1;++i) {
|
||||
auto& lp = points[i-1]; // left point
|
||||
auto& tp = points[i]; // this point
|
||||
Vec2d lrv = tp - lp;
|
||||
auto& rp = points[i+1]; // right point
|
||||
// calculate distance of the point to the line:
|
||||
double dist_mm = unscale<double>(scaleFactor) * std::abs(cross2(rp, lp) - cross2(rp - lp, tp)) / lrv.norm();
|
||||
if (dist_mm > tolerance) { // if the difference from straight line is more than this
|
||||
double x = 0.5f * (points[i-1](0) + points[i](0));
|
||||
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
|
||||
x = 0.5f * (points[i+1](0) + points[i](0));
|
||||
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
|
||||
// we added the points to the end, but need them all in order
|
||||
std::sort(points.begin(), points.end(), [](const Vec2d &lhs, const Vec2d &rhs){ return lhs < rhs; });
|
||||
// decrement i so we also check the first newly added point
|
||||
--i;
|
||||
for (double x = 0.; x < limit - EPSILON; x += dx) {
|
||||
points.emplace_back(Vec2d(x, f(x, z_sin, z_cos, vertical, flip)));
|
||||
}
|
||||
points.emplace_back(Vec2d(limit, f(limit, z_sin, z_cos, vertical, flip)));
|
||||
|
||||
// piecewise increase in resolution up to requested tolerance
|
||||
for(;;)
|
||||
{
|
||||
size_t size = points.size();
|
||||
for (unsigned int i = 1;i < size; ++i) {
|
||||
auto& lp = points[i-1]; // left point
|
||||
auto& rp = points[i]; // right point
|
||||
double x = lp(0) + (rp(0) - lp(0)) / 2;
|
||||
double y = f(x, z_sin, z_cos, vertical, flip);
|
||||
Vec2d ip = {x, y};
|
||||
if (std::abs(cross2(Vec2d(ip - lp), Vec2d(ip - rp))) > sqr(tolerance)) {
|
||||
points.emplace_back(std::move(ip));
|
||||
}
|
||||
}
|
||||
|
||||
if (size == points.size())
|
||||
break;
|
||||
else
|
||||
{
|
||||
// insert new points in order
|
||||
std::sort(points.begin(), points.end(),
|
||||
[](const Vec2d &lhs, const Vec2d &rhs) { return lhs(0) < rhs(0); });
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double line_spacing, double width, double height)
|
||||
{
|
||||
const double scaleFactor = scale_(line_spacing) / density_adjusted;
|
||||
//scale factor for 5% : 8 712 388
|
||||
// 1z = 10^-6 mm ?
|
||||
|
||||
// tolerance in scaled units. clamp the maximum tolerance as there's
|
||||
// no processing-speed benefit to do so beyond a certain point
|
||||
const double tolerance = std::min(line_spacing / 2, FillGyroid::PatternTolerance) / unscale<double>(scaleFactor);
|
||||
|
||||
//scale factor for 5% : 8 712 388
|
||||
// 1z = 10^-6 mm ?
|
||||
const double z = gridZ / scaleFactor;
|
||||
const double z_sin = sin(z);
|
||||
const double z_cos = cos(z);
|
||||
|
|
@ -109,20 +127,27 @@ static Polylines make_gyroid_waves(double gridZ, double density_adjusted, double
|
|||
std::swap(width,height);
|
||||
}
|
||||
|
||||
std::vector<Vec2d> one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
std::vector<Vec2d> one_period_odd = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance); // creates one period of the waves, so it doesn't have to be recalculated all the time
|
||||
flip = !flip; // even polylines are a bit shifted
|
||||
std::vector<Vec2d> one_period_even = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip, tolerance);
|
||||
Polylines result;
|
||||
|
||||
for (double y0 = lower_bound; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates odd polylines
|
||||
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
|
||||
|
||||
flip = !flip; // even polylines are a bit shifted
|
||||
one_period = make_one_period(width, scaleFactor, z_cos, z_sin, vertical, flip); // updates the one period sample
|
||||
for (double y0 = lower_bound + M_PI; y0 < upper_bound+EPSILON; y0 += 2*M_PI) // creates even polylines
|
||||
result.emplace_back(make_wave(one_period, width, height, y0, scaleFactor, z_cos, z_sin, vertical));
|
||||
for (double y0 = lower_bound; y0 < upper_bound + EPSILON; y0 += M_PI) {
|
||||
// creates odd polylines
|
||||
result.emplace_back(make_wave(one_period_odd, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
// creates even polylines
|
||||
y0 += M_PI;
|
||||
if (y0 < upper_bound + EPSILON) {
|
||||
result.emplace_back(make_wave(one_period_even, width, height, y0, scaleFactor, z_cos, z_sin, vertical, flip));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// FIXME: needed to fix build on Mac on buildserver
|
||||
constexpr double FillGyroid::PatternTolerance;
|
||||
|
||||
void FillGyroid::_fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
unsigned int thickness_layers,
|
||||
|
|
@ -130,63 +155,52 @@ void FillGyroid::_fill_surface_single(
|
|||
ExPolygon &expolygon,
|
||||
Polylines &polylines_out)
|
||||
{
|
||||
// no rotation is supported for this infill pattern (yet)
|
||||
float infill_angle = this->angle + (CorrectionAngle * 2*M_PI) / 360.;
|
||||
if(abs(infill_angle) >= EPSILON)
|
||||
expolygon.rotate(-infill_angle);
|
||||
|
||||
BoundingBox bb = expolygon.contour.bounding_box();
|
||||
// Density adjusted to have a good %of weight.
|
||||
double density_adjusted = std::max(0., params.density * 2.44);
|
||||
double density_adjusted = std::max(0., params.density * DensityAdjust);
|
||||
// Distance between the gyroid waves in scaled coordinates.
|
||||
coord_t distance = coord_t(scale_(this->spacing) / density_adjusted);
|
||||
|
||||
// align bounding box to a multiple of our grid module
|
||||
bb.merge(_align_to_grid(bb.min, Point(2.*M_PI*distance, 2.*M_PI*distance)));
|
||||
bb.merge(_align_to_grid(bb.min, Point(2*M_PI*distance, 2*M_PI*distance)));
|
||||
|
||||
// generate pattern
|
||||
Polylines polylines = make_gyroid_waves(
|
||||
Polylines polylines = make_gyroid_waves(
|
||||
scale_(this->z),
|
||||
density_adjusted,
|
||||
this->spacing,
|
||||
ceil(bb.size()(0) / distance) + 1.,
|
||||
ceil(bb.size()(1) / distance) + 1.);
|
||||
|
||||
// move pattern in place
|
||||
for (Polyline &polyline : polylines)
|
||||
polyline.translate(bb.min(0), bb.min(1));
|
||||
|
||||
// clip pattern to boundaries
|
||||
polylines = intersection_pl(polylines, (Polygons)expolygon);
|
||||
// shift the polyline to the grid origin
|
||||
for (Polyline &pl : polylines)
|
||||
pl.translate(bb.min);
|
||||
|
||||
// connect lines
|
||||
if (! params.dont_connect && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections
|
||||
ExPolygon expolygon_off;
|
||||
{
|
||||
ExPolygons expolygons_off = offset_ex(expolygon, (float)SCALED_EPSILON);
|
||||
if (! expolygons_off.empty()) {
|
||||
// When expanding a polygon, the number of islands could only shrink. Therefore the offset_ex shall generate exactly one expanded island for one input island.
|
||||
assert(expolygons_off.size() == 1);
|
||||
std::swap(expolygon_off, expolygons_off.front());
|
||||
}
|
||||
}
|
||||
bool first = true;
|
||||
for (Polyline &polyline : chain_polylines(std::move(polylines))) {
|
||||
if (! first) {
|
||||
// Try to connect the lines.
|
||||
Points &pts_end = polylines_out.back().points;
|
||||
const Point &first_point = polyline.points.front();
|
||||
const Point &last_point = pts_end.back();
|
||||
// TODO: we should also check that both points are on a fill_boundary to avoid
|
||||
// connecting paths on the boundaries of internal regions
|
||||
// TODO: avoid crossing current infill path
|
||||
if ((last_point - first_point).cast<double>().norm() <= 5 * distance &&
|
||||
expolygon_off.contains(Line(last_point, first_point))) {
|
||||
// Append the polyline.
|
||||
pts_end.insert(pts_end.end(), polyline.points.begin(), polyline.points.end());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// The lines cannot be connected.
|
||||
polylines_out.emplace_back(std::move(polyline));
|
||||
first = false;
|
||||
}
|
||||
polylines = intersection_pl(polylines, to_polygons(expolygon));
|
||||
|
||||
if (! polylines.empty())
|
||||
// remove too small bits (larger than longer)
|
||||
polylines.erase(
|
||||
std::remove_if(polylines.begin(), polylines.end(), [this](const Polyline &pl) { return pl.length() < scale_(this->spacing * 3); }),
|
||||
polylines.end());
|
||||
|
||||
if (! polylines.empty()) {
|
||||
polylines = chain_polylines(polylines);
|
||||
// connect lines
|
||||
size_t polylines_out_first_idx = polylines_out.size();
|
||||
if (params.dont_connect)
|
||||
append(polylines_out, std::move(polylines));
|
||||
else
|
||||
this->connect_infill(std::move(polylines), expolygon, polylines_out, params);
|
||||
// new paths must be rotated back
|
||||
if (abs(infill_angle) >= EPSILON) {
|
||||
for (auto it = polylines_out.begin() + polylines_out_first_idx; it != polylines_out.end(); ++ it)
|
||||
it->rotate(infill_angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@ public:
|
|||
// require bridge flow since most of this pattern hangs in air
|
||||
virtual bool use_bridge_flow() const { return false; }
|
||||
|
||||
// Correction applied to regular infill angle to maximize printing
|
||||
// speed in default configuration (degrees)
|
||||
static constexpr float CorrectionAngle = -45.;
|
||||
|
||||
// Density adjustment to have a good %of weight.
|
||||
static constexpr double DensityAdjust = 2.44;
|
||||
|
||||
// Gyroid upper resolution tolerance (mm^-2)
|
||||
static constexpr double PatternTolerance = 0.2;
|
||||
|
||||
|
||||
protected:
|
||||
virtual void _fill_surface_single(
|
||||
const FillParams ¶ms,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
#include "../Utils.hpp"
|
||||
#include "../GCode.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "../GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#include "../I18N.hpp"
|
||||
|
||||
|
|
@ -40,6 +43,9 @@ const std::string MODEL_EXTENSION = ".model";
|
|||
const std::string MODEL_FILE = "3D/3dmodel.model"; // << this is the only format of the string which works with CURA
|
||||
const std::string CONTENT_TYPES_FILE = "[Content_Types].xml";
|
||||
const std::string RELATIONSHIPS_FILE = "_rels/.rels";
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png";
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config";
|
||||
const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
|
||||
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
|
||||
|
|
@ -1851,11 +1857,22 @@ namespace Slic3r {
|
|||
typedef std::map<int, ObjectData> IdToObjectDataMap;
|
||||
|
||||
public:
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
|
||||
#else
|
||||
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
private:
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data);
|
||||
#else
|
||||
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _add_content_types_file_to_archive(mz_zip_archive& archive);
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _add_relationships_file_to_archive(mz_zip_archive& archive);
|
||||
bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data);
|
||||
bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets);
|
||||
|
|
@ -1869,13 +1886,25 @@ namespace Slic3r {
|
|||
bool _add_custom_gcode_per_height_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
};
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
|
||||
{
|
||||
clear_errors();
|
||||
return _save_model_to_file(filename, model, config, thumbnail_data);
|
||||
}
|
||||
#else
|
||||
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
|
||||
{
|
||||
clear_errors();
|
||||
return _save_model_to_file(filename, model, config);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
|
||||
#else
|
||||
bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
|
@ -1894,6 +1923,19 @@ namespace Slic3r {
|
|||
return false;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if ((thumbnail_data != nullptr) && thumbnail_data->is_valid())
|
||||
{
|
||||
// Adds the file Metadata/thumbnail.png.
|
||||
if (!_add_thumbnail_file_to_archive(archive, *thumbnail_data))
|
||||
{
|
||||
close_zip_writer(&archive);
|
||||
boost::filesystem::remove(filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Adds relationships file ("_rels/.rels").
|
||||
// The content of this file is the same for each PrusaSlicer 3mf.
|
||||
// The relationshis file contains a reference to the geometry file "3D/3dmodel.model", the name was chosen to be compatible with CURA.
|
||||
|
|
@ -1996,6 +2038,9 @@ namespace Slic3r {
|
|||
stream << "<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n";
|
||||
stream << " <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\" />\n";
|
||||
stream << " <Default Extension=\"model\" ContentType=\"application/vnd.ms-package.3dmanufacturing-3dmodel+xml\" />\n";
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
stream << " <Default Extension=\"png\" ContentType=\"image/png\" />\n";
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
stream << "</Types>";
|
||||
|
||||
std::string out = stream.str();
|
||||
|
|
@ -2009,12 +2054,35 @@ namespace Slic3r {
|
|||
return true;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool _3MF_Exporter::_add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data)
|
||||
{
|
||||
bool res = false;
|
||||
|
||||
size_t png_size = 0;
|
||||
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)thumbnail_data.pixels.data(), thumbnail_data.width, thumbnail_data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
|
||||
if (png_data != nullptr)
|
||||
{
|
||||
res = mz_zip_writer_add_mem(&archive, THUMBNAIL_FILE.c_str(), (const void*)png_data, png_size, MZ_DEFAULT_COMPRESSION);
|
||||
mz_free(png_data);
|
||||
}
|
||||
|
||||
if (!res)
|
||||
add_error("Unable to add thumbnail file to archive");
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
bool _3MF_Exporter::_add_relationships_file_to_archive(mz_zip_archive& archive)
|
||||
{
|
||||
std::stringstream stream;
|
||||
stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
||||
stream << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n";
|
||||
stream << " <Relationship Target=\"/" << MODEL_FILE << "\" Id=\"rel-1\" Type=\"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\" />\n";
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
stream << " <Relationship Target=\"/" << THUMBNAIL_FILE << "\" Id=\"rel-2\" Type=\"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail\" />\n";
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
stream << "</Relationships>";
|
||||
|
||||
std::string out = stream.str();
|
||||
|
|
@ -2550,13 +2618,21 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c
|
|||
return res;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data)
|
||||
#else
|
||||
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
if ((path == nullptr) || (model == nullptr))
|
||||
return false;
|
||||
|
||||
_3MF_Exporter exporter;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data);
|
||||
#else
|
||||
bool res = exporter.save_model_to_file(path, *model, config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
if (!res)
|
||||
exporter.log_errors();
|
||||
|
|
|
|||
|
|
@ -22,13 +22,20 @@ namespace Slic3r {
|
|||
|
||||
class Model;
|
||||
class DynamicPrintConfig;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Load the content of a 3mf file into the given model and preset bundle.
|
||||
extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version);
|
||||
|
||||
// Save the given model and the config data contained in the given Print into a 3mf file.
|
||||
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr);
|
||||
#else
|
||||
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@
|
|||
#include "Geometry.hpp"
|
||||
#include "GCode/PrintExtents.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "ShortestPath.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
|
|
@ -18,6 +21,9 @@
|
|||
#include <boost/foreach.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include <boost/beast/core/detail/base64.hpp>
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
|
|
@ -29,6 +35,10 @@
|
|||
|
||||
#include <Shiny/Shiny.h>
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
#include "miniz_extension.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
#if 0
|
||||
// Enable debugging and asserts, even in the release build.
|
||||
#define DEBUG
|
||||
|
|
@ -275,7 +285,7 @@ static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2
|
|||
return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const
|
||||
std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z) const
|
||||
{
|
||||
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
|
||||
throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
|
|
@ -311,6 +321,15 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
gcode += gcodegen.unretract();
|
||||
}
|
||||
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||
z = current_z;
|
||||
if (! is_approx(z, current_z)) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
|
||||
// Process the end filament gcode.
|
||||
std::string end_filament_gcode_str;
|
||||
|
|
@ -377,16 +396,23 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
if (! is_approx(z, current_z)) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
// Prepare a future wipe.
|
||||
gcodegen.m_wipe.path.points.clear();
|
||||
if (new_extruder_id >= 0) {
|
||||
// Start the wipe at the current position.
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
|
||||
Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
|
||||
end_pos.y())));
|
||||
else {
|
||||
// Prepare a future wipe.
|
||||
gcodegen.m_wipe.path.points.clear();
|
||||
if (new_extruder_id >= 0) {
|
||||
// Start the wipe at the current position.
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
// Wipe end point: Wipe direction away from the closer tower edge to the further tower edge.
|
||||
gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen,
|
||||
Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left,
|
||||
end_pos.y())));
|
||||
}
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
|
|
@ -512,7 +538,23 @@ std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id,
|
|||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||
if (! (size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||
throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id);
|
||||
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = (m_brim_done && m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool);
|
||||
if (m_tool_change_idx == 0 && ! ignore_sparse)
|
||||
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
|
||||
}
|
||||
|
||||
if (! ignore_sparse) {
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
|
||||
m_last_wipe_tower_print_z = wipe_tower_z;
|
||||
}
|
||||
}
|
||||
m_brim_done = true;
|
||||
}
|
||||
|
|
@ -652,7 +694,11 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||
return layers_to_print;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
|
||||
#else
|
||||
void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
PROFILE_CLEAR();
|
||||
|
||||
|
|
@ -678,7 +724,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
|
||||
try {
|
||||
m_placeholder_parser_failed_templates.clear();
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
this->_do_export(*print, file, thumbnail_data);
|
||||
#else
|
||||
this->_do_export(*print, file);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
fflush(file);
|
||||
if (ferror(file)) {
|
||||
fclose(file);
|
||||
|
|
@ -742,7 +792,11 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str());
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GCode::_do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data)
|
||||
#else
|
||||
void GCode::_do_export(Print &print, FILE *file)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
PROFILE_FUNC();
|
||||
|
||||
|
|
@ -937,6 +991,82 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
|
||||
// Write information on the generator.
|
||||
_write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// Write thumbnails using base64 encoding
|
||||
if (thumbnail_data != nullptr)
|
||||
{
|
||||
const size_t max_row_length = 78;
|
||||
|
||||
for (const ThumbnailData& data : *thumbnail_data)
|
||||
{
|
||||
if (data.is_valid())
|
||||
{
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
size_t png_size = 0;
|
||||
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
|
||||
if (png_data != nullptr)
|
||||
{
|
||||
std::string encoded;
|
||||
encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
|
||||
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));
|
||||
|
||||
_write_format(file, "\n;\n; thumbnail begin %dx%d %d\n", data.width, data.height, encoded.size());
|
||||
|
||||
unsigned int row_count = 0;
|
||||
while (encoded.size() > max_row_length)
|
||||
{
|
||||
_write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
|
||||
encoded = encoded.substr(max_row_length);
|
||||
++row_count;
|
||||
}
|
||||
|
||||
if (encoded.size() > 0)
|
||||
_write_format(file, "; %s\n", encoded.c_str());
|
||||
|
||||
_write(file, "; thumbnail end\n;\n");
|
||||
|
||||
mz_free(png_data);
|
||||
}
|
||||
#else
|
||||
_write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height);
|
||||
|
||||
size_t row_size = 4 * data.width;
|
||||
for (int r = (int)data.height - 1; r >= 0; --r)
|
||||
{
|
||||
std::string encoded;
|
||||
encoded.resize(boost::beast::detail::base64::encoded_size(row_size));
|
||||
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)(data.pixels.data() + r * row_size), row_size));
|
||||
|
||||
unsigned int row_count = 0;
|
||||
while (encoded.size() > max_row_length)
|
||||
{
|
||||
if (row_count == 0)
|
||||
_write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
|
||||
else
|
||||
_write_format(file, ";>%s\n", encoded.substr(0, max_row_length).c_str());
|
||||
|
||||
encoded = encoded.substr(max_row_length);
|
||||
++row_count;
|
||||
}
|
||||
|
||||
if (encoded.size() > 0)
|
||||
{
|
||||
if (row_count == 0)
|
||||
_write_format(file, "; %s\n", encoded.c_str());
|
||||
else
|
||||
_write_format(file, ";>%s\n", encoded.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
_write(file, "; thumbnail end\n;\n");
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Write notes (content of the Print Settings tab -> Notes)
|
||||
{
|
||||
std::list<std::string> lines;
|
||||
|
|
@ -978,6 +1108,9 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
_writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag);
|
||||
}
|
||||
|
||||
// Hold total number of print toolchanges. Check for negative toolchanges (single extruder mode) and set to 0 (no tool change).
|
||||
int total_toolchanges = std::max(0, print.wipe_tower_data().number_of_toolchanges);
|
||||
|
||||
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
|
||||
m_placeholder_parser = print.placeholder_parser();
|
||||
m_placeholder_parser.update_timestamp();
|
||||
|
|
@ -1083,6 +1216,7 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
// For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided.
|
||||
m_placeholder_parser.set("has_wipe_tower", has_wipe_tower);
|
||||
m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming);
|
||||
m_placeholder_parser.set("total_toolchanges", total_toolchanges);
|
||||
std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id);
|
||||
// Set bed temperature if the start G-code does not contain any bed temp control G-codes.
|
||||
this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true);
|
||||
|
|
@ -1333,7 +1467,7 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true);
|
||||
if (m_silent_time_estimator_enabled)
|
||||
print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true);
|
||||
|
||||
print.m_print_statistics.total_toolchanges = total_toolchanges;
|
||||
std::vector<Extruder> extruders = m_writer.extruders();
|
||||
if (! extruders.empty()) {
|
||||
std::pair<std::string, unsigned int> out_filament_used_mm ("; filament used [mm] = ", 0);
|
||||
|
|
@ -1383,6 +1517,8 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
}
|
||||
_write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight);
|
||||
_write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost);
|
||||
if (print.m_print_statistics.total_toolchanges > 0)
|
||||
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
|
||||
_write_format(file, "; estimated printing time (normal mode) = %s\n", m_normal_time_estimator.get_time_dhms().c_str());
|
||||
if (m_silent_time_estimator_enabled)
|
||||
_write_format(file, "; estimated printing time (silent mode) = %s\n", m_silent_time_estimator.get_time_dhms().c_str());
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ namespace Slic3r {
|
|||
// Forward declarations.
|
||||
class GCode;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
class AvoidCrossingPerimeters {
|
||||
public:
|
||||
|
|
@ -110,7 +113,7 @@ public:
|
|||
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
|
|
@ -131,6 +134,7 @@ private:
|
|||
int m_tool_change_idx;
|
||||
bool m_brim_done;
|
||||
bool i_have_brim = false;
|
||||
double m_last_wipe_tower_print_z = 0.f;
|
||||
};
|
||||
|
||||
class GCode {
|
||||
|
|
@ -162,7 +166,11 @@ public:
|
|||
|
||||
// throws std::runtime_exception on error,
|
||||
// throws CanceledException through print->throw_if_canceled().
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
|
||||
#else
|
||||
void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests.
|
||||
const Vec2d& origin() const { return m_origin; }
|
||||
|
|
@ -190,7 +198,11 @@ public:
|
|||
static void append_full_config(const Print& print, std::string& str);
|
||||
|
||||
protected:
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void _do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data);
|
||||
#else
|
||||
void _do_export(Print &print, FILE *file);
|
||||
#endif //ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Object and support extrusions of the same PrintObject at the same print_z.
|
||||
struct LayerToPrint
|
||||
|
|
|
|||
36
src/libslic3r/GCode/ThumbnailData.cpp
Normal file
36
src/libslic3r/GCode/ThumbnailData.cpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#include "libslic3r/libslic3r.h"
|
||||
#include "ThumbnailData.hpp"
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void ThumbnailData::set(unsigned int w, unsigned int h)
|
||||
{
|
||||
if ((w == 0) || (h == 0))
|
||||
return;
|
||||
|
||||
if ((width != w) || (height != h))
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
// defaults to white texture
|
||||
pixels = std::vector<unsigned char>(width * height * 4, 255);
|
||||
}
|
||||
}
|
||||
|
||||
void ThumbnailData::reset()
|
||||
{
|
||||
width = 0;
|
||||
height = 0;
|
||||
pixels.clear();
|
||||
}
|
||||
|
||||
bool ThumbnailData::is_valid() const
|
||||
{
|
||||
return (width != 0) && (height != 0) && ((unsigned int)pixels.size() == 4 * width * height);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
27
src/libslic3r/GCode/ThumbnailData.hpp
Normal file
27
src/libslic3r/GCode/ThumbnailData.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef slic3r_ThumbnailData_hpp_
|
||||
#define slic3r_ThumbnailData_hpp_
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct ThumbnailData
|
||||
{
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
std::vector<unsigned char> pixels;
|
||||
|
||||
ThumbnailData() { reset(); }
|
||||
void set(unsigned int w, unsigned int h);
|
||||
void reset();
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#endif // slic3r_ThumbnailData_hpp_
|
||||
|
|
@ -474,6 +474,7 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector<std::vector<fl
|
|||
m_z_pos(0.f),
|
||||
m_is_first_layer(false),
|
||||
m_bridging(float(config.wipe_tower_bridging)),
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_current_tool(initial_tool),
|
||||
wipe_volumes(wiping_matrix)
|
||||
|
|
@ -1145,9 +1146,10 @@ WipeTower::ToolChangeResult WipeTower::finish_layer()
|
|||
writer.set_initial_position((m_left_to_right ? fill_box.ru : fill_box.lu), // so there is never a diagonal travel
|
||||
m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation);
|
||||
|
||||
bool toolchanges_on_layer = m_layer_info->toolchanges_depth() > WT_EPSILON;
|
||||
box_coordinates box = fill_box;
|
||||
for (int i=0;i<2;++i) {
|
||||
if (m_layer_info->toolchanges_depth() < WT_EPSILON) { // there were no toolchanges on this layer
|
||||
if (! toolchanges_on_layer) {
|
||||
if (i==0) box.expand(m_perimeter_width);
|
||||
else box.expand(-m_perimeter_width);
|
||||
}
|
||||
|
|
@ -1201,9 +1203,12 @@ WipeTower::ToolChangeResult WipeTower::finish_layer()
|
|||
|
||||
m_depth_traversed = m_wipe_tower_depth-m_perimeter_width;
|
||||
|
||||
// Ask our writer about how much material was consumed:
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
// Ask our writer about how much material was consumed.
|
||||
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
|
||||
if (! m_no_sparse_layers || toolchanges_on_layer)
|
||||
if (m_current_tool < m_used_filament_length.size())
|
||||
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
|
||||
|
||||
ToolChangeResult result;
|
||||
result.priming = false;
|
||||
|
|
|
|||
|
|
@ -220,6 +220,7 @@ private:
|
|||
float m_parking_pos_retraction = 0.f;
|
||||
float m_extra_loading_move = 0.f;
|
||||
float m_bridging = 0.f;
|
||||
bool m_no_sparse_layers = false;
|
||||
bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ public:
|
|||
if (indices.empty())
|
||||
clear();
|
||||
else {
|
||||
// Allocate a next highest power of 2 nodes, because the incomplete binary tree will not have the leaves filled strictly from the left.
|
||||
// Allocate enough memory for a full binary tree.
|
||||
m_nodes.assign(next_highest_power_of_2(indices.size() + 1), npos);
|
||||
build_recursive(indices, 0, 0, 0, (int)(indices.size() - 1));
|
||||
build_recursive(indices, 0, 0, 0, indices.size() - 1);
|
||||
}
|
||||
indices.clear();
|
||||
}
|
||||
|
|
@ -81,7 +81,7 @@ public:
|
|||
|
||||
private:
|
||||
// Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension.
|
||||
void build_recursive(std::vector<size_t> &input, size_t node, int dimension, int left, int right)
|
||||
void build_recursive(std::vector<size_t> &input, size_t node, const size_t dimension, const size_t left, const size_t right)
|
||||
{
|
||||
if (left > right)
|
||||
return;
|
||||
|
|
@ -94,54 +94,56 @@ private:
|
|||
return;
|
||||
}
|
||||
|
||||
// Partition the input sequence to two equal halves.
|
||||
int center = (left + right) >> 1;
|
||||
// Partition the input to left / right pieces of the same length to produce a balanced tree.
|
||||
size_t center = (left + right) / 2;
|
||||
partition_input(input, dimension, left, right, center);
|
||||
// Insert a node into the tree.
|
||||
m_nodes[node] = input[center];
|
||||
// Partition the left and right subtrees.
|
||||
size_t next_dimension = (++ dimension == NumDimensions) ? 0 : dimension;
|
||||
build_recursive(input, (node << 1) + 1, next_dimension, left, center - 1);
|
||||
build_recursive(input, (node << 1) + 2, next_dimension, center + 1, right);
|
||||
// Build up the left / right subtrees.
|
||||
size_t next_dimension = dimension;
|
||||
if (++ next_dimension == NumDimensions)
|
||||
next_dimension = 0;
|
||||
if (center > left)
|
||||
build_recursive(input, node * 2 + 1, next_dimension, left, center - 1);
|
||||
build_recursive(input, node * 2 + 2, next_dimension, center + 1, right);
|
||||
}
|
||||
|
||||
// Partition the input m_nodes <left, right> at k using QuickSelect method.
|
||||
// Partition the input m_nodes <left, right> at "k" and "dimension" using the QuickSelect method:
|
||||
// https://en.wikipedia.org/wiki/Quickselect
|
||||
void partition_input(std::vector<size_t> &input, int dimension, int left, int right, int k) const
|
||||
// Items left of the k'th item are lower than the k'th item in the "dimension",
|
||||
// items right of the k'th item are higher than the k'th item in the "dimension",
|
||||
void partition_input(std::vector<size_t> &input, const size_t dimension, size_t left, size_t right, const size_t k) const
|
||||
{
|
||||
while (left < right) {
|
||||
// Guess the k'th element.
|
||||
// Pick the pivot as a median of first, center and last value.
|
||||
// Sort first, center and last values.
|
||||
int center = (left + right) >> 1;
|
||||
auto left_value = this->coordinate(input[left], dimension);
|
||||
auto center_value = this->coordinate(input[center], dimension);
|
||||
auto right_value = this->coordinate(input[right], dimension);
|
||||
if (center_value < left_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
size_t center = (left + right) / 2;
|
||||
CoordType pivot;
|
||||
{
|
||||
// Bubble sort the input[left], input[center], input[right], so that a median of the three values
|
||||
// will end up in input[center].
|
||||
CoordType left_value = this->coordinate(input[left], dimension);
|
||||
CoordType center_value = this->coordinate(input[center], dimension);
|
||||
CoordType right_value = this->coordinate(input[right], dimension);
|
||||
if (left_value > center_value) {
|
||||
std::swap(input[left], input[center]);
|
||||
std::swap(left_value, center_value);
|
||||
}
|
||||
if (left_value > right_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
right_value = left_value;
|
||||
}
|
||||
if (center_value > right_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
center_value = right_value;
|
||||
}
|
||||
pivot = center_value;
|
||||
}
|
||||
if (right_value < left_value) {
|
||||
std::swap(input[left], input[right]);
|
||||
std::swap(left_value, right_value);
|
||||
}
|
||||
if (right_value < center_value) {
|
||||
std::swap(input[center], input[right]);
|
||||
// No need to do that, result is not used.
|
||||
// std::swap(center_value, right_value);
|
||||
}
|
||||
// Only two or three values are left and those are sorted already.
|
||||
if (left + 3 > right)
|
||||
if (right <= left + 2)
|
||||
// The <left, right> interval is already sorted.
|
||||
break;
|
||||
// left and right items are already at their correct positions.
|
||||
// input[left].point[dimension] <= input[center].point[dimension] <= input[right].point[dimension]
|
||||
// Move the pivot to the (right - 1) position.
|
||||
std::swap(input[center], input[right - 1]);
|
||||
// Pivot value.
|
||||
double pivot = this->coordinate(input[right - 1], dimension);
|
||||
size_t i = left;
|
||||
size_t j = right - 1;
|
||||
std::swap(input[center], input[j]);
|
||||
// Partition the set based on the pivot.
|
||||
int i = left;
|
||||
int j = right - 1;
|
||||
for (;;) {
|
||||
// Skip left points that are already at correct positions.
|
||||
// Search will certainly stop at position (right - 1), which stores the pivot.
|
||||
|
|
@ -153,7 +155,7 @@ private:
|
|||
std::swap(input[i], input[j]);
|
||||
}
|
||||
// Restore pivot to the center of the sequence.
|
||||
std::swap(input[i], input[right]);
|
||||
std::swap(input[i], input[right - 1]);
|
||||
// Which side the kth element is in?
|
||||
if (k < i)
|
||||
right = i - 1;
|
||||
|
|
@ -173,7 +175,7 @@ private:
|
|||
return;
|
||||
|
||||
// Left / right child node index.
|
||||
size_t left = (node << 1) + 1;
|
||||
size_t left = node * 2 + 1;
|
||||
size_t right = left + 1;
|
||||
unsigned int mask = visitor(m_nodes[node], dimension);
|
||||
if ((mask & (unsigned int)VisitorReturnMask::STOP) == 0) {
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option
|
|||
|| opt_key == "wipe_tower"
|
||||
|| opt_key == "wipe_tower_width"
|
||||
|| opt_key == "wipe_tower_bridging"
|
||||
|| opt_key == "wipe_tower_no_sparse_layers"
|
||||
|| opt_key == "wiping_volumes_matrix"
|
||||
|| opt_key == "parking_pos_retraction"
|
||||
|| opt_key == "cooling_tube_retraction"
|
||||
|
|
@ -1598,7 +1599,11 @@ void Print::process()
|
|||
// The export_gcode may die for various reasons (fails to process output_filename_format,
|
||||
// write error into the G-code, cannot execute post-processing scripts).
|
||||
// It is up to the caller to show an error message.
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
|
||||
#else
|
||||
std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
// output everything to a G-code file
|
||||
// The following call may die if the output_filename_format template substitution fails.
|
||||
|
|
@ -1615,7 +1620,11 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa
|
|||
|
||||
// The following line may die for multiple reasons.
|
||||
GCode gcode;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
gcode.do_export(this, path.c_str(), preview_data, thumbnail_data);
|
||||
#else
|
||||
gcode.do_export(this, path.c_str(), preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
return path.c_str();
|
||||
}
|
||||
|
||||
|
|
@ -2118,6 +2127,7 @@ DynamicConfig PrintStatistics::config() const
|
|||
config.set_key_value("used_filament", new ConfigOptionFloat (this->total_used_filament / 1000.));
|
||||
config.set_key_value("extruded_volume", new ConfigOptionFloat (this->total_extruded_volume));
|
||||
config.set_key_value("total_cost", new ConfigOptionFloat (this->total_cost));
|
||||
config.set_key_value("total_toolchanges", new ConfigOptionInt(this->total_toolchanges));
|
||||
config.set_key_value("total_weight", new ConfigOptionFloat (this->total_weight));
|
||||
config.set_key_value("total_wipe_tower_cost", new ConfigOptionFloat (this->total_wipe_tower_cost));
|
||||
config.set_key_value("total_wipe_tower_filament", new ConfigOptionFloat (this->total_wipe_tower_filament));
|
||||
|
|
@ -2130,7 +2140,7 @@ DynamicConfig PrintStatistics::placeholders()
|
|||
for (const std::string &key : {
|
||||
"print_time", "normal_print_time", "silent_print_time",
|
||||
"used_filament", "extruded_volume", "total_cost", "total_weight",
|
||||
"total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
"total_toolchanges", "total_wipe_tower_cost", "total_wipe_tower_filament"})
|
||||
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
|
||||
return config;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ class PrintObject;
|
|||
class ModelObject;
|
||||
class GCode;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum PrintStep {
|
||||
|
|
@ -250,6 +253,7 @@ struct PrintStatistics
|
|||
double total_used_filament;
|
||||
double total_extruded_volume;
|
||||
double total_cost;
|
||||
int total_toolchanges;
|
||||
double total_weight;
|
||||
double total_wipe_tower_cost;
|
||||
double total_wipe_tower_filament;
|
||||
|
|
@ -270,6 +274,7 @@ struct PrintStatistics
|
|||
total_used_filament = 0.;
|
||||
total_extruded_volume = 0.;
|
||||
total_cost = 0.;
|
||||
total_toolchanges = 0;
|
||||
total_weight = 0.;
|
||||
total_wipe_tower_cost = 0.;
|
||||
total_wipe_tower_filament = 0.;
|
||||
|
|
@ -305,7 +310,11 @@ public:
|
|||
void process() override;
|
||||
// Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
|
||||
// If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
|
||||
#else
|
||||
std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// methods for handling state
|
||||
bool is_step_done(PrintStep step) const { return Inherited::is_step_done(step); }
|
||||
|
|
|
|||
|
|
@ -1327,8 +1327,10 @@ void PrintConfigDef::init_fff_params()
|
|||
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
|
||||
def->enum_values.push_back("octoprint");
|
||||
def->enum_values.push_back("duet");
|
||||
def->enum_values.push_back("flashair");
|
||||
def->enum_labels.push_back("OctoPrint");
|
||||
def->enum_labels.push_back("Duet");
|
||||
def->enum_labels.push_back("FlashAir");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<PrintHostType>(htOctoPrint));
|
||||
|
||||
|
|
@ -1835,6 +1837,14 @@ void PrintConfigDef::init_fff_params()
|
|||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("wipe_tower_no_sparse_layers", coBool);
|
||||
def->label = L("No sparse layers (EXPERIMENTAL)");
|
||||
def->tooltip = L("If enabled, the wipe tower will not be printed on layers with no toolchanges. "
|
||||
"On layers with a toolchange, extruder will travel downward to print the wipe tower. "
|
||||
"User is responsible for ensuring there is no collision with the print.");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("support_material", coBool);
|
||||
def->label = L("Generate support material");
|
||||
def->category = L("Support material");
|
||||
|
|
@ -2438,6 +2448,34 @@ void PrintConfigDef::init_sla_params()
|
|||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.3));
|
||||
|
||||
def = this->add("bottle_volume", coFloat);
|
||||
def->label = L("Bottle volume");
|
||||
def->tooltip = L("Bottle volume");
|
||||
def->sidetext = L("ml");
|
||||
def->min = 50;
|
||||
def->set_default_value(new ConfigOptionFloat(1000.0));
|
||||
|
||||
def = this->add("bottle_weight", coFloat);
|
||||
def->label = L("Bottle weight");
|
||||
def->tooltip = L("Bottle weight");
|
||||
def->sidetext = L("kg");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("material_density", coFloat);
|
||||
def->label = L("Density");
|
||||
def->tooltip = L("Density");
|
||||
def->sidetext = L("g/ml");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("bottle_cost", coFloat);
|
||||
def->label = L("Cost");
|
||||
def->tooltip = L("Cost");
|
||||
def->sidetext = L("money/bottle");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(0.0));
|
||||
|
||||
def = this->add("faded_layers", coInt);
|
||||
def->label = L("Faded layers");
|
||||
def->tooltip = L("Number of the layers needed for the exposure time fade from initial exposure time to the exposure time");
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ enum GCodeFlavor : unsigned char {
|
|||
};
|
||||
|
||||
enum PrintHostType {
|
||||
htOctoPrint, htDuet
|
||||
htOctoPrint, htDuet, htFlashAir
|
||||
};
|
||||
|
||||
enum InfillPattern {
|
||||
|
|
@ -108,6 +108,7 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g
|
|||
if (keys_map.empty()) {
|
||||
keys_map["octoprint"] = htOctoPrint;
|
||||
keys_map["duet"] = htDuet;
|
||||
keys_map["flashair"] = htFlashAir;
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
|
@ -674,6 +675,7 @@ public:
|
|||
ConfigOptionStrings start_filament_gcode;
|
||||
ConfigOptionBool single_extruder_multi_material;
|
||||
ConfigOptionBool single_extruder_multi_material_priming;
|
||||
ConfigOptionBool wipe_tower_no_sparse_layers;
|
||||
ConfigOptionString toolchange_gcode;
|
||||
ConfigOptionFloat travel_speed;
|
||||
ConfigOptionBool use_firmware_retraction;
|
||||
|
|
@ -744,6 +746,7 @@ protected:
|
|||
OPT_PTR(retract_speed);
|
||||
OPT_PTR(single_extruder_multi_material);
|
||||
OPT_PTR(single_extruder_multi_material_priming);
|
||||
OPT_PTR(wipe_tower_no_sparse_layers);
|
||||
OPT_PTR(start_gcode);
|
||||
OPT_PTR(start_filament_gcode);
|
||||
OPT_PTR(toolchange_gcode);
|
||||
|
|
@ -1131,6 +1134,10 @@ class SLAMaterialConfig : public StaticPrintConfig
|
|||
STATIC_PRINT_CONFIG_CACHE(SLAMaterialConfig)
|
||||
public:
|
||||
ConfigOptionFloat initial_layer_height;
|
||||
ConfigOptionFloat bottle_cost;
|
||||
ConfigOptionFloat bottle_volume;
|
||||
ConfigOptionFloat bottle_weight;
|
||||
ConfigOptionFloat material_density;
|
||||
ConfigOptionFloat exposure_time;
|
||||
ConfigOptionFloat initial_exposure_time;
|
||||
ConfigOptionFloats material_correction;
|
||||
|
|
@ -1138,6 +1145,10 @@ protected:
|
|||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
{
|
||||
OPT_PTR(initial_layer_height);
|
||||
OPT_PTR(bottle_cost);
|
||||
OPT_PTR(bottle_volume);
|
||||
OPT_PTR(bottle_weight);
|
||||
OPT_PTR(material_density);
|
||||
OPT_PTR(exposure_time);
|
||||
OPT_PTR(initial_exposure_time);
|
||||
OPT_PTR(material_correction);
|
||||
|
|
|
|||
|
|
@ -1772,7 +1772,8 @@ end:
|
|||
float delta = float(scale_(m_config.xy_size_compensation.value));
|
||||
//FIXME only apply the compensation if no raft is enabled.
|
||||
float elephant_foot_compensation = 0.f;
|
||||
if (layer_id == 0)
|
||||
if (layer_id == 0 && m_config.raft_layers == 0)
|
||||
// Only enable Elephant foot compensation if printing directly on the print bed.
|
||||
elephant_foot_compensation = float(scale_(m_config.elefant_foot_compensation.value));
|
||||
if (layer->m_regions.size() == 1) {
|
||||
// Optimized version for a single region layer.
|
||||
|
|
@ -1819,11 +1820,12 @@ end:
|
|||
if (delta < 0.f || elephant_foot_compensation > 0.f) {
|
||||
// Apply the negative XY compensation.
|
||||
Polygons trimming;
|
||||
static const float eps = float(scale_(m_config.slice_closing_radius.value) * 1.5);
|
||||
if (elephant_foot_compensation > 0.f) {
|
||||
trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(float(EPSILON)), std::min(delta, 0.f) - float(EPSILON)),
|
||||
trimming = to_polygons(Slic3r::elephant_foot_compensation(offset_ex(layer->merged(eps), std::min(delta, 0.f) - eps),
|
||||
layer->m_regions.front()->flow(frExternalPerimeter), unscale<double>(elephant_foot_compensation)));
|
||||
} else
|
||||
trimming = offset(layer->merged(float(EPSILON)), delta - float(EPSILON));
|
||||
trimming = offset(layer->merged(float(SCALED_EPSILON)), delta - float(SCALED_EPSILON));
|
||||
for (size_t region_id = 0; region_id < layer->m_regions.size(); ++ region_id)
|
||||
layer->m_regions[region_id]->trim_surfaces(trimming);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,29 +32,40 @@ void RasterWriter::save(const std::string &fpath, const std::string &prjname)
|
|||
{
|
||||
try {
|
||||
Zipper zipper(fpath); // zipper with no compression
|
||||
|
||||
std::string project = prjname.empty()?
|
||||
boost::filesystem::path(fpath).stem().string() : prjname;
|
||||
|
||||
save(zipper, prjname);
|
||||
zipper.finalize();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void RasterWriter::save(Zipper &zipper, const std::string &prjname)
|
||||
{
|
||||
try {
|
||||
std::string project =
|
||||
prjname.empty() ?
|
||||
boost::filesystem::path(zipper.get_filename()).stem().string() :
|
||||
prjname;
|
||||
|
||||
zipper.add_entry("config.ini");
|
||||
|
||||
|
||||
zipper << createIniContent(project);
|
||||
|
||||
|
||||
for(unsigned i = 0; i < m_layers_rst.size(); i++)
|
||||
{
|
||||
if(m_layers_rst[i].rawbytes.size() > 0) {
|
||||
char lyrnum[6];
|
||||
std::sprintf(lyrnum, "%.5d", i);
|
||||
auto zfilename = project + lyrnum + ".png";
|
||||
|
||||
|
||||
// Add binary entry to the zipper
|
||||
zipper.add_entry(zfilename,
|
||||
m_layers_rst[i].rawbytes.data(),
|
||||
m_layers_rst[i].rawbytes.size());
|
||||
}
|
||||
}
|
||||
|
||||
zipper.finalize();
|
||||
} catch(std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||
// Rethrow the exception
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
|
|
@ -112,9 +113,10 @@ public:
|
|||
}
|
||||
|
||||
void save(const std::string &fpath, const std::string &prjname = "");
|
||||
void save(Zipper &zipper, const std::string &prjname = "");
|
||||
|
||||
void set_statistics(const PrintStatistics &statistics);
|
||||
|
||||
|
||||
void set_config(const DynamicPrintConfig &cfg);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1372,7 +1372,12 @@ void SLAPrint::process()
|
|||
m_print_statistics.fast_layers_count = fast_layers;
|
||||
m_print_statistics.slow_layers_count = slow_layers;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// second argument set to -3 to differentiate it from the same call made into slice_supports()
|
||||
m_report_status(*this, -3, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
#else
|
||||
m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
};
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
|
|
@ -1602,7 +1607,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
|
|||
"output_filename_format",
|
||||
"fast_tilt_time",
|
||||
"slow_tilt_time",
|
||||
"area_fill"
|
||||
"area_fill",
|
||||
"bottle_cost",
|
||||
"bottle_volume",
|
||||
"bottle_weight",
|
||||
"material_density"
|
||||
};
|
||||
|
||||
std::vector<SLAPrintStep> steps;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "SLA/SLARasterWriter.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include "Zipper.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -364,6 +365,12 @@ public:
|
|||
if(m_printer) m_printer->save(fpath, projectname);
|
||||
}
|
||||
|
||||
inline void export_raster(Zipper &zipper,
|
||||
const std::string& projectname = "")
|
||||
{
|
||||
if(m_printer) m_printer->save(zipper, projectname);
|
||||
}
|
||||
|
||||
const PrintObjects& objects() const { return m_objects; }
|
||||
|
||||
const SLAPrintConfig& print_config() const { return m_print_config; }
|
||||
|
|
|
|||
|
|
@ -237,11 +237,19 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
|
|||
|
||||
// Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops.
|
||||
assert(num_segments >= 2);
|
||||
#ifndef NDEBUG
|
||||
double distance_taken_last = 0.;
|
||||
#endif /* NDEBUG */
|
||||
for (int iter = int(num_segments) - 2;; -- iter) {
|
||||
assert(validate_graph_and_queue());
|
||||
// Take the first end point, for which the link points to the currently closest valid neighbor.
|
||||
EndPoint &end_point1 = *queue.top();
|
||||
assert(end_point1.edge_out != nullptr);
|
||||
#ifndef NDEBUG
|
||||
// Each edge added shall be longer than the previous one taken.
|
||||
assert(end_point1.distance_out > distance_taken_last - SCALED_EPSILON);
|
||||
distance_taken_last = end_point1.distance_out;
|
||||
#endif /* NDEBUG */
|
||||
assert(end_point1.edge_out != nullptr);
|
||||
// No point on the queue may be connected yet.
|
||||
assert(end_point1.chain_id == 0);
|
||||
// Take the closest end point to the first end point,
|
||||
|
|
@ -313,6 +321,10 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
|
|||
assert(next_idx < end_points.size());
|
||||
end_point1.edge_out = &end_points[next_idx];
|
||||
end_point1.distance_out = (end_points[next_idx].pos - end_point1.pos).squaredNorm();
|
||||
#ifndef NDEBUG
|
||||
// Each edge shall be longer than the last one removed from the queue.
|
||||
assert(end_point1.distance_out > distance_taken_last - SCALED_EPSILON);
|
||||
#endif /* NDEBUG */
|
||||
// Update position of this end point in the queue based on the distance calculated at the line above.
|
||||
queue.update(end_point1.heap_idx);
|
||||
//FIXME Remove the other end point from the KD tree.
|
||||
|
|
@ -460,18 +472,206 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
|||
return out;
|
||||
}
|
||||
|
||||
// Flip the sequences of polylines to lower the total length of connecting lines.
|
||||
// #define DEBUG_SVG_OUTPUT
|
||||
static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bool fixed_start)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
auto cost = [&polylines]() {
|
||||
double sum = 0.;
|
||||
for (size_t i = 1; i < polylines.size(); ++i)
|
||||
sum += (polylines[i].first_point() - polylines[i - 1].last_point()).cast<double>().norm();
|
||||
return sum;
|
||||
};
|
||||
double cost_initial = cost();
|
||||
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
{
|
||||
SVG svg(debug_out_path("improve_ordering_by_segment_flipping-initial-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(polylines);
|
||||
for (size_t i = 1; i < polylines.size(); ++ i)
|
||||
svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
|
||||
}
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
#endif /* NDEBUG */
|
||||
|
||||
struct Connection {
|
||||
Connection(size_t heap_idx = std::numeric_limits<size_t>::max(), bool flipped = false) : heap_idx(heap_idx), flipped(flipped) {}
|
||||
// Position of this object on MutablePriorityHeap.
|
||||
size_t heap_idx;
|
||||
// Is segment_idx flipped?
|
||||
bool flipped;
|
||||
|
||||
double squaredNorm(const Polylines &polylines, const std::vector<Connection> &connections) const
|
||||
{ return ((this + 1)->start_point(polylines, connections) - this->end_point(polylines, connections)).squaredNorm(); }
|
||||
double norm(const Polylines &polylines, const std::vector<Connection> &connections) const
|
||||
{ return sqrt(this->squaredNorm(polylines, connections)); }
|
||||
double squaredNorm(const Polylines &polylines, const std::vector<Connection> &connections, bool try_flip1, bool try_flip2) const
|
||||
{ return ((this + 1)->start_point(polylines, connections, try_flip2) - this->end_point(polylines, connections, try_flip1)).squaredNorm(); }
|
||||
double norm(const Polylines &polylines, const std::vector<Connection> &connections, bool try_flip1, bool try_flip2) const
|
||||
{ return sqrt(this->squaredNorm(polylines, connections, try_flip1, try_flip2)); }
|
||||
Vec2d start_point(const Polylines &polylines, const std::vector<Connection> &connections, bool flip = false) const
|
||||
{ const Polyline &pl = polylines[this - connections.data()]; return ((this->flipped == flip) ? pl.points.front() : pl.points.back()).cast<double>(); }
|
||||
Vec2d end_point(const Polylines &polylines, const std::vector<Connection> &connections, bool flip = false) const
|
||||
{ const Polyline &pl = polylines[this - connections.data()]; return ((this->flipped == flip) ? pl.points.back() : pl.points.front()).cast<double>(); }
|
||||
|
||||
bool in_queue() const { return this->heap_idx != std::numeric_limits<size_t>::max(); }
|
||||
void flip() { this->flipped = ! this->flipped; }
|
||||
};
|
||||
std::vector<Connection> connections(polylines.size());
|
||||
|
||||
#ifndef NDEBUG
|
||||
auto cost_flipped = [fixed_start, &polylines, &connections]() {
|
||||
assert(! fixed_start || ! connections.front().flipped);
|
||||
double sum = 0.;
|
||||
for (size_t i = 1; i < polylines.size(); ++ i)
|
||||
sum += connections[i - 1].norm(polylines, connections);
|
||||
return sum;
|
||||
};
|
||||
double cost_prev = cost_flipped();
|
||||
assert(std::abs(cost_initial - cost_prev) < SCALED_EPSILON);
|
||||
|
||||
auto print_statistics = [&polylines, &connections]() {
|
||||
#if 0
|
||||
for (size_t i = 1; i < polylines.size(); ++ i)
|
||||
printf("Connecting %d with %d: Current length %lf flip(%d, %d), left flipped: %lf, right flipped: %lf, both flipped: %lf, \n",
|
||||
int(i - 1), int(i),
|
||||
unscale<double>(connections[i - 1].norm(polylines, connections)),
|
||||
int(connections[i - 1].flipped), int(connections[i].flipped),
|
||||
unscale<double>(connections[i - 1].norm(polylines, connections, true, false)),
|
||||
unscale<double>(connections[i - 1].norm(polylines, connections, false, true)),
|
||||
unscale<double>(connections[i - 1].norm(polylines, connections, true, true)));
|
||||
#endif
|
||||
};
|
||||
print_statistics();
|
||||
#endif /* NDEBUG */
|
||||
|
||||
// Initialize a MutablePriorityHeap of connections between polylines.
|
||||
auto queue = make_mutable_priority_queue<Connection*>(
|
||||
[](Connection *connection, size_t idx){ connection->heap_idx = idx; },
|
||||
// Sort by decreasing connection distance.
|
||||
[&polylines, &connections](Connection *l, Connection *r){ return l->squaredNorm(polylines, connections) > r->squaredNorm(polylines, connections); });
|
||||
queue.reserve(polylines.size() - 1);
|
||||
for (size_t i = 0; i + 1 < polylines.size(); ++ i)
|
||||
queue.push(&connections[i]);
|
||||
|
||||
static constexpr size_t itercnt = 100;
|
||||
size_t iter = 0;
|
||||
for (; ! queue.empty() && iter < itercnt; ++ iter) {
|
||||
Connection &connection = *queue.top();
|
||||
queue.pop();
|
||||
connection.heap_idx = std::numeric_limits<size_t>::max();
|
||||
size_t idx_first = &connection - connections.data();
|
||||
// Try to flip segments starting with idx_first + 1 to the end.
|
||||
// Calculate the last segment to be flipped to improve the total path length.
|
||||
double length_current = connection.norm(polylines, connections);
|
||||
double length_flipped = connection.norm(polylines, connections, false, true);
|
||||
int best_idx_forward = int(idx_first);
|
||||
double best_improvement_forward = 0.;
|
||||
for (size_t i = idx_first + 1; i + 1 < connections.size(); ++ i) {
|
||||
length_current += connections[i].norm(polylines, connections);
|
||||
double this_improvement = length_current - length_flipped - connections[i].norm(polylines, connections, true, false);
|
||||
length_flipped += connections[i].norm(polylines, connections, true, true);
|
||||
if (this_improvement > best_improvement_forward) {
|
||||
best_improvement_forward = this_improvement;
|
||||
best_idx_forward = int(i);
|
||||
}
|
||||
// if (length_flipped > 1.5 * length_current)
|
||||
// break;
|
||||
}
|
||||
if (length_current - length_flipped > best_improvement_forward)
|
||||
// Best improvement by flipping up to the end.
|
||||
best_idx_forward = int(connections.size()) - 1;
|
||||
// Try to flip segments starting with idx_first - 1 to the start.
|
||||
// Calculate the last segment to be flipped to improve the total path length.
|
||||
length_current = connection.norm(polylines, connections);
|
||||
length_flipped = connection.norm(polylines, connections, true, false);
|
||||
int best_idx_backwards = int(idx_first);
|
||||
double best_improvement_backwards = 0.;
|
||||
for (int i = int(idx_first) - 1; i >= 0; -- i) {
|
||||
length_current += connections[i].norm(polylines, connections);
|
||||
double this_improvement = length_current - length_flipped - connections[i].norm(polylines, connections, false, true);
|
||||
length_flipped += connections[i].norm(polylines, connections, true, true);
|
||||
if (this_improvement > best_improvement_backwards) {
|
||||
best_improvement_backwards = this_improvement;
|
||||
best_idx_backwards = int(i);
|
||||
}
|
||||
// if (length_flipped > 1.5 * length_current)
|
||||
// break;
|
||||
}
|
||||
if (! fixed_start && length_current - length_flipped > best_improvement_backwards)
|
||||
// Best improvement by flipping up to the start including the first polyline.
|
||||
best_idx_backwards = -1;
|
||||
int update_begin = int(idx_first);
|
||||
int update_end = best_idx_forward;
|
||||
if (best_improvement_backwards > 0. && best_improvement_backwards > best_improvement_forward) {
|
||||
// Flip the sequence of polylines from idx_first to best_improvement_forward + 1.
|
||||
update_begin = best_idx_backwards;
|
||||
update_end = int(idx_first);
|
||||
}
|
||||
assert(update_begin <= update_end);
|
||||
if (update_begin == update_end)
|
||||
continue;
|
||||
for (int i = update_begin + 1; i <= update_end; ++ i)
|
||||
connections[i].flip();
|
||||
|
||||
#ifndef NDEBUG
|
||||
double cost = cost_flipped();
|
||||
assert(cost < cost_prev);
|
||||
cost_prev = cost;
|
||||
print_statistics();
|
||||
#endif /* NDEBUG */
|
||||
|
||||
update_end = std::min(update_end + 1, int(connections.size()) - 1);
|
||||
for (int i = std::max(0, update_begin); i < update_end; ++ i) {
|
||||
Connection &c = connections[i];
|
||||
if (c.in_queue())
|
||||
queue.update(c.heap_idx);
|
||||
else
|
||||
queue.push(&c);
|
||||
}
|
||||
}
|
||||
|
||||
// Flip the segments based on the flip flag.
|
||||
for (Polyline &pl : polylines)
|
||||
if (connections[&pl - polylines.data()].flipped)
|
||||
pl.reverse();
|
||||
|
||||
#ifndef NDEBUG
|
||||
double cost_final = cost();
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
{
|
||||
SVG svg(debug_out_path("improve_ordering_by_segment_flipping-final-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(polylines);
|
||||
for (size_t i = 1; i < polylines.size(); ++ i)
|
||||
svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
|
||||
}
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
#endif /* NDEBUG */
|
||||
|
||||
assert(cost_final <= cost_prev);
|
||||
assert(cost_final <= cost_initial);
|
||||
}
|
||||
|
||||
Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
|
||||
Polylines out;
|
||||
out.reserve(polylines.size());
|
||||
for (auto &segment_and_reversal : ordered) {
|
||||
out.emplace_back(std::move(polylines[segment_and_reversal.first]));
|
||||
if (segment_and_reversal.second)
|
||||
out.back().reverse();
|
||||
if (! polylines.empty()) {
|
||||
auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
|
||||
out.reserve(polylines.size());
|
||||
for (auto &segment_and_reversal : ordered) {
|
||||
out.emplace_back(std::move(polylines[segment_and_reversal.first]));
|
||||
if (segment_and_reversal.second)
|
||||
out.back().reverse();
|
||||
}
|
||||
if (out.size() > 1)
|
||||
improve_ordering_by_segment_flipping(out, start_near != nullptr);
|
||||
}
|
||||
return out;
|
||||
return out;
|
||||
}
|
||||
|
||||
template<class T> static inline T chain_path_items(const Points &points, const T &items)
|
||||
|
|
|
|||
|
|
@ -32,4 +32,14 @@
|
|||
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1)
|
||||
|
||||
|
||||
//====================
|
||||
// 2.2.0.alpha1 techs
|
||||
//====================
|
||||
#define ENABLE_2_2_0_ALPHA1 1
|
||||
|
||||
// Enable thumbnail generator
|
||||
#define ENABLE_THUMBNAIL_GENERATOR (1 && ENABLE_2_2_0_ALPHA1)
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG (0 && ENABLE_THUMBNAIL_GENERATOR)
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE (1 && ENABLE_THUMBNAIL_GENERATOR)
|
||||
|
||||
#endif // _technologies_h_
|
||||
|
|
|
|||
|
|
@ -165,6 +165,65 @@ template<class T> size_t next_highest_power_of_2(T v,
|
|||
return next_highest_power_of_2(uint32_t(v));
|
||||
}
|
||||
|
||||
template<typename INDEX_TYPE>
|
||||
inline INDEX_TYPE prev_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
|
||||
{
|
||||
if (idx == 0)
|
||||
idx = count;
|
||||
return -- idx;
|
||||
}
|
||||
|
||||
template<typename INDEX_TYPE>
|
||||
inline INDEX_TYPE next_idx_modulo(INDEX_TYPE idx, const INDEX_TYPE count)
|
||||
{
|
||||
if (++ idx == count)
|
||||
idx = 0;
|
||||
return idx;
|
||||
}
|
||||
|
||||
template<typename CONTAINER_TYPE>
|
||||
inline typename CONTAINER_TYPE::size_type prev_idx_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container)
|
||||
{
|
||||
return prev_idx_modulo(idx, container.size());
|
||||
}
|
||||
|
||||
template<typename CONTAINER_TYPE>
|
||||
inline typename CONTAINER_TYPE::size_type next_idx_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container)
|
||||
{
|
||||
return next_idx_modulo(idx, container.size());
|
||||
}
|
||||
|
||||
template<typename CONTAINER_TYPE>
|
||||
inline typename const CONTAINER_TYPE::value_type& prev_value_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container)
|
||||
{
|
||||
return container[prev_idx_modulo(idx, container.size())];
|
||||
}
|
||||
|
||||
template<typename CONTAINER_TYPE>
|
||||
inline typename CONTAINER_TYPE::value_type& prev_value_modulo(typename CONTAINER_TYPE::size_type idx, CONTAINER_TYPE &container)
|
||||
{
|
||||
return container[prev_idx_modulo(idx, container.size())];
|
||||
}
|
||||
|
||||
template<typename CONTAINER_TYPE>
|
||||
inline typename const CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER_TYPE::size_type idx, const CONTAINER_TYPE &container)
|
||||
{
|
||||
return container[next_idx_modulo(idx, container.size())];
|
||||
}
|
||||
|
||||
template<typename CONTAINER_TYPE>
|
||||
inline typename CONTAINER_TYPE::value_type& next_value_modulo(typename CONTAINER_TYPE::size_type idx, CONTAINER_TYPE &container)
|
||||
{
|
||||
return container[next_idx_modulo(idx, container.size())];
|
||||
}
|
||||
|
||||
template<class T, class U = T>
|
||||
inline T exchange(T& obj, U&& new_value)
|
||||
{
|
||||
T old_value = std::move(obj);
|
||||
obj = std::forward<U>(new_value);
|
||||
return old_value;
|
||||
}
|
||||
|
||||
extern std::string xml_escape(std::string text);
|
||||
|
||||
|
|
|
|||
|
|
@ -217,4 +217,9 @@ void Zipper::finalize()
|
|||
m_impl->blow_up();
|
||||
}
|
||||
|
||||
const std::string &Zipper::get_filename() const
|
||||
{
|
||||
return m_impl->m_zipname;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ public:
|
|||
void finish_entry();
|
||||
|
||||
void finalize();
|
||||
|
||||
const std::string & get_filename() const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -116,5 +116,7 @@
|
|||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/OctoPrint.hpp
|
||||
Utils/Duet.cpp
|
||||
Utils/Duet.hpp
|
||||
Utils/FlashAir.cpp
|
||||
Utils/FlashAir.hpp
|
||||
Utils/PrintHost.cpp
|
||||
Utils/PrintHost.hpp
|
||||
Utils/Bonjour.cpp
|
||||
|
|
@ -156,6 +158,7 @@ set(SLIC3R_GUI_SOURCES
|
|||
Utils/UndoRedo.hpp
|
||||
Utils/HexFile.cpp
|
||||
Utils/HexFile.hpp
|
||||
Utils/Thread.hpp
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
|
|||
std::string cst_texture(custom_texture);
|
||||
if (!cst_texture.empty())
|
||||
{
|
||||
std::replace(cst_texture.begin(), cst_texture.end(), '\\', '/');
|
||||
if ((!boost::algorithm::iends_with(custom_texture, ".png") && !boost::algorithm::iends_with(custom_texture, ".svg")) || !boost::filesystem::exists(custom_texture))
|
||||
cst_texture = "";
|
||||
}
|
||||
|
|
@ -212,6 +213,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
|
|||
std::string cst_model(custom_model);
|
||||
if (!cst_model.empty())
|
||||
{
|
||||
std::replace(cst_model.begin(), cst_model.end(), '\\', '/');
|
||||
if (!boost::algorithm::iends_with(custom_model, ".stl") || !boost::filesystem::exists(custom_model))
|
||||
cst_model = "";
|
||||
}
|
||||
|
|
@ -270,27 +272,13 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const
|
|||
|
||||
switch (m_type)
|
||||
{
|
||||
case MK2:
|
||||
{
|
||||
render_prusa(canvas, "mk2", theta > 90.0f);
|
||||
break;
|
||||
}
|
||||
case MK3:
|
||||
{
|
||||
render_prusa(canvas, "mk3", theta > 90.0f);
|
||||
break;
|
||||
}
|
||||
case SL1:
|
||||
{
|
||||
render_prusa(canvas, "sl1", theta > 90.0f);
|
||||
break;
|
||||
}
|
||||
case MK2: { render_prusa(canvas, "mk2", theta > 90.0f); break; }
|
||||
case MK3: { render_prusa(canvas, "mk3", theta > 90.0f); break; }
|
||||
case SL1: { render_prusa(canvas, "sl1", theta > 90.0f); break; }
|
||||
case MINI: { render_prusa(canvas, "mini", theta > 90.0f); break; }
|
||||
case ENDER3: { render_prusa(canvas, "ender3", theta > 90.0f); break; }
|
||||
default:
|
||||
case Custom:
|
||||
{
|
||||
render_custom(canvas, theta > 90.0f);
|
||||
break;
|
||||
}
|
||||
case Custom: { render_custom(canvas, theta > 90.0f); break; }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,22 +350,38 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const
|
|||
{
|
||||
if (curr->config.has("bed_shape"))
|
||||
{
|
||||
if ((curr->vendor != nullptr) && (curr->vendor->name == "Prusa Research") && (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values))
|
||||
if (curr->vendor != nullptr)
|
||||
{
|
||||
if (boost::contains(curr->name, "SL1"))
|
||||
if ((curr->vendor->name == "Prusa Research") && (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values))
|
||||
{
|
||||
type = SL1;
|
||||
break;
|
||||
if (boost::contains(curr->name, "SL1"))
|
||||
{
|
||||
type = SL1;
|
||||
break;
|
||||
}
|
||||
else if (boost::contains(curr->name, "MK3") || boost::contains(curr->name, "MK2.5"))
|
||||
{
|
||||
type = MK3;
|
||||
break;
|
||||
}
|
||||
else if (boost::contains(curr->name, "MK2"))
|
||||
{
|
||||
type = MK2;
|
||||
break;
|
||||
}
|
||||
else if (boost::contains(curr->name, "MINI"))
|
||||
{
|
||||
type = MINI;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (boost::contains(curr->name, "MK3") || boost::contains(curr->name, "MK2.5"))
|
||||
else if ((curr->vendor->name == "Creality") && (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values))
|
||||
{
|
||||
type = MK3;
|
||||
break;
|
||||
}
|
||||
else if (boost::contains(curr->name, "MK2"))
|
||||
{
|
||||
type = MK2;
|
||||
break;
|
||||
if (boost::contains(curr->name, "ENDER-3"))
|
||||
{
|
||||
type = ENDER3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ public:
|
|||
MK2,
|
||||
MK3,
|
||||
SL1,
|
||||
MINI,
|
||||
ENDER3,
|
||||
Custom,
|
||||
Num_Types
|
||||
};
|
||||
|
|
|
|||
|
|
@ -707,24 +707,24 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
|
|||
print_volume.min(2) = -1e10;
|
||||
|
||||
ModelInstance::EPrintVolumeState state = ModelInstance::PVS_Inside;
|
||||
bool all_contained = true;
|
||||
|
||||
bool contained_min_one = false;
|
||||
|
||||
for (GLVolume* volume : this->volumes)
|
||||
{
|
||||
if ((volume == nullptr) || !volume->printable || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled))
|
||||
if ((volume == nullptr) || volume->is_modifier || (volume->is_wipe_tower && !volume->shader_outside_printer_detection_enabled) || ((volume->composite_id.volume_id < 0) && !volume->shader_outside_printer_detection_enabled))
|
||||
continue;
|
||||
|
||||
const BoundingBoxf3& bb = volume->transformed_convex_hull_bounding_box();
|
||||
bool contained = print_volume.contains(bb);
|
||||
all_contained &= contained;
|
||||
|
||||
volume->is_outside = !contained;
|
||||
if (!volume->printable)
|
||||
continue;
|
||||
|
||||
if (contained)
|
||||
contained_min_one = true;
|
||||
|
||||
volume->is_outside = !contained;
|
||||
|
||||
if ((state == ModelInstance::PVS_Inside) && volume->is_outside)
|
||||
state = ModelInstance::PVS_Fully_Outside;
|
||||
|
||||
|
|
@ -735,7 +735,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M
|
|||
if (out_state != nullptr)
|
||||
*out_state = state;
|
||||
|
||||
return /*all_contained*/ contained_min_one; // #ys_FIXME_delete_after_testing
|
||||
return contained_min_one;
|
||||
}
|
||||
|
||||
void GLVolumeCollection::reset_outside_state()
|
||||
|
|
|
|||
|
|
@ -10,12 +10,19 @@
|
|||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include <miniz.h>
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <cassert>
|
||||
|
|
@ -84,7 +91,11 @@ void BackgroundSlicingProcess::process_fff()
|
|||
assert(m_print == m_fff_print);
|
||||
m_print->process();
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_data);
|
||||
#else
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
if (m_fff_print->model().custom_gcode_per_height != GUI::wxGetApp().model().custom_gcode_per_height) {
|
||||
GUI::wxGetApp().model().custom_gcode_per_height = m_fff_print->model().custom_gcode_per_height;
|
||||
|
|
@ -112,14 +123,43 @@ void BackgroundSlicingProcess::process_fff()
|
|||
}
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
static void write_thumbnail(Zipper& zipper, const ThumbnailData& data)
|
||||
{
|
||||
size_t png_size = 0;
|
||||
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
|
||||
if (png_data != nullptr)
|
||||
{
|
||||
zipper.add_entry("thumbnail/thumbnail" + std::to_string(data.width) + "x" + std::to_string(data.height) + ".png", (const std::uint8_t*)png_data, png_size);
|
||||
mz_free(png_data);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void BackgroundSlicingProcess::process_sla()
|
||||
{
|
||||
assert(m_print == m_sla_print);
|
||||
m_print->process();
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
m_sla_print->export_raster(export_path);
|
||||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
|
||||
Zipper zipper(export_path);
|
||||
m_sla_print->export_raster(zipper);
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (m_thumbnail_data != nullptr)
|
||||
{
|
||||
for (const ThumbnailData& data : *m_thumbnail_data)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
zipper.finalize();
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
|
||||
} else if (! m_upload_job.empty()) {
|
||||
prepare_upload();
|
||||
|
|
@ -230,11 +270,7 @@ bool BackgroundSlicingProcess::start()
|
|||
if (m_state == STATE_INITIAL) {
|
||||
// The worker thread is not running yet. Start it.
|
||||
assert(! m_thread.joinable());
|
||||
boost::thread::attributes attrs;
|
||||
// Duplicating the stack allocation size of Thread Building Block worker threads of the thread pool:
|
||||
// allocate 4MB on a 64bit system, allocate 2MB on a 32bit system by default.
|
||||
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
|
||||
m_thread = boost::thread(attrs, [this]{this->thread_proc_safe();});
|
||||
m_thread = create_thread([this]{this->thread_proc_safe();});
|
||||
// Wait until the worker thread is ready to execute the background processing task.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
|
||||
}
|
||||
|
|
@ -427,13 +463,26 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
|
||||
}
|
||||
run_post_process_scripts(source_path.string(), m_fff_print->config());
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
} else {
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
m_sla_print->export_raster(source_path.string(), m_upload_job.upload_data.upload_path.string());
|
||||
}
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str());
|
||||
Zipper zipper{source_path.string()};
|
||||
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (m_thumbnail_data != nullptr)
|
||||
{
|
||||
for (const ThumbnailData& data : *m_thumbnail_data)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
zipper.finalize();
|
||||
}
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"))) % m_upload_job.printhost->get_host()).str());
|
||||
|
||||
m_upload_job.upload_data.source_path = std::move(source_path);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,17 +6,20 @@
|
|||
#include <mutex>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
#include "slic3r/Utils/Thread.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
class Model;
|
||||
class SLAPrint;
|
||||
|
||||
|
|
@ -49,6 +52,10 @@ public:
|
|||
void set_fff_print(Print *print) { m_fff_print = print; }
|
||||
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
|
||||
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void set_thumbnail_data(const std::vector<ThumbnailData>* data) { m_thumbnail_data = data; }
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
|
||||
// and the background processing will transition into G-code export.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
|
|
@ -156,6 +163,10 @@ private:
|
|||
SLAPrint *m_sla_print = nullptr;
|
||||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodePreviewData *m_gcode_preview_data = nullptr;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// Data structures, used to write thumbnails into gcode.
|
||||
const std::vector<ThumbnailData>* m_thumbnail_data = nullptr;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
|
||||
std::string m_temp_output_path;
|
||||
// Output path provided by the user. The output path may be set even if the slicing is running,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include "boost/nowide/iostream.hpp"
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -60,7 +61,9 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf
|
|||
{
|
||||
m_shape = default_pt.values;
|
||||
m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value;
|
||||
std::replace(m_custom_texture.begin(), m_custom_texture.end(), '\\', '/');
|
||||
m_custom_model = custom_model.value.empty() ? NONE : custom_model.value;
|
||||
std::replace(m_custom_model.begin(), m_custom_model.end(), '\\', '/');
|
||||
|
||||
auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape")));
|
||||
sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font());
|
||||
|
|
@ -212,7 +215,18 @@ wxPanel* BedShapePanel::init_texture_panel()
|
|||
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
|
||||
if (lbl != nullptr)
|
||||
{
|
||||
wxString tooltip_text = (m_custom_texture == NONE) ? "" : _(m_custom_texture);
|
||||
bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture);
|
||||
lbl->SetForegroundColour(exists ? wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) : wxColor(*wxRED));
|
||||
|
||||
wxString tooltip_text = "";
|
||||
if (m_custom_texture != NONE)
|
||||
{
|
||||
if (!exists)
|
||||
tooltip_text += _(L("Not found: "));
|
||||
|
||||
tooltip_text += _(m_custom_texture);
|
||||
}
|
||||
|
||||
wxToolTip* tooltip = lbl->GetToolTip();
|
||||
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
|
||||
lbl->SetToolTip(tooltip_text);
|
||||
|
|
@ -280,7 +294,18 @@ wxPanel* BedShapePanel::init_model_panel()
|
|||
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
|
||||
if (lbl != nullptr)
|
||||
{
|
||||
wxString tooltip_text = (m_custom_model == NONE) ? "" : _(m_custom_model);
|
||||
bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model);
|
||||
lbl->SetForegroundColour(exists ? wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT) : wxColor(*wxRED));
|
||||
|
||||
wxString tooltip_text = "";
|
||||
if (m_custom_model != NONE)
|
||||
{
|
||||
if (!exists)
|
||||
tooltip_text += _(L("Not found: "));
|
||||
|
||||
tooltip_text += _(m_custom_model);
|
||||
}
|
||||
|
||||
wxToolTip* tooltip = lbl->GetToolTip();
|
||||
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
|
||||
lbl->SetToolTip(tooltip_text);
|
||||
|
|
@ -521,6 +546,8 @@ void BedShapePanel::load_texture()
|
|||
return;
|
||||
}
|
||||
|
||||
std::replace(file_name.begin(), file_name.end(), '\\', '/');
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
m_custom_texture = file_name;
|
||||
|
|
@ -544,6 +571,8 @@ void BedShapePanel::load_model()
|
|||
return;
|
||||
}
|
||||
|
||||
std::replace(file_name.begin(), file_name.end(), '\\', '/');
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
m_custom_model = file_name;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "Camera.hpp"
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "3DScene.hpp"
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "GUI_App.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
|
||||
|
|
@ -22,6 +24,10 @@ namespace Slic3r {
|
|||
namespace GUI {
|
||||
|
||||
const double Camera::DefaultDistance = 1000.0;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
const double Camera::DefaultZoomToBoxMarginFactor = 1.025;
|
||||
const double Camera::DefaultZoomToVolumesMarginFactor = 1.025;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
double Camera::FrustrumMinZRange = 50.0;
|
||||
double Camera::FrustrumMinNearZ = 100.0;
|
||||
double Camera::FrustrumZMargin = 10.0;
|
||||
|
|
@ -266,10 +272,18 @@ void Camera::apply_projection(const BoundingBoxf3& box) const
|
|||
glsafe(::glMatrixMode(GL_MODELVIEW));
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor)
|
||||
#else
|
||||
void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
// Calculate the zoom factor needed to adjust the view around the given box.
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h, margin_factor);
|
||||
#else
|
||||
double zoom = calc_zoom_to_bounding_box_factor(box, canvas_w, canvas_h);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
if (zoom > 0.0)
|
||||
{
|
||||
m_zoom = zoom;
|
||||
|
|
@ -278,6 +292,20 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h)
|
|||
}
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor)
|
||||
{
|
||||
Vec3d center;
|
||||
double zoom = calc_zoom_to_volumes_factor(volumes, canvas_w, canvas_h, center, margin_factor);
|
||||
if (zoom > 0.0)
|
||||
{
|
||||
m_zoom = zoom;
|
||||
// center view around the calculated center
|
||||
m_target = center;
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#if ENABLE_CAMERA_STATISTICS
|
||||
void Camera::debug_render() const
|
||||
{
|
||||
|
|
@ -372,7 +400,11 @@ std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBo
|
|||
return ret;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor) const
|
||||
#else
|
||||
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
double max_bb_size = box.max_size();
|
||||
if (max_bb_size == 0.0)
|
||||
|
|
@ -405,13 +437,15 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
|
|||
double max_x = 0.0;
|
||||
double max_y = 0.0;
|
||||
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR
|
||||
// margin factor to give some empty space around the box
|
||||
double margin_factor = 1.25;
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
for (const Vec3d& v : vertices)
|
||||
{
|
||||
// project vertex on the plane perpendicular to camera forward axis
|
||||
Vec3d pos(v(0) - bb_center(0), v(1) - bb_center(1), v(2) - bb_center(2));
|
||||
Vec3d pos = v - bb_center;
|
||||
Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
|
||||
|
||||
// calculates vertex coordinate along camera xy axes
|
||||
|
|
@ -431,6 +465,72 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int ca
|
|||
return std::min((double)canvas_w / (2.0 * max_x), (double)canvas_h / (2.0 * max_y));
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor) const
|
||||
{
|
||||
if (volumes.empty())
|
||||
return -1.0;
|
||||
|
||||
// project the volumes vertices on a plane perpendicular to the camera forward axis
|
||||
// then calculates the vertices coordinate on this plane along the camera xy axes
|
||||
|
||||
// ensure that the view matrix is updated
|
||||
apply_view_matrix();
|
||||
|
||||
Vec3d right = get_dir_right();
|
||||
Vec3d up = get_dir_up();
|
||||
Vec3d forward = get_dir_forward();
|
||||
|
||||
BoundingBoxf3 box;
|
||||
for (const GLVolume* volume : volumes)
|
||||
{
|
||||
box.merge(volume->transformed_bounding_box());
|
||||
}
|
||||
center = box.center();
|
||||
|
||||
double min_x = DBL_MAX;
|
||||
double min_y = DBL_MAX;
|
||||
double max_x = -DBL_MAX;
|
||||
double max_y = -DBL_MAX;
|
||||
|
||||
for (const GLVolume* volume : volumes)
|
||||
{
|
||||
const Transform3d& transform = volume->world_matrix();
|
||||
const TriangleMesh* hull = volume->convex_hull();
|
||||
if (hull == nullptr)
|
||||
continue;
|
||||
|
||||
for (const Vec3f& vertex : hull->its.vertices)
|
||||
{
|
||||
Vec3d v = transform * vertex.cast<double>();
|
||||
|
||||
// project vertex on the plane perpendicular to camera forward axis
|
||||
Vec3d pos = v - center;
|
||||
Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
|
||||
|
||||
// calculates vertex coordinate along camera xy axes
|
||||
double x_on_plane = proj_on_plane.dot(right);
|
||||
double y_on_plane = proj_on_plane.dot(up);
|
||||
|
||||
min_x = std::min(min_x, x_on_plane);
|
||||
min_y = std::min(min_y, y_on_plane);
|
||||
max_x = std::max(max_x, x_on_plane);
|
||||
max_y = std::max(max_y, y_on_plane);
|
||||
}
|
||||
}
|
||||
|
||||
center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up;
|
||||
|
||||
double dx = margin_factor * (max_x - min_x);
|
||||
double dy = margin_factor * (max_y - min_y);
|
||||
|
||||
if ((dx == 0.0) || (dy == 0.0))
|
||||
return -1.0f;
|
||||
|
||||
return std::min((double)canvas_w / dx, (double)canvas_h / dy);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void Camera::set_distance(double distance) const
|
||||
{
|
||||
m_distance = distance;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
#define slic3r_Camera_hpp_
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "3DScene.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include <array>
|
||||
|
||||
namespace Slic3r {
|
||||
|
|
@ -10,6 +13,10 @@ namespace GUI {
|
|||
struct Camera
|
||||
{
|
||||
static const double DefaultDistance;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
static const double DefaultZoomToBoxMarginFactor;
|
||||
static const double DefaultZoomToVolumesMarginFactor;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
static double FrustrumMinZRange;
|
||||
static double FrustrumMinNearZ;
|
||||
static double FrustrumZMargin;
|
||||
|
|
@ -90,7 +97,12 @@ public:
|
|||
void apply_view_matrix() const;
|
||||
void apply_projection(const BoundingBoxf3& box) const;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor);
|
||||
void zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToVolumesMarginFactor);
|
||||
#else
|
||||
void zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#if ENABLE_CAMERA_STATISTICS
|
||||
void debug_render() const;
|
||||
|
|
@ -100,7 +112,12 @@ private:
|
|||
// returns tight values for nearZ and farZ plane around the given bounding box
|
||||
// the camera MUST be outside of the bounding box in eye coordinate of the given box
|
||||
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h, double margin_factor = DefaultZoomToBoxMarginFactor) const;
|
||||
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, int canvas_w, int canvas_h, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
|
||||
#else
|
||||
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
void set_distance(double distance) const;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -166,6 +166,8 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
|||
int max_row_width = 0;
|
||||
int current_row_width = 0;
|
||||
|
||||
bool is_variants = false;
|
||||
|
||||
for (const auto &model : models) {
|
||||
if (! filter(model)) { continue; }
|
||||
|
||||
|
|
@ -220,6 +222,7 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
|||
auto *alt_label = new wxStaticText(variants_panel, wxID_ANY, _(L("Alternate nozzles:")));
|
||||
alt_label->SetFont(font_alt_nozzle);
|
||||
variants_sizer->Add(alt_label, 0, wxBOTTOM, 3);
|
||||
is_variants = true;
|
||||
}
|
||||
|
||||
auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
|
||||
|
|
@ -280,10 +283,10 @@ PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxSt
|
|||
}
|
||||
title_sizer->AddStretchSpacer();
|
||||
|
||||
if (titles.size() > 1) {
|
||||
if (/*titles.size() > 1*/is_variants) {
|
||||
// It only makes sense to add the All / None buttons if there's multiple printers
|
||||
|
||||
auto *sel_all_std = new wxButton(this, wxID_ANY, _(L("All standard")));
|
||||
auto *sel_all_std = new wxButton(this, wxID_ANY, titles.size() > 1 ? _(L("All standard")) : _(L("Standard")));
|
||||
auto *sel_all = new wxButton(this, wxID_ANY, _(L("All")));
|
||||
auto *sel_none = new wxButton(this, wxID_ANY, _(L("None")));
|
||||
sel_all_std->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true, false); });
|
||||
|
|
|
|||
|
|
@ -43,8 +43,10 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
|||
|
||||
auto editor_sz = wxSize(4*em, wxDefaultCoord);
|
||||
|
||||
wxRadioButton* rb_by_layers = new wxRadioButton(this, wxID_ANY, "");
|
||||
rb_by_layers->Bind(wxEVT_RADIOBUTTON, [this](wxEvent&) { m_sequence.is_mm_intervals = false; });
|
||||
auto ID_RADIO_BUTTON = wxWindow::NewControlId(1);
|
||||
|
||||
wxRadioButton* rb_by_layers = new wxRadioButton(this, ID_RADIO_BUTTON, "", wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
|
||||
rb_by_layers->Bind(wxEVT_RADIOBUTTON, [this](wxCommandEvent& event) { m_sequence.is_mm_intervals = false; });
|
||||
|
||||
wxStaticText* st_by_layers = new wxStaticText(this, wxID_ANY, _(L("layers")));
|
||||
m_interval_by_layers = new wxTextCtrl(this, wxID_ANY,
|
||||
|
|
@ -58,7 +60,16 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
|||
return;
|
||||
}
|
||||
|
||||
m_sequence.interval_by_layers = wxAtoi(str);
|
||||
int val = wxAtoi(str);
|
||||
if (val < 1) {
|
||||
m_interval_by_layers->SetValue("1");
|
||||
val = 1;
|
||||
}
|
||||
|
||||
if (m_sequence.interval_by_layers == val)
|
||||
return;
|
||||
|
||||
m_sequence.interval_by_layers = val;
|
||||
|
||||
m_sequence.is_mm_intervals = false;
|
||||
rb_by_layers->SetValue(true);
|
||||
|
|
@ -68,15 +79,16 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
|||
m_intervals_grid_sizer->Add(m_interval_by_layers,0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(st_by_layers,0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
wxRadioButton* rb_by_mm = new wxRadioButton(this, wxID_ANY, "");
|
||||
wxRadioButton* rb_by_mm = new wxRadioButton(this, ID_RADIO_BUTTON, "");
|
||||
rb_by_mm->Bind(wxEVT_RADIOBUTTON, [this](wxEvent&) { m_sequence.is_mm_intervals = true; });
|
||||
rb_by_mm->SetValue(m_sequence.is_mm_intervals);
|
||||
|
||||
wxStaticText* st_by_mm = new wxStaticText(this, wxID_ANY, _(L("mm")));
|
||||
m_interval_by_mm = new wxTextCtrl(this, wxID_ANY,
|
||||
double_to_string(sequence.interval_by_mm),
|
||||
wxDefaultPosition, editor_sz);
|
||||
m_interval_by_mm->Bind(wxEVT_TEXT, [this, rb_by_mm](wxEvent&)
|
||||
wxDefaultPosition, editor_sz, wxTE_PROCESS_ENTER);
|
||||
|
||||
auto change_value = [this]()
|
||||
{
|
||||
wxString str = m_interval_by_mm->GetValue();
|
||||
if (str.IsEmpty()) {
|
||||
|
|
@ -86,15 +98,32 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
|||
|
||||
str.Replace(",", ".", false);
|
||||
double val;
|
||||
if (str == "." || !str.ToCDouble(&val))
|
||||
val = 0.0;
|
||||
if (str == "." || !str.ToCDouble(&val) || val <= 0.0)
|
||||
val = 3.0; // default value
|
||||
|
||||
if (fabs(m_sequence.interval_by_layers - val) < 0.001)
|
||||
return;
|
||||
|
||||
m_sequence.interval_by_mm = val;
|
||||
};
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_TEXT, [this, rb_by_mm](wxEvent&)
|
||||
{
|
||||
m_sequence.is_mm_intervals = true;
|
||||
rb_by_mm->SetValue(true);
|
||||
});
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_KILL_FOCUS, [this, change_value](wxFocusEvent& event)
|
||||
{
|
||||
change_value();
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_TEXT_ENTER, [this, change_value](wxEvent&)
|
||||
{
|
||||
change_value();
|
||||
});
|
||||
|
||||
m_intervals_grid_sizer->Add(rb_by_mm, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(m_interval_by_mm,0, wxALIGN_CENTER_VERTICAL);
|
||||
m_intervals_grid_sizer->Add(st_by_mm,0, wxALIGN_CENTER_VERTICAL);
|
||||
|
|
@ -108,7 +137,7 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
|||
|
||||
m_extruders_grid_sizer = new wxFlexGridSizer(3, 5, em);
|
||||
|
||||
apply_extruder_sequence();
|
||||
apply_extruder_sequence();
|
||||
|
||||
extruders_box_sizer->Add(m_extruders_grid_sizer, 0, wxALL, em);
|
||||
option_sizer->Add(extruders_box_sizer, 0, wxEXPAND | wxTOP, em);
|
||||
|
|
@ -120,11 +149,20 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
|||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
/* For this moment min sizes for dialog and its sizer are calculated.
|
||||
* If we left them, it can cause a problem with layouts during deleting of extruders
|
||||
*/
|
||||
if (m_sequence.extruders.size()>1)
|
||||
{
|
||||
wxSize sz = wxSize(-1, 10 * em);
|
||||
SetMinSize(sz);
|
||||
GetSizer()->SetMinSize(sz);
|
||||
}
|
||||
}
|
||||
|
||||
void ExtruderSequenceDialog::apply_extruder_sequence()
|
||||
{
|
||||
Freeze();
|
||||
m_extruders_grid_sizer->Clear(true);
|
||||
|
||||
for (size_t extruder=0; extruder < m_sequence.extruders.size(); ++extruder)
|
||||
|
|
@ -161,11 +199,10 @@ void ExtruderSequenceDialog::apply_extruder_sequence()
|
|||
m_extruders_grid_sizer->Add(del_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_extruders_grid_sizer->Add(add_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
}
|
||||
m_extruders_grid_sizer->ShowItems(true); // show items hidden in apply_extruder_selector()
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
|
||||
Thaw();
|
||||
}
|
||||
|
||||
void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@
|
|||
#include <wx/tooltip.h>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#ifdef __WXOSX__
|
||||
#define wxOSX true
|
||||
#else
|
||||
#define wxOSX false
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
wxString double_to_string(double const value, const int max_precision /*= 4*/)
|
||||
|
|
@ -304,7 +310,7 @@ void TextCtrl::BUILD() {
|
|||
auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style);
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
|
||||
if (! m_opt.multiline)
|
||||
if (! m_opt.multiline && !wxOSX)
|
||||
// Only disable background refresh for single line input fields, as they are completely painted over by the edit control.
|
||||
// This does not apply to the multi-line edit field, where the last line and a narrow frame around the text is not cleared.
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
|
@ -491,7 +497,7 @@ void CheckBox::BUILD() {
|
|||
// Set Label as a string of at least one space simbol to correct system scaling of a CheckBox
|
||||
auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(" "), wxDefaultPosition, size);
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
temp->SetValue(check_value);
|
||||
if (m_opt.readonly) temp->Disable();
|
||||
|
||||
|
|
@ -601,7 +607,7 @@ void SpinCtrl::BUILD() {
|
|||
auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
|
||||
0|wxTE_PROCESS_ENTER, min_val, max_val, default_value);
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
// XXX: On OS X the wxSpinCtrl widget is made up of two subwidgets, unfortunatelly
|
||||
// the kill focus event is not propagated to the encompassing widget,
|
||||
|
|
@ -717,7 +723,7 @@ void Choice::BUILD() {
|
|||
}
|
||||
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
// recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
|
@ -1072,7 +1078,7 @@ void ColourPicker::BUILD()
|
|||
|
||||
auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
// // recast as a wxWindow to fit the calling convention
|
||||
window = dynamic_cast<wxWindow*>(temp);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
|
@ -1253,6 +1256,10 @@ wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
|
|||
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar)
|
||||
: m_canvas(canvas)
|
||||
, m_context(nullptr)
|
||||
|
|
@ -1780,6 +1787,18 @@ void GLCanvas3D::render()
|
|||
#endif // ENABLE_RENDER_STATISTICS
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
switch (GLCanvas3DManager::get_framebuffers_type())
|
||||
{
|
||||
case GLCanvas3DManager::FB_Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
|
||||
case GLCanvas3DManager::FB_Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
|
||||
default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, transparent_background); break; }
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void GLCanvas3D::select_all()
|
||||
{
|
||||
m_selection.add_all();
|
||||
|
|
@ -2993,6 +3012,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
volume_bbox.offset(1.0);
|
||||
if (volume_bbox.contains(m_mouse.scene_position))
|
||||
{
|
||||
m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None;
|
||||
// The dragging operation is initiated.
|
||||
m_mouse.drag.move_volume_idx = volume_idx;
|
||||
m_selection.start_dragging();
|
||||
|
|
@ -3678,6 +3698,341 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
|
|||
imgui->end();
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
|
||||
{
|
||||
// debug export of generated image
|
||||
wxImage image(thumbnail_data.width, thumbnail_data.height);
|
||||
image.InitAlpha();
|
||||
|
||||
for (unsigned int r = 0; r < thumbnail_data.height; ++r)
|
||||
{
|
||||
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
|
||||
for (unsigned int c = 0; c < thumbnail_data.width; ++c)
|
||||
{
|
||||
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
|
||||
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
||||
image.SetAlpha((int)c, (int)r, px[3]);
|
||||
}
|
||||
}
|
||||
|
||||
image.SaveFile("C:/prusa/test/test.png", wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
|
||||
|
||||
static void render_volumes_in_thumbnail(Shader& shader, const GLVolumePtrs& volumes, ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
auto is_visible = [](const GLVolume& v) -> bool
|
||||
{
|
||||
bool ret = v.printable;
|
||||
ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside);
|
||||
return ret;
|
||||
};
|
||||
|
||||
static const GLfloat orange[] = { 0.923f, 0.504f, 0.264f, 1.0f };
|
||||
static const GLfloat gray[] = { 0.64f, 0.64f, 0.64f, 1.0f };
|
||||
|
||||
GLVolumePtrs visible_volumes;
|
||||
|
||||
for (GLVolume* vol : volumes)
|
||||
{
|
||||
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0)))
|
||||
{
|
||||
if (!printable_only || is_visible(*vol))
|
||||
visible_volumes.push_back(vol);
|
||||
}
|
||||
}
|
||||
|
||||
if (visible_volumes.empty())
|
||||
return;
|
||||
|
||||
BoundingBoxf3 box;
|
||||
for (const GLVolume* vol : visible_volumes)
|
||||
{
|
||||
box.merge(vol->transformed_bounding_box());
|
||||
}
|
||||
|
||||
Camera camera;
|
||||
camera.set_type(Camera::Ortho);
|
||||
camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height);
|
||||
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
|
||||
camera.apply_view_matrix();
|
||||
camera.apply_projection(box);
|
||||
|
||||
if (transparent_background)
|
||||
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 0.0f));
|
||||
|
||||
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
shader.start_using();
|
||||
|
||||
GLint shader_id = shader.get_shader_program_id();
|
||||
GLint color_id = ::glGetUniformLocation(shader_id, "uniform_color");
|
||||
GLint print_box_detection_id = ::glGetUniformLocation(shader_id, "print_box.volume_detection");
|
||||
glcheck();
|
||||
|
||||
if (print_box_detection_id != -1)
|
||||
glsafe(::glUniform1i(print_box_detection_id, 0));
|
||||
|
||||
for (const GLVolume* vol : visible_volumes)
|
||||
{
|
||||
if (color_id >= 0)
|
||||
glsafe(::glUniform4fv(color_id, 1, (vol->printable && !vol->is_outside) ? orange : gray));
|
||||
else
|
||||
glsafe(::glColor4fv((vol->printable && !vol->is_outside) ? orange : gray));
|
||||
|
||||
vol->render();
|
||||
}
|
||||
|
||||
shader.stop_using();
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
if (transparent_background)
|
||||
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
thumbnail_data.set(w, h);
|
||||
if (!thumbnail_data.is_valid())
|
||||
return;
|
||||
|
||||
bool multisample = m_multisample_allowed;
|
||||
if (multisample)
|
||||
glsafe(::glEnable(GL_MULTISAMPLE));
|
||||
|
||||
GLint max_samples;
|
||||
glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples));
|
||||
GLsizei num_samples = max_samples / 2;
|
||||
|
||||
GLuint render_fbo;
|
||||
glsafe(::glGenFramebuffers(1, &render_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
|
||||
|
||||
GLuint render_tex = 0;
|
||||
GLuint render_tex_buffer = 0;
|
||||
if (multisample)
|
||||
{
|
||||
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
|
||||
glsafe(::glGenRenderbuffers(1, &render_tex_buffer));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer));
|
||||
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h));
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
glsafe(::glGenTextures(1, &render_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
|
||||
}
|
||||
|
||||
GLuint render_depth;
|
||||
glsafe(::glGenRenderbuffers(1, &render_depth));
|
||||
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
|
||||
if (multisample)
|
||||
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h));
|
||||
else
|
||||
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h));
|
||||
|
||||
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
|
||||
|
||||
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
|
||||
|
||||
if (multisample)
|
||||
{
|
||||
GLuint resolve_fbo;
|
||||
glsafe(::glGenFramebuffers(1, &resolve_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo));
|
||||
|
||||
GLuint resolve_tex;
|
||||
glsafe(::glGenTextures(1, &resolve_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0));
|
||||
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo));
|
||||
glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo));
|
||||
glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
|
||||
|
||||
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo));
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
}
|
||||
|
||||
glsafe(::glDeleteTextures(1, &resolve_tex));
|
||||
glsafe(::glDeleteFramebuffers(1, &resolve_fbo));
|
||||
}
|
||||
else
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
debug_output_thumbnail(thumbnail_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
||||
glsafe(::glDeleteRenderbuffers(1, &render_depth));
|
||||
if (render_tex_buffer != 0)
|
||||
glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer));
|
||||
if (render_tex != 0)
|
||||
glsafe(::glDeleteTextures(1, &render_tex));
|
||||
glsafe(::glDeleteFramebuffers(1, &render_fbo));
|
||||
|
||||
if (multisample)
|
||||
glsafe(::glDisable(GL_MULTISAMPLE));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
thumbnail_data.set(w, h);
|
||||
if (!thumbnail_data.is_valid())
|
||||
return;
|
||||
|
||||
bool multisample = m_multisample_allowed;
|
||||
if (multisample)
|
||||
glsafe(::glEnable(GL_MULTISAMPLE));
|
||||
|
||||
GLint max_samples;
|
||||
glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples));
|
||||
GLsizei num_samples = max_samples / 2;
|
||||
|
||||
GLuint render_fbo;
|
||||
glsafe(::glGenFramebuffersEXT(1, &render_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo));
|
||||
|
||||
GLuint render_tex = 0;
|
||||
GLuint render_tex_buffer = 0;
|
||||
if (multisample)
|
||||
{
|
||||
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
|
||||
glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer));
|
||||
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer));
|
||||
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h));
|
||||
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer));
|
||||
}
|
||||
else
|
||||
{
|
||||
glsafe(::glGenTextures(1, &render_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
|
||||
}
|
||||
|
||||
GLuint render_depth;
|
||||
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
|
||||
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
|
||||
if (multisample)
|
||||
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h));
|
||||
else
|
||||
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h));
|
||||
|
||||
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
|
||||
|
||||
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
|
||||
{
|
||||
render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
|
||||
|
||||
if (multisample)
|
||||
{
|
||||
GLuint resolve_fbo;
|
||||
glsafe(::glGenFramebuffersEXT(1, &resolve_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo));
|
||||
|
||||
GLuint resolve_tex;
|
||||
glsafe(::glGenTextures(1, &resolve_tex));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0));
|
||||
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT)
|
||||
{
|
||||
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo));
|
||||
glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo));
|
||||
glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
|
||||
|
||||
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo));
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
}
|
||||
|
||||
glsafe(::glDeleteTextures(1, &resolve_tex));
|
||||
glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo));
|
||||
}
|
||||
else
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
debug_output_thumbnail(thumbnail_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
||||
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
|
||||
if (render_tex_buffer != 0)
|
||||
glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer));
|
||||
if (render_tex != 0)
|
||||
glsafe(::glDeleteTextures(1, &render_tex));
|
||||
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
|
||||
|
||||
if (multisample)
|
||||
glsafe(::glDisable(GL_MULTISAMPLE));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
// check that thumbnail size does not exceed the default framebuffer size
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
unsigned int cnv_w = (unsigned int)cnv_size.get_width();
|
||||
unsigned int cnv_h = (unsigned int)cnv_size.get_height();
|
||||
if ((w > cnv_w) || (h > cnv_h))
|
||||
{
|
||||
float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h);
|
||||
w = (unsigned int)(ratio * (float)w);
|
||||
h = (unsigned int)(ratio * (float)h);
|
||||
}
|
||||
|
||||
thumbnail_data.set(w, h);
|
||||
if (!thumbnail_data.is_valid())
|
||||
return;
|
||||
|
||||
render_volumes_in_thumbnail(m_shader, m_volumes.volumes, thumbnail_data, printable_only, parts_only, transparent_background);
|
||||
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
debug_output_thumbnail(thumbnail_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
|
||||
// restore the default framebuffer size to avoid flickering on the 3D scene
|
||||
m_camera.apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height());
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
bool GLCanvas3D::_init_toolbars()
|
||||
{
|
||||
if (!_init_main_toolbar())
|
||||
|
|
@ -3989,12 +4344,21 @@ BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_be
|
|||
return bb;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor)
|
||||
{
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height(), margin_factor);
|
||||
m_dirty = true;
|
||||
}
|
||||
#else
|
||||
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box)
|
||||
{
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
m_camera.zoom_to_box(box, cnv_size.get_width(), cnv_size.get_height());
|
||||
m_dirty = true;
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void GLCanvas3D::_refresh_if_shown_on_screen()
|
||||
{
|
||||
|
|
@ -4191,7 +4555,9 @@ void GLCanvas3D::_render_objects() const
|
|||
if (m_volumes.empty())
|
||||
return;
|
||||
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR
|
||||
glsafe(::glEnable(GL_LIGHTING));
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
|
||||
|
|
@ -4235,7 +4601,9 @@ void GLCanvas3D::_render_objects() const
|
|||
m_shader.stop_using();
|
||||
|
||||
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR
|
||||
glsafe(::glDisable(GL_LIGHTING));
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_selection() const
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ class GLShader;
|
|||
class ExPolygon;
|
||||
class BackgroundSlicingProcess;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
struct SlicingParameters;
|
||||
enum LayerHeightEditActionType : unsigned int;
|
||||
|
||||
|
|
@ -104,6 +107,10 @@ wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
|||
|
||||
class GLCanvas3D
|
||||
{
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
static const double DefaultCameraZoomToBoxMarginFactor;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
public:
|
||||
struct GCodePreviewVolumeIndex
|
||||
{
|
||||
|
|
@ -525,6 +532,11 @@ public:
|
|||
bool is_dragging() const { return m_gizmos.is_dragging() || m_moving; }
|
||||
|
||||
void render();
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// printable_only == false -> render also non printable volumes as grayed
|
||||
// parts_only == false -> render also sla support and pad
|
||||
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void select_all();
|
||||
void deselect_all();
|
||||
|
|
@ -647,7 +659,11 @@ private:
|
|||
|
||||
BoundingBoxf3 _max_bounding_box(bool include_gizmos, bool include_bed_model) const;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void _zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultCameraZoomToBoxMarginFactor);
|
||||
#else
|
||||
void _zoom_to_box(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void _refresh_if_shown_on_screen();
|
||||
|
||||
|
|
@ -675,6 +691,14 @@ private:
|
|||
void _render_sla_slices() const;
|
||||
void _render_selection_sidebar_hints() const;
|
||||
void _render_undo_redo_stack(const bool is_undo, float pos_x);
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// render thumbnail using an off-screen framebuffer
|
||||
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
// render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported
|
||||
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
// render thumbnail using the default framebuffer
|
||||
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void _update_volumes_hover_state() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool exten
|
|||
|
||||
GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
|
||||
bool GLCanvas3DManager::s_compressed_textures_supported = false;
|
||||
GLCanvas3DManager::EFramebufferType GLCanvas3DManager::s_framebuffers_type = GLCanvas3DManager::FB_None;
|
||||
GLCanvas3DManager::GLInfo GLCanvas3DManager::s_gl_info;
|
||||
|
||||
GLCanvas3DManager::GLCanvas3DManager()
|
||||
|
|
@ -269,6 +270,13 @@ void GLCanvas3DManager::init_gl()
|
|||
else
|
||||
s_compressed_textures_supported = false;
|
||||
|
||||
if (GLEW_ARB_framebuffer_object)
|
||||
s_framebuffers_type = FB_Arb;
|
||||
else if (GLEW_EXT_framebuffer_object)
|
||||
s_framebuffers_type = FB_Ext;
|
||||
else
|
||||
s_framebuffers_type = FB_None;
|
||||
|
||||
if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) {
|
||||
// Complain about the OpenGL version.
|
||||
wxString message = wxString::Format(
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ struct Camera;
|
|||
class GLCanvas3DManager
|
||||
{
|
||||
public:
|
||||
enum EFramebufferType : unsigned char
|
||||
{
|
||||
FB_None,
|
||||
FB_Arb,
|
||||
FB_Ext
|
||||
};
|
||||
|
||||
class GLInfo
|
||||
{
|
||||
mutable bool m_detected;
|
||||
|
|
@ -77,6 +84,7 @@ private:
|
|||
bool m_gl_initialized;
|
||||
static EMultisampleState s_multisample;
|
||||
static bool s_compressed_textures_supported;
|
||||
static EFramebufferType s_framebuffers_type;
|
||||
|
||||
public:
|
||||
GLCanvas3DManager();
|
||||
|
|
@ -97,6 +105,8 @@ public:
|
|||
|
||||
static bool can_multisample() { return s_multisample == MS_Enabled; }
|
||||
static bool are_compressed_textures_supported() { return s_compressed_textures_supported; }
|
||||
static bool are_framebuffers_supported() { return (s_framebuffers_type != FB_None); }
|
||||
static EFramebufferType get_framebuffers_type() { return s_framebuffers_type; }
|
||||
|
||||
static wxGLCanvas* create_wxglcanvas(wxWindow *parent);
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@
|
|||
#include <Shlobj.h>
|
||||
#endif // __WXMSW__
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include <boost/beast/core/detail/base64.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
|
@ -100,7 +105,7 @@ static void register_dpi_event()
|
|||
const auto rect = reinterpret_cast<PRECT>(lParam);
|
||||
const wxRect wxrect(wxPoint(rect->top, rect->left), wxPoint(rect->bottom, rect->right));
|
||||
|
||||
DpiChangedEvent evt(EVT_DPI_CHANGED, dpi, wxrect);
|
||||
DpiChangedEvent evt(EVT_DPI_CHANGED_SLICER, dpi, wxrect);
|
||||
win->GetEventHandler()->AddPendingEvent(evt);
|
||||
|
||||
return true;
|
||||
|
|
@ -1082,6 +1087,123 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
|
|||
return res;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
void GUI_App::gcode_thumbnails_debug()
|
||||
{
|
||||
const std::string BEGIN_MASK = "; thumbnail begin";
|
||||
const std::string END_MASK = "; thumbnail end";
|
||||
std::string gcode_line;
|
||||
bool reading_image = false;
|
||||
unsigned int width = 0;
|
||||
unsigned int height = 0;
|
||||
|
||||
wxFileDialog dialog(GetTopWindow(), _(L("Select a gcode file:")), "", "", "G-code files (*.gcode)|*.gcode;*.GCODE;", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
std::string in_filename = into_u8(dialog.GetPath());
|
||||
std::string out_path = boost::filesystem::path(in_filename).remove_filename().append(L"thumbnail").string();
|
||||
|
||||
boost::nowide::ifstream in_file(in_filename.c_str());
|
||||
std::vector<std::string> rows;
|
||||
std::string row;
|
||||
if (in_file.good())
|
||||
{
|
||||
while (std::getline(in_file, gcode_line))
|
||||
{
|
||||
if (in_file.good())
|
||||
{
|
||||
if (boost::starts_with(gcode_line, BEGIN_MASK))
|
||||
{
|
||||
reading_image = true;
|
||||
gcode_line = gcode_line.substr(BEGIN_MASK.length() + 1);
|
||||
std::string::size_type x_pos = gcode_line.find('x');
|
||||
std::string width_str = gcode_line.substr(0, x_pos);
|
||||
width = (unsigned int)::atoi(width_str.c_str());
|
||||
std::string height_str = gcode_line.substr(x_pos + 1);
|
||||
height = (unsigned int)::atoi(height_str.c_str());
|
||||
row.clear();
|
||||
}
|
||||
else if (reading_image && boost::starts_with(gcode_line, END_MASK))
|
||||
{
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
|
||||
boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
|
||||
if (out_file.good())
|
||||
{
|
||||
std::string decoded;
|
||||
decoded.resize(boost::beast::detail::base64::decoded_size(row.size()));
|
||||
decoded.resize(boost::beast::detail::base64::decode((void*)&decoded[0], row.data(), row.size()).first);
|
||||
|
||||
out_file.write(decoded.c_str(), decoded.size());
|
||||
out_file.close();
|
||||
}
|
||||
#else
|
||||
if (!row.empty())
|
||||
{
|
||||
rows.push_back(row);
|
||||
row.clear();
|
||||
}
|
||||
|
||||
if ((unsigned int)rows.size() == height)
|
||||
{
|
||||
std::vector<unsigned char> thumbnail(4 * width * height, 0);
|
||||
for (unsigned int r = 0; r < (unsigned int)rows.size(); ++r)
|
||||
{
|
||||
std::string decoded_row;
|
||||
decoded_row.resize(boost::beast::detail::base64::decoded_size(rows[r].size()));
|
||||
decoded_row.resize(boost::beast::detail::base64::decode((void*)&decoded_row[0], rows[r].data(), rows[r].size()).first);
|
||||
|
||||
if ((unsigned int)decoded_row.size() == width * 4)
|
||||
{
|
||||
void* image_ptr = (void*)(thumbnail.data() + r * width * 4);
|
||||
::memcpy(image_ptr, (const void*)decoded_row.c_str(), width * 4);
|
||||
}
|
||||
}
|
||||
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
|
||||
for (unsigned int r = 0; r < height; ++r)
|
||||
{
|
||||
unsigned int rr = r * width;
|
||||
for (unsigned int c = 0; c < width; ++c)
|
||||
{
|
||||
unsigned char* px = thumbnail.data() + 4 * (rr + c);
|
||||
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
||||
image.SetAlpha((int)c, (int)r, px[3]);
|
||||
}
|
||||
}
|
||||
|
||||
image.SaveFile(out_path + std::to_string(width) + "x" + std::to_string(height) + ".png", wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
reading_image = false;
|
||||
width = 0;
|
||||
height = 0;
|
||||
rows.clear();
|
||||
}
|
||||
else if (reading_image)
|
||||
{
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
if (!row.empty() && (gcode_line[1] == ' '))
|
||||
{
|
||||
rows.push_back(row);
|
||||
row.clear();
|
||||
}
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
row += gcode_line.substr(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in_file.close();
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
|
||||
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
|
||||
{
|
||||
if (name.empty()) { return; }
|
||||
|
|
|
|||
|
|
@ -188,6 +188,11 @@ public:
|
|||
void open_web_page_localized(const std::string &http_address);
|
||||
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// temporary and debug only -> extract thumbnails from selected gcode and save them as png files
|
||||
void gcode_thumbnails_debug();
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
private:
|
||||
bool on_init_inner();
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ static wxBitmapComboBox* create_word_local_combo(wxWindow *parent)
|
|||
#endif //__WXOSX__
|
||||
|
||||
temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
|
||||
temp->Append(_(L("World coordinates")));
|
||||
temp->Append(_(L("Local coordinates")));
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback)
|
|||
#endif
|
||||
}
|
||||
|
||||
wxDEFINE_EVENT(EVT_DPI_CHANGED, DpiChangedEvent);
|
||||
wxDEFINE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent);
|
||||
|
||||
#ifdef _WIN32
|
||||
template<class F> typename F::FN winapi_get_function(const wchar_t *dll, const char *fn_name) {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ struct DpiChangedEvent : public wxEvent {
|
|||
}
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_DPI_CHANGED, DpiChangedEvent);
|
||||
wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent);
|
||||
|
||||
template<class P> class DPIAware : public P
|
||||
{
|
||||
|
|
@ -75,7 +75,7 @@ public:
|
|||
|
||||
// recalc_font();
|
||||
|
||||
this->Bind(EVT_DPI_CHANGED, [this](const DpiChangedEvent &evt) {
|
||||
this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent &evt) {
|
||||
m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT;
|
||||
|
||||
m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize();
|
||||
|
|
|
|||
|
|
@ -682,6 +682,11 @@ void MainFrame::init_menubar()
|
|||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")),
|
||||
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("DEBUG gcode thumbnails")), _(L("DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails")),
|
||||
[this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
|
||||
}
|
||||
|
||||
// menubar
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ public:
|
|||
staticbox(title!=""), extra_column(extra_clmn) {
|
||||
if (staticbox) {
|
||||
stb = new wxStaticBox(_parent, wxID_ANY, title);
|
||||
stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
stb->SetFont(wxGetApp().bold_font());
|
||||
} else
|
||||
stb = nullptr;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@
|
|||
#include "libslic3r/Format/AMF.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
|
@ -72,6 +75,7 @@
|
|||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/FixModelByWin10.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
#include "../Utils/Thread.hpp"
|
||||
|
||||
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
|
||||
#include "WipeTowerDialog.hpp"
|
||||
|
|
@ -82,6 +86,11 @@ using Slic3r::_3DScene;
|
|||
using Slic3r::Preset;
|
||||
using Slic3r::PrintHostJob;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
static const std::vector < std::pair<unsigned int, unsigned int>> THUMBNAIL_SIZE_FFF = { { 240, 320 }, { 220, 165 }, { 16, 16 } };
|
||||
static const std::vector<std::pair<unsigned int, unsigned int>> THUMBNAIL_SIZE_SLA = { { 800, 480 } };
|
||||
static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
|
@ -175,7 +184,7 @@ void ObjectInfo::msw_rescale()
|
|||
manifold_warning_icon->SetBitmap(create_scaled_bitmap(nullptr, "exclamation"));
|
||||
}
|
||||
|
||||
enum SlisedInfoIdx
|
||||
enum SlicedInfoIdx
|
||||
{
|
||||
siFilament_m,
|
||||
siFilament_mm3,
|
||||
|
|
@ -192,7 +201,7 @@ class SlicedInfo : public wxStaticBoxSizer
|
|||
{
|
||||
public:
|
||||
SlicedInfo(wxWindow *parent);
|
||||
void SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label="");
|
||||
void SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label="");
|
||||
|
||||
private:
|
||||
std::vector<std::pair<wxStaticText*, wxStaticText*>> info_vec;
|
||||
|
|
@ -222,7 +231,7 @@ SlicedInfo::SlicedInfo(wxWindow *parent) :
|
|||
init_info_label(_(L("Used Filament (mm³)")));
|
||||
init_info_label(_(L("Used Filament (g)")));
|
||||
init_info_label(_(L("Used Material (unit)")));
|
||||
init_info_label(_(L("Cost")));
|
||||
init_info_label(_(L("Cost (money)")));
|
||||
init_info_label(_(L("Estimated printing time")));
|
||||
init_info_label(_(L("Number of tool changes")));
|
||||
|
||||
|
|
@ -230,7 +239,7 @@ SlicedInfo::SlicedInfo(wxWindow *parent) :
|
|||
this->Show(false);
|
||||
}
|
||||
|
||||
void SlicedInfo::SetTextAndShow(SlisedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/)
|
||||
void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const wxString& new_label/*=""*/)
|
||||
{
|
||||
const bool show = text != "N/A";
|
||||
if (show)
|
||||
|
|
@ -1120,12 +1129,10 @@ void Sidebar::show_info_sizer()
|
|||
}
|
||||
}
|
||||
|
||||
void Sidebar::show_sliced_info_sizer(const bool show)
|
||||
void Sidebar::update_sliced_info_sizer()
|
||||
{
|
||||
wxWindowUpdateLocker freeze_guard(this);
|
||||
|
||||
p->sliced_info->Show(show);
|
||||
if (show) {
|
||||
if (p->sliced_info->IsShown(size_t(0)))
|
||||
{
|
||||
if (p->plater->printer_technology() == ptSLA)
|
||||
{
|
||||
const SLAPrintStatistics& ps = p->plater->sla_print().print_statistics();
|
||||
|
|
@ -1141,7 +1148,18 @@ void Sidebar::show_sliced_info_sizer(const bool show)
|
|||
wxString::Format("%.2f", (ps.objects_used_material + ps.support_used_material) / 1000);
|
||||
p->sliced_info->SetTextAndShow(siMateril_unit, info_text, new_label);
|
||||
|
||||
p->sliced_info->SetTextAndShow(siCost, "N/A"/*wxString::Format("%.2f", ps.total_cost)*/);
|
||||
wxString str_total_cost = "N/A";
|
||||
|
||||
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_SLA_MATERIAL)->get_config();
|
||||
if (cfg->option("bottle_cost")->getFloat() > 0.0 &&
|
||||
cfg->option("bottle_volume")->getFloat() > 0.0)
|
||||
{
|
||||
double material_cost = cfg->option("bottle_cost")->getFloat() /
|
||||
cfg->option("bottle_volume")->getFloat();
|
||||
str_total_cost = wxString::Format("%.2f", material_cost*(ps.objects_used_material + ps.support_used_material) / 1000);
|
||||
}
|
||||
p->sliced_info->SetTextAndShow(siCost, str_total_cost);
|
||||
|
||||
wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time));
|
||||
p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _(L("Estimated printing time")) + " :");
|
||||
|
||||
|
|
@ -1209,12 +1227,21 @@ void Sidebar::show_sliced_info_sizer(const bool show)
|
|||
}
|
||||
|
||||
// if there is a wipe tower, insert number of toolchanges info into the array:
|
||||
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", p->plater->fff_print().wipe_tower_data().number_of_toolchanges) : "N/A");
|
||||
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A");
|
||||
|
||||
// Hide non-FFF sliced info parameters
|
||||
p->sliced_info->SetTextAndShow(siMateril_unit, "N/A");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sidebar::show_sliced_info_sizer(const bool show)
|
||||
{
|
||||
wxWindowUpdateLocker freeze_guard(this);
|
||||
|
||||
p->sliced_info->Show(show);
|
||||
if (show)
|
||||
update_sliced_info_sizer();
|
||||
|
||||
Layout();
|
||||
p->scrolled->Refresh();
|
||||
|
|
@ -1362,6 +1389,9 @@ struct Plater::priv
|
|||
Slic3r::Model model;
|
||||
PrinterTechnology printer_technology = ptFFF;
|
||||
Slic3r::GCodePreviewData gcode_preview_data;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::vector<Slic3r::ThumbnailData> thumbnail_data;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// GUI elements
|
||||
wxSizer* panel_sizer{ nullptr };
|
||||
|
|
@ -1428,7 +1458,7 @@ struct Plater::priv
|
|||
class Job : public wxEvtHandler
|
||||
{
|
||||
int m_range = 100;
|
||||
std::future<void> m_ftr;
|
||||
boost::thread m_thread;
|
||||
priv * m_plater = nullptr;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false;
|
||||
|
|
@ -1469,7 +1499,8 @@ struct Plater::priv
|
|||
// Do a full refresh of scene tree, including regenerating
|
||||
// all the GLVolumes. FIXME The update function shall just
|
||||
// reload the modified matrices.
|
||||
if (!was_canceled()) plater().update((unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
|
||||
if (!was_canceled())
|
||||
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -1498,9 +1529,9 @@ struct Plater::priv
|
|||
}
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = default;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = default;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
virtual void process() = 0;
|
||||
|
||||
|
|
@ -1524,7 +1555,7 @@ struct Plater::priv
|
|||
wxBeginBusyCursor();
|
||||
|
||||
try { // Execute the job
|
||||
m_ftr = std::async(std::launch::async, &Job::run, this);
|
||||
m_thread = create_thread([this] { this->run(); });
|
||||
} catch (std::exception &) {
|
||||
update_status(status_range(),
|
||||
_(L("ERROR: not enough resources to "
|
||||
|
|
@ -1540,16 +1571,15 @@ struct Plater::priv
|
|||
// returned if the timeout has been reached and the job is still
|
||||
// running. Call cancel() before this fn if you want to explicitly
|
||||
// end the job.
|
||||
bool join(int timeout_ms = 0) const
|
||||
bool join(int timeout_ms = 0)
|
||||
{
|
||||
if (!m_ftr.valid()) return true;
|
||||
|
||||
if (!m_thread.joinable()) return true;
|
||||
|
||||
if (timeout_ms <= 0)
|
||||
m_ftr.wait();
|
||||
else if (m_ftr.wait_for(std::chrono::milliseconds(
|
||||
timeout_ms)) == std::future_status::timeout)
|
||||
m_thread.join();
|
||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||
return false;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1916,6 +1946,10 @@ struct Plater::priv
|
|||
bool can_mirror() const;
|
||||
bool can_reload_from_disk() const;
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void msw_rescale_object_menu();
|
||||
|
||||
// returns the path to project file with the given extension (none if extension == wxEmptyString)
|
||||
|
|
@ -1983,6 +2017,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
background_process.set_fff_print(&fff_print);
|
||||
background_process.set_sla_print(&sla_print);
|
||||
background_process.set_gcode_preview_data(&gcode_preview_data);
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
background_process.set_thumbnail_data(&thumbnail_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
|
||||
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
|
||||
// Default printer technology for default config.
|
||||
|
|
@ -3032,6 +3069,34 @@ bool Plater::priv::restart_background_process(unsigned int state)
|
|||
( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
|
||||
(state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
|
||||
(state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (((state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) == 0) &&
|
||||
(this->background_process.state() != BackgroundSlicingProcess::STATE_RUNNING))
|
||||
{
|
||||
// update thumbnail data
|
||||
if (this->printer_technology == ptFFF)
|
||||
{
|
||||
// for ptFFF we need to generate the thumbnails before the export of gcode starts
|
||||
this->thumbnail_data.clear();
|
||||
for (const std::pair<unsigned int, unsigned int>& size : THUMBNAIL_SIZE_FFF)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, true, false);
|
||||
}
|
||||
}
|
||||
else if (this->printer_technology == ptSLA)
|
||||
{
|
||||
// for ptSLA generate thumbnails without supports and pad (not yet calculated)
|
||||
// to render also supports and pad see on_slicing_update()
|
||||
this->thumbnail_data.clear();
|
||||
for (const std::pair<unsigned int, unsigned int>& size : THUMBNAIL_SIZE_SLA)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// The print is valid and it can be started.
|
||||
if (this->background_process.start()) {
|
||||
this->statusbar()->set_cancel_callback([this]() {
|
||||
|
|
@ -3369,6 +3434,23 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
|||
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
|
||||
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
|
||||
this->preview->reload_print();
|
||||
|
||||
// uncomment the following lines if you want to render into the thumbnail also supports and pad for SLA printer
|
||||
/*
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// update thumbnail data
|
||||
// for ptSLA generate the thumbnail after supports and pad have been calculated to have them rendered
|
||||
if ((this->printer_technology == ptSLA) && (evt.status.percent == -3))
|
||||
{
|
||||
this->thumbnail_data.clear();
|
||||
for (const std::pair<unsigned int, unsigned int>& size : THUMBNAIL_SIZE_SLA)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.first, size.second, true, false, false);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3594,6 +3676,13 @@ bool Plater::priv::init_object_menu()
|
|||
return true;
|
||||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, transparent_background);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void Plater::priv::msw_rescale_object_menu()
|
||||
{
|
||||
for (MenuWithSeparators* menu : { &object_menu, &sla_object_menu, &part_menu, &default_menu })
|
||||
|
|
@ -4632,7 +4721,13 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
|||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
const std::string path_u8 = into_u8(path);
|
||||
wxBusyCursor wait;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
ThumbnailData thumbnail_data;
|
||||
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true);
|
||||
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, &thumbnail_data)) {
|
||||
#else
|
||||
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// Success
|
||||
p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path));
|
||||
p->set_project_filename(path);
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ public:
|
|||
void update_objects_list_extruder_column(size_t extruders_count);
|
||||
void show_info_sizer();
|
||||
void show_sliced_info_sizer(const bool show);
|
||||
void update_sliced_info_sizer();
|
||||
void enable_buttons(bool enable);
|
||||
void set_btn_label(const ActionButtonType btn_type, const wxString& label) const;
|
||||
bool show_reslice(bool show) const;
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ const std::vector<std::string>& Preset::print_options()
|
|||
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects",
|
||||
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
|
||||
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming",
|
||||
"compatible_printers", "compatible_printers_condition", "inherits"
|
||||
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
|
||||
};
|
||||
return s_opts;
|
||||
}
|
||||
|
|
@ -513,6 +513,10 @@ const std::vector<std::string>& Preset::sla_material_options()
|
|||
s_opts = {
|
||||
"material_type",
|
||||
"initial_layer_height",
|
||||
"bottle_cost",
|
||||
"bottle_volume",
|
||||
"bottle_weight",
|
||||
"material_density",
|
||||
"exposure_time",
|
||||
"initial_exposure_time",
|
||||
"material_correction",
|
||||
|
|
|
|||
|
|
@ -1170,6 +1170,7 @@ void TabPrint::build()
|
|||
optgroup->append_single_option_line("wipe_tower_width");
|
||||
optgroup->append_single_option_line("wipe_tower_rotation_angle");
|
||||
optgroup->append_single_option_line("wipe_tower_bridging");
|
||||
optgroup->append_single_option_line("wipe_tower_no_sparse_layers");
|
||||
optgroup->append_single_option_line("single_extruder_multi_material_priming");
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Advanced")));
|
||||
|
|
@ -3419,8 +3420,41 @@ void TabSLAMaterial::build()
|
|||
|
||||
auto page = add_options_page(_(L("Material")), "resin");
|
||||
|
||||
auto optgroup = page->new_optgroup(_(L("Layers")));
|
||||
// optgroup->append_single_option_line("layer_height");
|
||||
auto optgroup = page->new_optgroup(_(L("Material")));
|
||||
optgroup->append_single_option_line("bottle_cost");
|
||||
optgroup->append_single_option_line("bottle_volume");
|
||||
optgroup->append_single_option_line("bottle_weight");
|
||||
optgroup->append_single_option_line("material_density");
|
||||
|
||||
optgroup->m_on_change = [this, optgroup](t_config_option_key opt_key, boost::any value)
|
||||
{
|
||||
DynamicPrintConfig new_conf = *m_config;
|
||||
|
||||
if (opt_key == "bottle_volume") {
|
||||
double new_bottle_weight = boost::any_cast<double>(value)/(new_conf.option("material_density")->getFloat() * 1000);
|
||||
new_conf.set_key_value("bottle_weight", new ConfigOptionFloat(new_bottle_weight));
|
||||
}
|
||||
if (opt_key == "bottle_weight") {
|
||||
double new_bottle_volume = boost::any_cast<double>(value)*(new_conf.option("material_density")->getFloat() * 1000);
|
||||
new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume));
|
||||
}
|
||||
if (opt_key == "material_density") {
|
||||
double new_bottle_volume = new_conf.option("bottle_weight")->getFloat() * boost::any_cast<double>(value) * 1000;
|
||||
new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume));
|
||||
}
|
||||
|
||||
load_config(new_conf);
|
||||
|
||||
update_dirty();
|
||||
on_value_change(opt_key, value);
|
||||
|
||||
if (opt_key == "bottle_volume" || opt_key == "bottle_cost") {
|
||||
wxGetApp().sidebar().update_sliced_info_sizer();
|
||||
wxGetApp().sidebar().Layout();
|
||||
}
|
||||
};
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Layers")));
|
||||
optgroup->append_single_option_line("initial_layer_height");
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Exposure")));
|
||||
|
|
|
|||
|
|
@ -533,6 +533,8 @@ void apply_extruder_selector(wxBitmapComboBox** ctrl,
|
|||
(*ctrl)->SetSize(size);
|
||||
(*ctrl)->Clear();
|
||||
}
|
||||
if (first_item.empty())
|
||||
(*ctrl)->Hide(); // to avoid unwanted rendering before layout (ExtruderSequenceDialog)
|
||||
|
||||
if (icons.empty() && !first_item.empty()) {
|
||||
(*ctrl)->Append(_(first_item), wxNullBitmap);
|
||||
|
|
|
|||
219
src/slic3r/Utils/FlashAir.cpp
Normal file
219
src/slic3r/Utils/FlashAir.cpp
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
#include "FlashAir.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
FlashAir::FlashAir(DynamicPrintConfig *config) :
|
||||
host(config->opt_string("print_host"))
|
||||
{}
|
||||
|
||||
FlashAir::~FlashAir() {}
|
||||
|
||||
const char* FlashAir::get_name() const { return "FlashAir"; }
|
||||
|
||||
bool FlashAir::test(wxString &msg) const
|
||||
{
|
||||
// Since the request is performed synchronously here,
|
||||
// it is ok to refer to `msg` from within the closure
|
||||
|
||||
const char *name = get_name();
|
||||
|
||||
bool res = false;
|
||||
auto url = make_url("command.cgi", "op", "118");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get upload enabled at: %2%") % name % url;
|
||||
|
||||
auto http = Http::get(std::move(url));
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting upload enabled: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
msg = format_error(body, error, status);
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got upload enabled: %2%") % name % body;
|
||||
|
||||
res = boost::starts_with(body, "1");
|
||||
if (! res) {
|
||||
msg = _(L("Upload not enabled on FlashAir card."));
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString FlashAir::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to FlashAir works correctly and upload is enabled."));
|
||||
}
|
||||
|
||||
wxString FlashAir::get_test_failed_msg (wxString &msg) const
|
||||
{
|
||||
return wxString::Format("%s: %s", _(L("Could not connect to FlashAir")), msg, _(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required.")));
|
||||
}
|
||||
|
||||
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
||||
{
|
||||
const char *name = get_name();
|
||||
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
|
||||
wxString test_msg;
|
||||
if (! test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = false;
|
||||
|
||||
auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str());
|
||||
auto urlUpload = make_url("upload.cgi");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%")
|
||||
% name
|
||||
% upload_data.source_path
|
||||
% urlPrepare
|
||||
% urlUpload
|
||||
% upload_filename.string();
|
||||
|
||||
// set filetime for upload and make card writeprotect to prevent filesystem damage
|
||||
auto httpPrepare = Http::get(std::move(urlPrepare));
|
||||
httpPrepare.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error prepareing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got prepare result: %2%") % name % body;
|
||||
res = boost::icontains(body, "SUCCESS");
|
||||
if (! res) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if(! res ) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// start file upload
|
||||
auto http = Http::post(std::move(urlUpload));
|
||||
http.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
res = boost::icontains(body, "SUCCESS");
|
||||
if (! res) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name;
|
||||
error_fn(format_error(body, L("Unknown error occured"), 0));
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
// Upload was canceled
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Upload canceled") % name;
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool FlashAir::has_auto_discovery() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FlashAir::can_test() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FlashAir::can_start_print() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string FlashAir::timestamp_str() const
|
||||
{
|
||||
auto t = std::time(nullptr);
|
||||
auto tm = *std::localtime(&t);
|
||||
|
||||
const char *name = get_name();
|
||||
|
||||
unsigned long fattime = ((tm.tm_year - 80) << 25) |
|
||||
((tm.tm_mon + 1) << 21) |
|
||||
(tm.tm_mday << 16) |
|
||||
(tm.tm_hour << 11) |
|
||||
(tm.tm_min << 5) |
|
||||
(tm.tm_sec >> 1);
|
||||
|
||||
return (boost::format("%1$#x") % fattime).str();
|
||||
}
|
||||
|
||||
std::string FlashAir::make_url(const std::string &path) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % host % path).str();
|
||||
}
|
||||
} else {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("http://%1%%2%") % host % path).str();
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % host % path).str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string FlashAir::make_url(const std::string &path, const std::string &arg, const std::string &val) const
|
||||
{
|
||||
if (host.find("http://") == 0 || host.find("https://") == 0) {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("%1%%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
}
|
||||
} else {
|
||||
if (host.back() == '/') {
|
||||
return (boost::format("http://%1%%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%?%3%=%4%") % host % path % arg % val).str();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
44
src/slic3r/Utils/FlashAir.hpp
Normal file
44
src/slic3r/Utils/FlashAir.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef slic3r_FlashAir_hpp_
|
||||
#define slic3r_FlashAir_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
class FlashAir : public PrintHost
|
||||
{
|
||||
public:
|
||||
FlashAir(DynamicPrintConfig *config);
|
||||
virtual ~FlashAir();
|
||||
|
||||
virtual const char* get_name() const;
|
||||
|
||||
virtual bool test(wxString &curl_msg) const;
|
||||
virtual wxString get_test_ok_msg () const;
|
||||
virtual wxString get_test_failed_msg (wxString &msg) const;
|
||||
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const;
|
||||
virtual bool has_auto_discovery() const;
|
||||
virtual bool can_test() const;
|
||||
virtual bool can_start_print() const;
|
||||
virtual std::string get_host() const { return host; }
|
||||
|
||||
private:
|
||||
std::string host;
|
||||
|
||||
std::string timestamp_str() const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
std::string make_url(const std::string &path, const std::string &arg, const std::string &val) const;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
#include "libslic3r/Channel.hpp"
|
||||
#include "OctoPrint.hpp"
|
||||
#include "Duet.hpp"
|
||||
#include "FlashAir.hpp"
|
||||
#include "../GUI/PrintHostDialogs.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
|
@ -43,6 +44,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
|||
switch (host_type) {
|
||||
case htOctoPrint: return new OctoPrint(config);
|
||||
case htDuet: return new Duet(config);
|
||||
case htFlashAir: return new FlashAir(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
28
src/slic3r/Utils/Thread.hpp
Normal file
28
src/slic3r/Utils/Thread.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef THREAD_HPP
|
||||
#define THREAD_HPP
|
||||
|
||||
#include <utility>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
template<class Fn>
|
||||
inline boost::thread create_thread(boost::thread::attributes &attrs, Fn &&fn)
|
||||
{
|
||||
// Duplicating the stack allocation size of Thread Building Block worker
|
||||
// threads of the thread pool: allocate 4MB on a 64bit system, allocate 2MB
|
||||
// on a 32bit system by default.
|
||||
|
||||
attrs.set_stack_size((sizeof(void*) == 4) ? (2048 * 1024) : (4096 * 1024));
|
||||
return boost::thread{attrs, std::forward<Fn>(fn)};
|
||||
}
|
||||
|
||||
template<class Fn> inline boost::thread create_thread(Fn &&fn)
|
||||
{
|
||||
boost::thread::attributes attrs;
|
||||
return create_thread(attrs, std::forward<Fn>(fn));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // THREAD_HPP
|
||||
Loading…
Add table
Add a link
Reference in a new issue