mirror of
https://github.com/QIDITECH/QIDIStudio.git
synced 2026-02-04 10:58:41 +03:00
update
This commit is contained in:
1600
src/libslic3r/GCode/AvoidCrossingPerimeters.cpp
Normal file
1600
src/libslic3r/GCode/AvoidCrossingPerimeters.cpp
Normal file
File diff suppressed because it is too large
Load Diff
71
src/libslic3r/GCode/AvoidCrossingPerimeters.hpp
Normal file
71
src/libslic3r/GCode/AvoidCrossingPerimeters.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef slic3r_AvoidCrossingPerimeters_hpp_
|
||||
#define slic3r_AvoidCrossingPerimeters_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../ExPolygon.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class GCode;
|
||||
class Layer;
|
||||
class Point;
|
||||
|
||||
class AvoidCrossingPerimeters
|
||||
{
|
||||
public:
|
||||
// Routing around the objects vs. inside a single object.
|
||||
void use_external_mp(bool use = true) { m_use_external_mp = use; };
|
||||
void use_external_mp_once() { m_use_external_mp_once = true; }
|
||||
bool used_external_mp_once() { return m_use_external_mp_once; }
|
||||
void disable_once() { m_disabled_once = true; }
|
||||
bool disabled_once() const { return m_disabled_once; }
|
||||
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point)
|
||||
{
|
||||
bool could_be_wipe_disabled;
|
||||
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
|
||||
}
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
|
||||
|
||||
struct Boundary {
|
||||
// Collection of boundaries used for detection of crossing perimeters for travels
|
||||
Polygons boundaries;
|
||||
// Bounding box of boundaries
|
||||
BoundingBoxf bbox;
|
||||
// Precomputed distances of all points in boundaries
|
||||
std::vector<std::vector<float>> boundaries_params;
|
||||
// Used for detection of intersection between line and any polygon from boundaries
|
||||
EdgeGrid::Grid grid;
|
||||
|
||||
void clear()
|
||||
{
|
||||
boundaries.clear();
|
||||
boundaries_params.clear();
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
bool m_use_external_mp { false };
|
||||
// just for the next travel move
|
||||
bool m_use_external_mp_once { false };
|
||||
// this flag disables reduce_crossing_wall just for the next travel move
|
||||
// we enable it by default for the first travel move in print
|
||||
bool m_disabled_once { true };
|
||||
|
||||
// Used for detection of line or polyline is inside of any polygon.
|
||||
EdgeGrid::Grid m_grid_lslice;
|
||||
// Store all needed data for travels inside object
|
||||
Boundary m_internal;
|
||||
// Store all needed data for travels outside object
|
||||
Boundary m_external;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_AvoidCrossingPerimeters_hpp_
|
||||
311
src/libslic3r/GCode/ConflictChecker.cpp
Normal file
311
src/libslic3r/GCode/ConflictChecker.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "ConflictChecker.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace RasterizationImpl {
|
||||
using IndexPair = std::pair<int64_t, int64_t>;
|
||||
using Grids = std::vector<IndexPair>;
|
||||
|
||||
inline constexpr int64_t RasteXDistance = scale_(1);
|
||||
inline constexpr int64_t RasteYDistance = scale_(1);
|
||||
|
||||
inline IndexPair point_map_grid_index(const Point &pt, int64_t xdist, int64_t ydist)
|
||||
{
|
||||
auto x = pt.x() / xdist;
|
||||
auto y = pt.y() / ydist;
|
||||
return std::make_pair(x, y);
|
||||
}
|
||||
|
||||
inline bool nearly_equal(const Point &p1, const Point &p2) { return std::abs(p1.x() - p2.x()) < SCALED_EPSILON && std::abs(p1.y() - p2.y()) < SCALED_EPSILON; }
|
||||
|
||||
inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance, int64_t ydist = RasteYDistance)
|
||||
{
|
||||
Grids res;
|
||||
Point rayStart = line.a;
|
||||
Point rayEnd = line.b;
|
||||
IndexPair currentVoxel = point_map_grid_index(rayStart, xdist, ydist);
|
||||
IndexPair firstVoxel = currentVoxel;
|
||||
IndexPair lastVoxel = point_map_grid_index(rayEnd, xdist, ydist);
|
||||
|
||||
Point ray = rayEnd - rayStart;
|
||||
|
||||
double stepX = ray.x() >= 0 ? 1 : -1;
|
||||
double stepY = ray.y() >= 0 ? 1 : -1;
|
||||
|
||||
double nextVoxelBoundaryX = (currentVoxel.first + stepX) * xdist;
|
||||
double nextVoxelBoundaryY = (currentVoxel.second + stepY) * ydist;
|
||||
|
||||
if (stepX < 0) { nextVoxelBoundaryX += xdist; }
|
||||
if (stepY < 0) { nextVoxelBoundaryY += ydist; }
|
||||
|
||||
double tMaxX = ray.x() != 0 ? (nextVoxelBoundaryX - rayStart.x()) / ray.x() : DBL_MAX;
|
||||
double tMaxY = ray.y() != 0 ? (nextVoxelBoundaryY - rayStart.y()) / ray.y() : DBL_MAX;
|
||||
|
||||
double tDeltaX = ray.x() != 0 ? static_cast<double>(xdist) / ray.x() * stepX : DBL_MAX;
|
||||
double tDeltaY = ray.y() != 0 ? static_cast<double>(ydist) / ray.y() * stepY : DBL_MAX;
|
||||
|
||||
res.push_back(currentVoxel);
|
||||
|
||||
double tx = tMaxX;
|
||||
double ty = tMaxY;
|
||||
|
||||
while (lastVoxel != currentVoxel) {
|
||||
if (lastVoxel.first == currentVoxel.first) {
|
||||
for (int64_t i = currentVoxel.second; i != lastVoxel.second; i += (int64_t) stepY) {
|
||||
currentVoxel.second += (int64_t) stepY;
|
||||
res.push_back(currentVoxel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (lastVoxel.second == currentVoxel.second) {
|
||||
for (int64_t i = currentVoxel.first; i != lastVoxel.first; i += (int64_t) stepX) {
|
||||
currentVoxel.first += (int64_t) stepX;
|
||||
res.push_back(currentVoxel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (tx < ty) {
|
||||
currentVoxel.first += (int64_t) stepX;
|
||||
tx += tDeltaX;
|
||||
} else {
|
||||
currentVoxel.second += (int64_t) stepY;
|
||||
ty += tDeltaY;
|
||||
}
|
||||
res.push_back(currentVoxel);
|
||||
if (res.size() >= 100000) { // bug
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace RasterizationImpl
|
||||
|
||||
void LinesBucketQueue::emplace_back_bucket(ExtrusionLayers &&els, const void *objPtr, Point offset)
|
||||
{
|
||||
auto oldSize = line_buckets.capacity();
|
||||
line_buckets.emplace_back(std::move(els), objPtr, offset);
|
||||
auto newSize = line_buckets.capacity();
|
||||
// Since line_bucket_ptr_queue is storing pointers into line_buckets,
|
||||
// we need to handle the case where the capacity changes since that makes
|
||||
// the existing pointers invalid
|
||||
if (oldSize == newSize) {
|
||||
line_bucket_ptr_queue.push(&line_buckets.back());
|
||||
}
|
||||
else { // pointers change, create a new queue from scratch
|
||||
decltype(line_bucket_ptr_queue) newQueue;
|
||||
for (LinesBucket &bucket : line_buckets) { newQueue.push(&bucket); }
|
||||
std::swap(line_bucket_ptr_queue, newQueue);
|
||||
}
|
||||
}
|
||||
|
||||
// remove lowest and get the current bottom z
|
||||
float LinesBucketQueue::getCurrBottomZ()
|
||||
{
|
||||
auto lowest = line_bucket_ptr_queue.top();
|
||||
line_bucket_ptr_queue.pop();
|
||||
float layerBottomZ = lowest->curBottomZ();
|
||||
std::vector<LinesBucket *> lowests;
|
||||
lowests.push_back(lowest);
|
||||
|
||||
while (line_bucket_ptr_queue.empty() == false && std::abs(line_bucket_ptr_queue.top()->curBottomZ() - lowest->curBottomZ()) < EPSILON) {
|
||||
lowests.push_back(line_bucket_ptr_queue.top());
|
||||
line_bucket_ptr_queue.pop();
|
||||
}
|
||||
|
||||
for (LinesBucket *bp : lowests) {
|
||||
bp->raise();
|
||||
if (bp->valid()) { line_bucket_ptr_queue.push(bp); }
|
||||
}
|
||||
return layerBottomZ;
|
||||
}
|
||||
|
||||
LineWithIDs LinesBucketQueue::getCurLines() const
|
||||
{
|
||||
LineWithIDs lines;
|
||||
for (const LinesBucket &bucket : line_buckets) {
|
||||
if (bucket.valid()) {
|
||||
LineWithIDs tmpLines = bucket.curLines();
|
||||
lines.insert(lines.end(), tmpLines.begin(), tmpLines.end());
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths)
|
||||
{
|
||||
std::function<void(const ExtrusionEntityCollection *, ExtrusionPaths &)> getExtrusionPathImpl = [&](const ExtrusionEntityCollection *entity, ExtrusionPaths &paths) {
|
||||
for (auto entityPtr : entity->entities) {
|
||||
if (const ExtrusionEntityCollection *collection = dynamic_cast<ExtrusionEntityCollection *>(entityPtr)) {
|
||||
getExtrusionPathImpl(collection, paths);
|
||||
} else if (const ExtrusionPath *path = dynamic_cast<ExtrusionPath *>(entityPtr)) {
|
||||
paths.push_back(*path);
|
||||
} else if (const ExtrusionMultiPath *multipath = dynamic_cast<ExtrusionMultiPath *>(entityPtr)) {
|
||||
for (const ExtrusionPath &path : multipath->paths) { paths.push_back(path); }
|
||||
} else if (const ExtrusionLoop *loop = dynamic_cast<ExtrusionLoop *>(entityPtr)) {
|
||||
for (const ExtrusionPath &path : loop->paths) { paths.push_back(path); }
|
||||
}
|
||||
}
|
||||
};
|
||||
getExtrusionPathImpl(entity, paths);
|
||||
}
|
||||
|
||||
ExtrusionLayers getExtrusionPathsFromLayer(const LayerRegionPtrs layerRegionPtrs)
|
||||
{
|
||||
ExtrusionLayers perimeters; // periments and infills
|
||||
perimeters.resize(layerRegionPtrs.size());
|
||||
int i = 0;
|
||||
for (LayerRegion *regionPtr : layerRegionPtrs) {
|
||||
perimeters[i].layer = regionPtr->layer();
|
||||
perimeters[i].bottom_z = regionPtr->layer()->bottom_z();
|
||||
perimeters[i].height = regionPtr->layer()->height;
|
||||
getExtrusionPathsFromEntity(®ionPtr->perimeters, perimeters[i].paths);
|
||||
getExtrusionPathsFromEntity(®ionPtr->fills, perimeters[i].paths);
|
||||
++i;
|
||||
}
|
||||
return perimeters;
|
||||
}
|
||||
|
||||
ExtrusionLayer getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer)
|
||||
{
|
||||
ExtrusionLayer el;
|
||||
getExtrusionPathsFromEntity(&supportLayer->support_fills, el.paths);
|
||||
el.layer = supportLayer;
|
||||
el.bottom_z = supportLayer->bottom_z();
|
||||
el.height = supportLayer->height;
|
||||
return el;
|
||||
}
|
||||
|
||||
ObjectExtrusions getAllLayersExtrusionPathsFromObject(PrintObject *obj)
|
||||
{
|
||||
ObjectExtrusions oe;
|
||||
|
||||
for (auto layerPtr : obj->layers()) {
|
||||
auto perimeters = getExtrusionPathsFromLayer(layerPtr->regions());
|
||||
oe.perimeters.insert(oe.perimeters.end(), perimeters.begin(), perimeters.end());
|
||||
}
|
||||
|
||||
for (auto supportLayerPtr : obj->support_layers()) { oe.support.push_back(getExtrusionPathsFromSupportLayer(supportLayerPtr)); }
|
||||
|
||||
return oe;
|
||||
}
|
||||
|
||||
ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines)
|
||||
{
|
||||
using namespace RasterizationImpl;
|
||||
std::map<IndexPair, std::vector<int>> indexToLine;
|
||||
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
const LineWithID &l1 = lines[i];
|
||||
auto indexes = line_rasterization(l1._line);
|
||||
for (auto index : indexes) {
|
||||
const auto &possibleIntersectIdxs = indexToLine[index];
|
||||
for (auto possibleIntersectIdx : possibleIntersectIdxs) {
|
||||
const LineWithID &l2 = lines[possibleIntersectIdx];
|
||||
if (auto interRes = line_intersect(l1, l2); interRes.has_value()) { return interRes; }
|
||||
}
|
||||
indexToLine[index].push_back(i);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs,
|
||||
std::optional<const FakeWipeTower *> wtdptr) // find the first intersection point of lines in different objects
|
||||
{
|
||||
if (objs.size() <= 1 && !wtdptr) { return {}; }
|
||||
LinesBucketQueue conflictQueue;
|
||||
|
||||
if (wtdptr.has_value()) { // wipe tower at 0 by default
|
||||
auto wtpaths = wtdptr.value()->getFakeExtrusionPathsFromWipeTower();
|
||||
ExtrusionLayers wtels;
|
||||
wtels.type = ExtrusionLayersType::WIPE_TOWER;
|
||||
for (int i = 0; i < wtpaths.size(); ++i) { // assume that wipe tower always has same height
|
||||
ExtrusionLayer el;
|
||||
el.paths = wtpaths[i];
|
||||
el.bottom_z = wtpaths[i].front().height * (float) i;
|
||||
el.layer = nullptr;
|
||||
wtels.push_back(el);
|
||||
}
|
||||
conflictQueue.emplace_back_bucket(std::move(wtels), wtdptr.value(), {wtdptr.value()->plate_origin.x(), wtdptr.value()->plate_origin.y()});
|
||||
}
|
||||
for (PrintObject *obj : objs) {
|
||||
auto layers = getAllLayersExtrusionPathsFromObject(obj);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.perimeters), obj, obj->instances().front().shift);
|
||||
conflictQueue.emplace_back_bucket(std::move(layers.support), obj, obj->instances().front().shift);
|
||||
}
|
||||
|
||||
std::vector<LineWithIDs> layersLines;
|
||||
std::vector<float> bottomZs;
|
||||
while (conflictQueue.valid()) {
|
||||
LineWithIDs lines = conflictQueue.getCurLines();
|
||||
float curBottomZ = conflictQueue.getCurrBottomZ();
|
||||
bottomZs.push_back(curBottomZ);
|
||||
layersLines.push_back(std::move(lines));
|
||||
}
|
||||
|
||||
bool find = false;
|
||||
tbb::concurrent_vector<std::pair<ConflictComputeResult, float>> conflict;
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, layersLines.size()), [&](tbb::blocked_range<size_t> range) {
|
||||
for (size_t i = range.begin(); i < range.end(); i++) {
|
||||
auto interRes = find_inter_of_lines(layersLines[i]);
|
||||
if (interRes.has_value()) {
|
||||
find = true;
|
||||
conflict.emplace_back(interRes.value(), bottomZs[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (find) {
|
||||
const void *ptr1 = conflict[0].first._obj1;
|
||||
const void *ptr2 = conflict[0].first._obj2;
|
||||
float conflictPrintZ = conflict[0].second;
|
||||
if (wtdptr.has_value()) {
|
||||
const FakeWipeTower *wtdp = wtdptr.value();
|
||||
if (ptr1 == wtdp || ptr2 == wtdp) {
|
||||
if (ptr2 == wtdp) { std::swap(ptr1, ptr2); }
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictPrintZ, nullptr, ptr2);
|
||||
}
|
||||
}
|
||||
const PrintObject *obj1 = reinterpret_cast<const PrintObject *>(ptr1);
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>(obj1->model_object()->name, obj2->model_object()->name, conflictPrintZ, ptr1, ptr2);
|
||||
} else
|
||||
return {};
|
||||
}
|
||||
|
||||
ConflictComputeOpt ConflictChecker::line_intersect(const LineWithID &l1, const LineWithID &l2)
|
||||
{
|
||||
constexpr double SUPPORT_THRESHOLD = 100; // this large almost disables conflict check of supports
|
||||
constexpr double OTHER_THRESHOLD = 0.01;
|
||||
if (l1._id == l2._id) { return {}; } // return true if lines are from same object
|
||||
Point inter;
|
||||
bool intersect = l1._line.intersection(l2._line, &inter);
|
||||
|
||||
if (intersect) {
|
||||
double dist1 = std::min(unscale(Point(l1._line.a - inter)).norm(), unscale(Point(l1._line.b - inter)).norm());
|
||||
double dist2 = std::min(unscale(Point(l2._line.a - inter)).norm(), unscale(Point(l2._line.b - inter)).norm());
|
||||
double dist = std::min(dist1, dist2);
|
||||
ExtrusionRole r1 = l1._role;
|
||||
ExtrusionRole r2 = l2._role;
|
||||
bool both_support = r1 == ExtrusionRole::erSupportMaterial || r1 == ExtrusionRole::erSupportMaterialInterface || r1 == ExtrusionRole::erSupportTransition;
|
||||
both_support = both_support && ( r2 == ExtrusionRole::erSupportMaterial || r2 == ExtrusionRole::erSupportMaterialInterface || r2 == ExtrusionRole::erSupportTransition);
|
||||
if (dist > (both_support ? SUPPORT_THRESHOLD:OTHER_THRESHOLD)) {
|
||||
// the two lines intersects if dist>0.01mm for regular lines, and if dist>1mm for both supports
|
||||
return std::make_optional<ConflictComputeResult>(l1._id, l2._id);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
152
src/libslic3r/GCode/ConflictChecker.hpp
Normal file
152
src/libslic3r/GCode/ConflictChecker.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#ifndef slic3r_ConflictChecker_hpp_
|
||||
#define slic3r_ConflictChecker_hpp_
|
||||
|
||||
#include "../Utils.hpp"
|
||||
#include "../Model.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../Layer.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct LineWithID
|
||||
{
|
||||
Line _line;
|
||||
const void * _id;
|
||||
ExtrusionRole _role;
|
||||
|
||||
LineWithID(const Line &line, const void* id, ExtrusionRole role) : _line(line), _id(id), _role(role) {}
|
||||
};
|
||||
|
||||
using LineWithIDs = std::vector<LineWithID>;
|
||||
|
||||
struct ExtrusionLayer
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
const Layer * layer;
|
||||
float bottom_z;
|
||||
float height;
|
||||
};
|
||||
|
||||
enum class ExtrusionLayersType { INFILL, PERIMETERS, SUPPORT, WIPE_TOWER };
|
||||
|
||||
struct ExtrusionLayers : public std::vector<ExtrusionLayer>
|
||||
{
|
||||
ExtrusionLayersType type;
|
||||
};
|
||||
|
||||
struct ObjectExtrusions
|
||||
{
|
||||
ExtrusionLayers perimeters;
|
||||
ExtrusionLayers support;
|
||||
|
||||
ObjectExtrusions()
|
||||
{
|
||||
perimeters.type = ExtrusionLayersType::PERIMETERS;
|
||||
support.type = ExtrusionLayersType::SUPPORT;
|
||||
}
|
||||
};
|
||||
|
||||
class LinesBucket
|
||||
{
|
||||
public:
|
||||
float _curBottomZ = 0.0;
|
||||
unsigned _curPileIdx = 0;
|
||||
|
||||
ExtrusionLayers _piles;
|
||||
const void* _id;
|
||||
Point _offset;
|
||||
|
||||
public:
|
||||
LinesBucket(ExtrusionLayers &&paths, const void* id, Point offset) : _piles(paths), _id(id), _offset(offset) {}
|
||||
LinesBucket(LinesBucket &&) = default;
|
||||
|
||||
std::pair<int, int> curRange() const
|
||||
{
|
||||
auto begin = std::lower_bound(_piles.begin(), _piles.end(), _piles[_curPileIdx], [](const ExtrusionLayer &l, const ExtrusionLayer &r) { return l.bottom_z < r.bottom_z; });
|
||||
auto end = std::upper_bound(_piles.begin(), _piles.end(), _piles[_curPileIdx], [](const ExtrusionLayer &l, const ExtrusionLayer &r) { return l.bottom_z < r.bottom_z; });
|
||||
return std::make_pair<int, int>(std::distance(_piles.begin(), begin), std::distance(_piles.begin(), end));
|
||||
}
|
||||
bool valid() const { return _curPileIdx < _piles.size(); }
|
||||
void raise()
|
||||
{
|
||||
if (!valid()) { return; }
|
||||
auto [b, e] = curRange();
|
||||
_curPileIdx += (e - b);
|
||||
_curBottomZ = _curPileIdx == _piles.size() ? _piles.back().bottom_z : _piles[_curPileIdx].bottom_z;
|
||||
}
|
||||
float curBottomZ() const { return _curBottomZ; }
|
||||
LineWithIDs curLines() const
|
||||
{
|
||||
auto [b, e] = curRange();
|
||||
LineWithIDs lines;
|
||||
for (int i = b; i < e; ++i) {
|
||||
for (const ExtrusionPath &path : _piles[i].paths) {
|
||||
if (path.is_force_no_extrusion() == false) {
|
||||
Polyline check_polyline = path.polyline;
|
||||
check_polyline.translate(_offset);
|
||||
Lines tmpLines = check_polyline.lines();
|
||||
for (const Line &line : tmpLines) { lines.emplace_back(line, _id, path.role()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
friend bool operator>(const LinesBucket &left, const LinesBucket &right) { return left._curBottomZ > right._curBottomZ; }
|
||||
friend bool operator<(const LinesBucket &left, const LinesBucket &right) { return left._curBottomZ < right._curBottomZ; }
|
||||
friend bool operator==(const LinesBucket &left, const LinesBucket &right) { return left._curBottomZ == right._curBottomZ; }
|
||||
};
|
||||
|
||||
struct LinesBucketPtrComp
|
||||
{
|
||||
bool operator()(const LinesBucket *left, const LinesBucket *right) { return *left > *right; }
|
||||
};
|
||||
|
||||
class LinesBucketQueue
|
||||
{
|
||||
public:
|
||||
std::vector<LinesBucket> line_buckets;
|
||||
std::priority_queue<LinesBucket *, std::vector<LinesBucket *>, LinesBucketPtrComp> line_bucket_ptr_queue;
|
||||
|
||||
public:
|
||||
void emplace_back_bucket(ExtrusionLayers &&els, const void *objPtr, Point offset);
|
||||
bool valid() const { return line_bucket_ptr_queue.empty() == false; }
|
||||
float getCurrBottomZ();
|
||||
LineWithIDs getCurLines() const;
|
||||
};
|
||||
|
||||
void getExtrusionPathsFromEntity(const ExtrusionEntityCollection *entity, ExtrusionPaths &paths);
|
||||
|
||||
ExtrusionLayers getExtrusionPathsFromLayer(const LayerRegionPtrs layerRegionPtrs);
|
||||
|
||||
ExtrusionLayer getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer);
|
||||
|
||||
ObjectExtrusions getAllLayersExtrusionPathsFromObject(PrintObject *obj);
|
||||
|
||||
struct ConflictComputeResult
|
||||
{
|
||||
const void* _obj1;
|
||||
const void* _obj2;
|
||||
|
||||
ConflictComputeResult(const void* o1, const void* o2) : _obj1(o1), _obj2(o2) {}
|
||||
ConflictComputeResult() = default;
|
||||
};
|
||||
|
||||
using ConflictComputeOpt = std::optional<ConflictComputeResult>;
|
||||
|
||||
using ConflictObjName = std::optional<std::pair<std::string, std::string>>;
|
||||
|
||||
struct ConflictChecker
|
||||
{
|
||||
static ConflictResultOpt find_inter_of_lines_in_diff_objs(PrintObjectPtrs objs, std::optional<const FakeWipeTower *> wtdptr);
|
||||
static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines);
|
||||
static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2);
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
955
src/libslic3r/GCode/CoolingBuffer.cpp
Normal file
955
src/libslic3r/GCode/CoolingBuffer.cpp
Normal file
@@ -0,0 +1,955 @@
|
||||
#include "../GCode.hpp"
|
||||
#include "CoolingBuffer.hpp"
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <iostream>
|
||||
#include <float.h>
|
||||
|
||||
#if 0
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
#undef NDEBUG
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
{
|
||||
this->reset(gcodegen.writer().get_position());
|
||||
|
||||
const std::vector<Extruder> &extruders = gcodegen.writer().extruders();
|
||||
m_extruder_ids.reserve(extruders.size());
|
||||
for (const Extruder &ex : extruders) {
|
||||
m_num_extruders = std::max(ex.id() + 1, m_num_extruders);
|
||||
m_extruder_ids.emplace_back(ex.id());
|
||||
}
|
||||
}
|
||||
|
||||
void CoolingBuffer::reset(const Vec3d &position)
|
||||
{
|
||||
// QDS: add I and J axis to store center of arc
|
||||
m_current_pos.assign(7, 0.f);
|
||||
m_current_pos[0] = float(position.x());
|
||||
m_current_pos[1] = float(position.y());
|
||||
m_current_pos[2] = float(position.z());
|
||||
m_current_pos[4] = float(m_config.travel_speed.value);
|
||||
m_fan_speed = -1;
|
||||
m_additional_fan_speed = -1;
|
||||
m_current_fan_speed = -1;
|
||||
}
|
||||
|
||||
struct CoolingLine
|
||||
{
|
||||
enum Type {
|
||||
TYPE_SET_TOOL = 1 << 0,
|
||||
TYPE_EXTRUDE_END = 1 << 1,
|
||||
TYPE_OVERHANG_FAN_START = 1 << 2,
|
||||
TYPE_OVERHANG_FAN_END = 1 << 3,
|
||||
TYPE_G0 = 1 << 4,
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
//QDS: add G2 G3 type
|
||||
TYPE_G2 = 1 << 12,
|
||||
TYPE_G3 = 1 << 13,
|
||||
TYPE_FORCE_RESUME_FAN = 1 << 14,
|
||||
TYPE_SET_FAN_CHANGING_LAYER = 1 << 15,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
|
||||
type(type), line_start(line_start), line_end(line_end),
|
||||
length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
|
||||
|
||||
bool adjustable(bool slowdown_external_perimeters) const {
|
||||
return (this->type & TYPE_ADJUSTABLE) &&
|
||||
(! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
|
||||
this->time < this->time_max;
|
||||
}
|
||||
|
||||
bool adjustable() const {
|
||||
return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max;
|
||||
}
|
||||
|
||||
size_t type;
|
||||
// Start of this line at the G-code snippet.
|
||||
size_t line_start;
|
||||
// End of this line at the G-code snippet.
|
||||
size_t line_end;
|
||||
// XY Euclidian length of this segment.
|
||||
float length;
|
||||
// Current feedrate, possibly adjusted.
|
||||
float feedrate;
|
||||
// Current duration of this segment.
|
||||
float time;
|
||||
// Maximum duration of this segment.
|
||||
float time_max;
|
||||
// If marked with the "slowdown" flag, the line has been slowed down.
|
||||
bool slowdown;
|
||||
};
|
||||
|
||||
// Calculate the required per extruder time stretches.
|
||||
struct PerExtruderAdjustments
|
||||
{
|
||||
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
|
||||
float elapsed_time_total() const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the total elapsed time when slowing down
|
||||
// to the minimum extrusion feed rate defined for the current material.
|
||||
float maximum_time_after_slowdown(bool slowdown_external_perimeters) const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
if (line.time_max == FLT_MAX)
|
||||
return FLT_MAX;
|
||||
else
|
||||
time_total += line.time_max;
|
||||
} else
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the adjustable part of the total time.
|
||||
float adjustable_time(bool slowdown_external_perimeters) const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (line.adjustable(slowdown_external_perimeters))
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Calculate the non-adjustable part of the total time.
|
||||
float non_adjustable_time(bool slowdown_external_perimeters) const {
|
||||
float time_total = 0.f;
|
||||
for (const CoolingLine &line : lines)
|
||||
if (! line.adjustable(slowdown_external_perimeters))
|
||||
time_total += line.time;
|
||||
return time_total;
|
||||
}
|
||||
// Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
|
||||
// Used by both proportional and non-proportional slow down.
|
||||
float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) {
|
||||
float time_total = 0.f;
|
||||
for (CoolingLine &line : lines) {
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
|
||||
line.slowdown = true;
|
||||
line.time = line.time_max;
|
||||
line.feedrate = line.length / line.time;
|
||||
}
|
||||
time_total += line.time;
|
||||
}
|
||||
return time_total;
|
||||
}
|
||||
// Slow down each adjustable G-code line proportionally by a factor.
|
||||
// Used by the proportional slow down.
|
||||
float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
|
||||
assert(factor >= 1.f);
|
||||
float time_total = 0.f;
|
||||
for (CoolingLine &line : lines) {
|
||||
if (line.adjustable(slowdown_external_perimeters)) {
|
||||
line.slowdown = true;
|
||||
line.time = std::min(line.time_max, line.time * factor);
|
||||
line.feedrate = line.length / line.time;
|
||||
}
|
||||
time_total += line.time;
|
||||
}
|
||||
return time_total;
|
||||
}
|
||||
|
||||
// Sort the lines, adjustable first, higher feedrate first.
|
||||
// Used by non-proportional slow down.
|
||||
void sort_lines_by_decreasing_feedrate() {
|
||||
std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
|
||||
bool adj1 = l1.adjustable();
|
||||
bool adj2 = l2.adjustable();
|
||||
return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
|
||||
});
|
||||
for (n_lines_adjustable = 0;
|
||||
n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
|
||||
++ n_lines_adjustable);
|
||||
time_non_adjustable = 0.f;
|
||||
for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
|
||||
time_non_adjustable += lines[i].time;
|
||||
}
|
||||
|
||||
// Calculate the maximum time stretch when slowing down to min_feedrate.
|
||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||
// Used by non-proportional slow down.
|
||||
float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const {
|
||||
float time_stretch = 0.f;
|
||||
assert(this->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
|
||||
const CoolingLine &line = lines[i];
|
||||
if (line.feedrate > min_feedrate)
|
||||
time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
|
||||
}
|
||||
return time_stretch;
|
||||
}
|
||||
|
||||
// Slow down all adjustable lines down to min_feedrate.
|
||||
// Slowdown to min_feedrate shall be allowed for this extruder's material.
|
||||
// Used by non-proportional slow down.
|
||||
void slow_down_to_feedrate(float min_feedrate) {
|
||||
assert(this->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < n_lines_adjustable; ++ i) {
|
||||
CoolingLine &line = lines[i];
|
||||
if (line.feedrate > min_feedrate) {
|
||||
line.time *= std::max(1.f, line.feedrate / min_feedrate);
|
||||
line.feedrate = min_feedrate;
|
||||
line.slowdown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extruder, for which the G-code will be adjusted.
|
||||
unsigned int extruder_id = 0;
|
||||
// Is the cooling slow down logic enabled for this extruder's material?
|
||||
bool cooling_slow_down_enabled = false;
|
||||
// Slow down the print down to slow_down_min_speed if the total layer time is below slow_down_layer_time.
|
||||
float slow_down_layer_time = 0.f;
|
||||
// Minimum print speed allowed for this extruder.
|
||||
float slow_down_min_speed = 0.f;
|
||||
//w14
|
||||
bool dont_slow_down_outer_wall = false;
|
||||
|
||||
// Parsed lines.
|
||||
std::vector<CoolingLine> lines;
|
||||
// The following two values are set by sort_lines_by_decreasing_feedrate():
|
||||
// Number of adjustable lines, at the start of lines.
|
||||
size_t n_lines_adjustable = 0;
|
||||
// Non-adjustable time of lines starting with n_lines_adjustable.
|
||||
float time_non_adjustable = 0;
|
||||
// Current total time for this extruder.
|
||||
float time_total = 0;
|
||||
// Maximum time for this extruder, when the maximum slow down is applied.
|
||||
float time_maximum = 0;
|
||||
|
||||
// Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
|
||||
size_t idx_line_begin = 0;
|
||||
size_t idx_line_end = 0;
|
||||
};
|
||||
|
||||
// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate.
|
||||
// Used by non-proportional slow down.
|
||||
float new_feedrate_to_reach_time_stretch(
|
||||
std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end,
|
||||
float min_feedrate, float time_stretch, size_t max_iter = 20)
|
||||
{
|
||||
float new_feedrate = min_feedrate;
|
||||
for (size_t iter = 0; iter < max_iter; ++ iter) {
|
||||
double nomin = 0;
|
||||
double denom = time_stretch;
|
||||
for (auto it = it_begin; it != it_end; ++ it) {
|
||||
assert((*it)->slow_down_min_speed < min_feedrate + EPSILON);
|
||||
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
|
||||
const CoolingLine &line = (*it)->lines[i];
|
||||
if (line.feedrate > min_feedrate) {
|
||||
nomin += (double)line.time * (double)line.feedrate;
|
||||
denom += (double)line.time;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(denom > 0);
|
||||
if (denom < 0)
|
||||
return min_feedrate;
|
||||
new_feedrate = (float)(nomin / denom);
|
||||
assert(new_feedrate > min_feedrate - EPSILON);
|
||||
if (new_feedrate < min_feedrate + EPSILON)
|
||||
goto finished;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
|
||||
const CoolingLine &line = (*it)->lines[i];
|
||||
if (line.feedrate > min_feedrate && line.feedrate < new_feedrate)
|
||||
// Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate,
|
||||
// which makes the new_feedrate lower than it should be.
|
||||
// Re-run the calculation with a new min_feedrate limit, so that the segments with current feedrate lower than new_feedrate
|
||||
// are not taken into account.
|
||||
goto not_finished_yet;
|
||||
}
|
||||
goto finished;
|
||||
not_finished_yet:
|
||||
min_feedrate = new_feedrate;
|
||||
}
|
||||
// Failed to find the new feedrate for the time_stretch.
|
||||
|
||||
finished:
|
||||
// Test whether the time_stretch was achieved.
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
float time_stretch_final = 0.f;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate);
|
||||
assert(std::abs(time_stretch - time_stretch_final) < EPSILON);
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
return new_feedrate;
|
||||
}
|
||||
|
||||
std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, bool flush)
|
||||
{
|
||||
// Cache the input G-code.
|
||||
if (m_gcode.empty())
|
||||
m_gcode = std::move(gcode);
|
||||
else
|
||||
m_gcode += gcode;
|
||||
|
||||
std::string out;
|
||||
if (flush) {
|
||||
// This is either an object layer or the very last print layer. Calculate cool down over the collected support layers
|
||||
// and one object layer.
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos);
|
||||
float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
|
||||
out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
|
||||
m_gcode.clear();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Parse the layer G-code for the moves, which could be adjusted.
|
||||
// Return the list of parsed lines, bucketed by an extruder.
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const
|
||||
{
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
|
||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
|
||||
for (size_t i = 0; i < m_extruder_ids.size(); ++ i) {
|
||||
PerExtruderAdjustments &adj = per_extruder_adjustments[i];
|
||||
unsigned int extruder_id = m_extruder_ids[i];
|
||||
adj.extruder_id = extruder_id;
|
||||
adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id);
|
||||
adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id));
|
||||
adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id));
|
||||
//w14
|
||||
adj.dont_slow_down_outer_wall = m_config.dont_slow_down_outer_wall.get_at(extruder_id);
|
||||
map_extruder_to_per_extruder_adjustment[extruder_id] = i;
|
||||
}
|
||||
|
||||
unsigned int current_extruder = m_current_extruder;
|
||||
PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
|
||||
const char *line_start = gcode.c_str();
|
||||
const char *line_end = line_start;
|
||||
// Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
|
||||
// for a sequence of extrusion moves.
|
||||
size_t active_speed_modifier = size_t(-1);
|
||||
|
||||
for (; *line_start != 0; line_start = line_end)
|
||||
{
|
||||
while (*line_end != '\n' && *line_end != 0)
|
||||
++ line_end;
|
||||
// sline will not contain the trailing '\n'.
|
||||
std::string sline(line_start, line_end);
|
||||
// CoolingLine will contain the trailing '\n'.
|
||||
if (*line_end == '\n')
|
||||
++ line_end;
|
||||
CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
|
||||
if (boost::starts_with(sline, "G0 "))
|
||||
line.type = CoolingLine::TYPE_G0;
|
||||
else if (boost::starts_with(sline, "G1 "))
|
||||
line.type = CoolingLine::TYPE_G1;
|
||||
else if (boost::starts_with(sline, "G92 "))
|
||||
line.type = CoolingLine::TYPE_G92;
|
||||
else if (boost::starts_with(sline, "G2 "))
|
||||
line.type = CoolingLine::TYPE_G2;
|
||||
else if (boost::starts_with(sline, "G3 "))
|
||||
line.type = CoolingLine::TYPE_G3;
|
||||
if (line.type) {
|
||||
// G0, G1 or G92
|
||||
// Parse the G-code line.
|
||||
std::vector<float> new_pos(current_pos);
|
||||
const char *c = sline.data() + 3;
|
||||
for (;;) {
|
||||
// Skip whitespaces.
|
||||
for (; *c == ' ' || *c == '\t'; ++ c);
|
||||
if (*c == 0 || *c == ';')
|
||||
break;
|
||||
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
//QDS: Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == 'E') ? 3 : (*c == 'F') ? 4 :
|
||||
(*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
new_pos[axis] = float(atof(++c));
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0)
|
||||
// This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
|
||||
line.type |= CoolingLine::TYPE_HAS_F;
|
||||
} else if (axis == 5 || axis == 6) {
|
||||
// QDS: get position of arc center
|
||||
new_pos[axis] += current_pos[axis - 5];
|
||||
}
|
||||
}
|
||||
// Skip this word.
|
||||
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
|
||||
}
|
||||
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||
bool wipe = boost::contains(sline, ";_WIPE");
|
||||
if (external_perimeter)
|
||||
line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
|
||||
if (wipe)
|
||||
line.type |= CoolingLine::TYPE_WIPE;
|
||||
//w14
|
||||
bool adjust_external = true;
|
||||
if (adjustment->dont_slow_down_outer_wall && external_perimeter) adjust_external = false;
|
||||
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && !wipe && adjust_external) {
|
||||
line.type |= CoolingLine::TYPE_ADJUSTABLE;
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
//QDS: G0, G1, G2, G3. Calculate the duration.
|
||||
if (m_config.use_relative_e_distances.value)
|
||||
// Reset extruder accumulator.
|
||||
current_pos[3] = 0.f;
|
||||
float dif[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
dif[i] = new_pos[i] - current_pos[i];
|
||||
float dxy2 = 0;
|
||||
//QDS: support to calculate length of arc
|
||||
if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) {
|
||||
Vec3f start(current_pos[0], current_pos[1], 0);
|
||||
Vec3f end(new_pos[0], new_pos[1], 0);
|
||||
Vec3f center(new_pos[5], new_pos[6], 0);
|
||||
bool is_ccw = line.type & CoolingLine::TYPE_G3;
|
||||
float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw);
|
||||
dxy2 = dxy * dxy;
|
||||
} else {
|
||||
dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||
}
|
||||
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||
if (dxyz2 > 0.f) {
|
||||
// Movement in xyz, calculate time from the xyz Euclidian distance.
|
||||
line.length = sqrt(dxyz2);
|
||||
} else if (std::abs(dif[3]) > 0.f) {
|
||||
// Movement in the extruder axis.
|
||||
line.length = std::abs(dif[3]);
|
||||
}
|
||||
line.feedrate = new_pos[4];
|
||||
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
|
||||
if (line.length > 0)
|
||||
line.time = line.length / line.feedrate;
|
||||
line.time_max = line.time;
|
||||
if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
|
||||
line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed);
|
||||
// QDS: add G2 and G3 support
|
||||
if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) ||
|
||||
(line.type & CoolingLine::TYPE_G2) ||
|
||||
(line.type & CoolingLine::TYPE_G3))) {
|
||||
// Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
|
||||
assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
|
||||
CoolingLine &sm = adjustment->lines[active_speed_modifier];
|
||||
assert(sm.feedrate > 0.f);
|
||||
sm.length += line.length;
|
||||
sm.time += line.time;
|
||||
if (sm.time_max != FLT_MAX) {
|
||||
if (line.time_max == FLT_MAX)
|
||||
sm.time_max = FLT_MAX;
|
||||
else
|
||||
sm.time_max += line.time_max;
|
||||
}
|
||||
// Don't store this line.
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||
line.type = CoolingLine::TYPE_EXTRUDE_END;
|
||||
active_speed_modifier = size_t(-1);
|
||||
} else if (boost::starts_with(sline, m_toolchange_prefix)) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size());
|
||||
// Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored.
|
||||
if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) {
|
||||
if (new_extruder != current_extruder) {
|
||||
// Switch the tool.
|
||||
line.type = CoolingLine::TYPE_SET_TOOL;
|
||||
current_extruder = new_extruder;
|
||||
adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway.
|
||||
if (map_extruder_to_per_extruder_adjustment.size() > 1)
|
||||
BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline;
|
||||
}
|
||||
|
||||
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) {
|
||||
line.type = CoolingLine::TYPE_OVERHANG_FAN_START;
|
||||
} else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) {
|
||||
line.type = CoolingLine::TYPE_OVERHANG_FAN_END;
|
||||
} else if (boost::starts_with(sline, "G4 ")) {
|
||||
// Parse the wait time.
|
||||
line.type = CoolingLine::TYPE_G4;
|
||||
size_t pos_S = sline.find('S', 3);
|
||||
size_t pos_P = sline.find('P', 3);
|
||||
assert(is_decimal_separator_point()); // for atof
|
||||
line.time = line.time_max = float(
|
||||
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
|
||||
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
|
||||
} else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_FORCE_RESUME_FAN;
|
||||
} else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) {
|
||||
line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER;
|
||||
}
|
||||
if (line.type != 0)
|
||||
adjustment->lines.emplace_back(std::move(line));
|
||||
}
|
||||
|
||||
return per_extruder_adjustments;
|
||||
}
|
||||
|
||||
// Slow down an extruder range proportionally down to slow_down_layer_time.
|
||||
// Return the total time for the complete layer.
|
||||
static inline float extruder_range_slow_down_proportional(
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_begin,
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_end,
|
||||
// Elapsed time for the extruders already processed.
|
||||
float elapsed_time_total0,
|
||||
// Initial total elapsed time before slow down.
|
||||
float elapsed_time_before_slowdown,
|
||||
// Target time for the complete layer (all extruders applied).
|
||||
float slow_down_layer_time)
|
||||
{
|
||||
// Total layer time after the slow down has been applied.
|
||||
float total_after_slowdown = elapsed_time_before_slowdown;
|
||||
// Now decide, whether the external perimeters shall be slowed down as well.
|
||||
float max_time_nep = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
max_time_nep += (*it)->maximum_time_after_slowdown(false);
|
||||
if (max_time_nep > slow_down_layer_time) {
|
||||
// It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
|
||||
// Slow down the non-external perimeters proportionally.
|
||||
float non_adjustable_time = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
non_adjustable_time += (*it)->non_adjustable_time(false);
|
||||
// The following step is a linear programming task due to the minimum movement speeds of the print moves.
|
||||
// Run maximum 5 iterations until a good enough approximation is reached.
|
||||
for (size_t iter = 0; iter < 5; ++ iter) {
|
||||
float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
|
||||
assert(factor > 1.f);
|
||||
total_after_slowdown = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
total_after_slowdown += (*it)->slow_down_proportional(factor, false);
|
||||
if (total_after_slowdown > 0.95f * slow_down_layer_time)
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Slow down everything. First slow down the non-external perimeters to maximum.
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
(*it)->slowdown_to_minimum_feedrate(false);
|
||||
// Slow down the external perimeters proportionally.
|
||||
float non_adjustable_time = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
non_adjustable_time += (*it)->non_adjustable_time(true);
|
||||
for (size_t iter = 0; iter < 5; ++ iter) {
|
||||
float factor = (slow_down_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
|
||||
assert(factor > 1.f);
|
||||
total_after_slowdown = elapsed_time_total0;
|
||||
for (auto it = it_begin; it != it_end; ++ it)
|
||||
total_after_slowdown += (*it)->slow_down_proportional(factor, true);
|
||||
if (total_after_slowdown > 0.95f * slow_down_layer_time)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return total_after_slowdown;
|
||||
}
|
||||
|
||||
// Slow down an extruder range to slow_down_layer_time.
|
||||
// Return the total time for the complete layer.
|
||||
static inline void extruder_range_slow_down_non_proportional(
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_begin,
|
||||
std::vector<PerExtruderAdjustments*>::iterator it_end,
|
||||
float time_stretch)
|
||||
{
|
||||
// Slow down. Try to equalize the feedrates.
|
||||
std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
|
||||
// Find the next highest adjustable feedrate among the extruders.
|
||||
float feedrate = 0;
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||
adj->idx_line_begin = 0;
|
||||
adj->idx_line_end = 0;
|
||||
assert(adj->idx_line_begin < adj->n_lines_adjustable);
|
||||
if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
|
||||
feedrate = adj->lines[adj->idx_line_begin].feedrate;
|
||||
}
|
||||
assert(feedrate > 0.f);
|
||||
// Sort by slow_down_min_speed, maximum speed first.
|
||||
std::sort(by_min_print_speed.begin(), by_min_print_speed.end(),
|
||||
[](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->slow_down_min_speed > p2->slow_down_min_speed; });
|
||||
// Slow down, fast moves first.
|
||||
for (;;) {
|
||||
// For each extruder, find the span of lines with a feedrate close to feedrate.
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||
for (adj->idx_line_end = adj->idx_line_begin;
|
||||
adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
|
||||
++ adj->idx_line_end) ;
|
||||
}
|
||||
// Find the next highest adjustable feedrate among the extruders.
|
||||
float feedrate_next = 0.f;
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed)
|
||||
if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
|
||||
feedrate_next = adj->lines[adj->idx_line_end].feedrate;
|
||||
// Slow down, limited by max(feedrate_next, slow_down_min_speed).
|
||||
for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
|
||||
// Slow down at most by time_stretch.
|
||||
if ((*adj)->slow_down_min_speed == 0.f) {
|
||||
// All the adjustable speeds are now lowered to the same speed,
|
||||
// and the minimum speed is set to zero.
|
||||
float time_adjustable = 0.f;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
time_adjustable += (*it)->adjustable_time(true);
|
||||
float rate = (time_adjustable + time_stretch) / time_adjustable;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
(*it)->slow_down_proportional(rate, true);
|
||||
return;
|
||||
} else {
|
||||
float feedrate_limit = std::max(feedrate_next, (*adj)->slow_down_min_speed);
|
||||
bool done = false;
|
||||
float time_stretch_max = 0.f;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
|
||||
if (time_stretch_max >= time_stretch) {
|
||||
feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20);
|
||||
done = true;
|
||||
} else
|
||||
time_stretch -= time_stretch_max;
|
||||
for (auto it = adj; it != by_min_print_speed.end(); ++ it)
|
||||
(*it)->slow_down_to_feedrate(feedrate_limit);
|
||||
if (done)
|
||||
return;
|
||||
}
|
||||
// Skip the other extruders with nearly the same slow_down_min_speed, as they have been processed already.
|
||||
auto next = adj;
|
||||
for (++ next; next != by_min_print_speed.end() && (*next)->slow_down_min_speed > (*adj)->slow_down_min_speed - EPSILON; ++ next);
|
||||
adj = next;
|
||||
}
|
||||
if (feedrate_next == 0.f)
|
||||
// There are no other extrusions available for slow down.
|
||||
break;
|
||||
for (PerExtruderAdjustments *adj : by_min_print_speed) {
|
||||
adj->idx_line_begin = adj->idx_line_end;
|
||||
feedrate = feedrate_next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate slow down for all the extruders.
|
||||
float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
// Sort the extruders by an increasing slow_down_layer_time.
|
||||
// The layers with a lower slow_down_layer_time are slowed down
|
||||
// together with all the other layers with slow_down_layer_time above.
|
||||
std::vector<PerExtruderAdjustments*> by_slowdown_time;
|
||||
by_slowdown_time.reserve(per_extruder_adjustments.size());
|
||||
// Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
|
||||
// Collect total print time of non-adjustable extruders.
|
||||
float elapsed_time_total0 = 0.f;
|
||||
for (PerExtruderAdjustments &adj : per_extruder_adjustments) {
|
||||
// Curren total time for this extruder.
|
||||
adj.time_total = adj.elapsed_time_total();
|
||||
// Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed.
|
||||
adj.time_maximum = adj.maximum_time_after_slowdown(true);
|
||||
if (adj.cooling_slow_down_enabled && adj.lines.size() > 0) {
|
||||
by_slowdown_time.emplace_back(&adj);
|
||||
if (! m_cooling_logic_proportional)
|
||||
// sorts the lines, also sets adj.time_non_adjustable
|
||||
adj.sort_lines_by_decreasing_feedrate();
|
||||
} else
|
||||
elapsed_time_total0 += adj.elapsed_time_total();
|
||||
}
|
||||
std::sort(by_slowdown_time.begin(), by_slowdown_time.end(),
|
||||
[](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2)
|
||||
{ return adj1->slow_down_layer_time < adj2->slow_down_layer_time; });
|
||||
|
||||
for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) {
|
||||
PerExtruderAdjustments &adj = *(*cur_begin);
|
||||
// Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
|
||||
float total = elapsed_time_total0;
|
||||
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
|
||||
total += (*it)->time_total;
|
||||
float slow_down_layer_time = adj.slow_down_layer_time * 1.001f;
|
||||
if (total > slow_down_layer_time) {
|
||||
// The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
|
||||
} else {
|
||||
// Adjust this and all the following (higher m_config.slow_down_layer_time) extruders.
|
||||
// Sum maximum slow down time as if everything was slowed down including the external perimeters.
|
||||
float max_time = elapsed_time_total0;
|
||||
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
|
||||
max_time += (*it)->time_maximum;
|
||||
if (max_time > slow_down_layer_time) {
|
||||
if (m_cooling_logic_proportional)
|
||||
extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slow_down_layer_time);
|
||||
else
|
||||
extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slow_down_layer_time - total);
|
||||
} else {
|
||||
// Slow down to maximum possible.
|
||||
for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
|
||||
(*it)->slowdown_to_minimum_feedrate(true);
|
||||
}
|
||||
}
|
||||
elapsed_time_total0 += adj.elapsed_time_total();
|
||||
}
|
||||
|
||||
return elapsed_time_total0;
|
||||
}
|
||||
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string CoolingBuffer::apply_layer_cooldown(
|
||||
// Source G-code for the current layer.
|
||||
const std::string &gcode,
|
||||
// ID of the current layer, used to disable fan for the first n layers.
|
||||
size_t layer_id,
|
||||
// Total time of this layer after slow down, used to control the fan.
|
||||
float layer_time,
|
||||
// Per extruder list of G-code lines and their cool down attributes.
|
||||
std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
|
||||
{
|
||||
// First sort the adjustment lines by of multiple extruders by their position in the source G-code.
|
||||
std::vector<const CoolingLine*> lines;
|
||||
{
|
||||
size_t n_lines = 0;
|
||||
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
|
||||
n_lines += adj.lines.size();
|
||||
lines.reserve(n_lines);
|
||||
for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
|
||||
for (const CoolingLine &line : adj.lines)
|
||||
lines.emplace_back(&line);
|
||||
std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
|
||||
}
|
||||
// Second generate the adjusted G-code.
|
||||
std::string new_gcode;
|
||||
new_gcode.reserve(gcode.size() * 2);
|
||||
bool overhang_fan_control= false;
|
||||
int overhang_fan_speed = 0;
|
||||
|
||||
enum class SetFanType {
|
||||
sfChangingLayer = 0,
|
||||
sfChangingFilament,
|
||||
sfImmediatelyApply
|
||||
};
|
||||
|
||||
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed](SetFanType type) {
|
||||
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
|
||||
int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed);
|
||||
int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0;
|
||||
//QDS
|
||||
//w13
|
||||
int additional_fan_speed_new;
|
||||
if (m_config.seal)
|
||||
additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed);
|
||||
else
|
||||
additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed_unseal);
|
||||
//int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed);
|
||||
|
||||
int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers);
|
||||
// Is the fan speed ramp enabled?
|
||||
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
|
||||
if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) {
|
||||
// When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero,
|
||||
// so there will be a zero fan speed at least at the 1st layer.
|
||||
close_fan_the_first_x_layers = 1;
|
||||
}
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers) {
|
||||
int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed);
|
||||
float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time));
|
||||
float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time));
|
||||
//QDS: always enable the fan speed interpolation according to layer time
|
||||
//if (EXTRUDER_CONFIG(cooling)) {
|
||||
if (layer_time < slow_down_layer_time) {
|
||||
// Layer time very short. Enable the fan to a full throttle.
|
||||
fan_speed_new = fan_max_speed;
|
||||
} else if (layer_time < fan_cooling_layer_time) {
|
||||
// Layer time quite short. Enable the fan proportionally according to the current layer time.
|
||||
assert(layer_time >= slow_down_layer_time);
|
||||
double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time);
|
||||
fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5);
|
||||
}
|
||||
//}
|
||||
overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed);
|
||||
if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) {
|
||||
// Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer.
|
||||
float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers);
|
||||
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
|
||||
overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255);
|
||||
}
|
||||
#undef EXTRUDER_CONFIG
|
||||
overhang_fan_control= overhang_fan_speed > fan_speed_new;
|
||||
} else {
|
||||
overhang_fan_control= false;
|
||||
overhang_fan_speed = 0;
|
||||
fan_speed_new = 0;
|
||||
additional_fan_speed_new = 0;
|
||||
}
|
||||
if (fan_speed_new != m_fan_speed) {
|
||||
m_fan_speed = fan_speed_new;
|
||||
//QDS
|
||||
m_current_fan_speed = fan_speed_new;
|
||||
if (type == SetFanType::sfImmediatelyApply)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
else if (type == SetFanType::sfChangingLayer)
|
||||
this->m_set_fan_changing_layer = true;
|
||||
//QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished
|
||||
}
|
||||
//QDS
|
||||
if (additional_fan_speed_new != m_additional_fan_speed) {
|
||||
m_additional_fan_speed = additional_fan_speed_new;
|
||||
if (type == SetFanType::sfImmediatelyApply)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
else if (type == SetFanType::sfChangingLayer)
|
||||
this->m_set_addition_fan_changing_layer = true;
|
||||
//QDS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished
|
||||
}
|
||||
};
|
||||
|
||||
const char *pos = gcode.c_str();
|
||||
int current_feedrate = 0;
|
||||
//QDS
|
||||
m_set_fan_changing_layer = false;
|
||||
m_set_addition_fan_changing_layer = false;
|
||||
change_extruder_set_fan(SetFanType::sfChangingLayer);
|
||||
for (const CoolingLine *line : lines) {
|
||||
const char *line_start = gcode.c_str() + line->line_start;
|
||||
const char *line_end = gcode.c_str() + line->line_end;
|
||||
if (line_start > pos)
|
||||
new_gcode.append(pos, line_start - pos);
|
||||
if (line->type & CoolingLine::TYPE_SET_TOOL) {
|
||||
unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size());
|
||||
if (new_extruder != m_current_extruder) {
|
||||
m_current_extruder = new_extruder;
|
||||
change_extruder_set_fan(SetFanType::sfChangingFilament); //QDS: will force to resume fan speed when filament change is finished
|
||||
}
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) {
|
||||
if (overhang_fan_control) {
|
||||
//QDS
|
||||
m_current_fan_speed = overhang_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed);
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) {
|
||||
if (overhang_fan_control) {
|
||||
//QDS
|
||||
m_current_fan_speed = m_fan_speed;
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed);
|
||||
}
|
||||
} else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) {
|
||||
//QDS: force to write a fan speed command again
|
||||
if (m_current_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
if (m_additional_fan_speed != -1)
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
} else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) {
|
||||
//QDS: check whether fan speed need to changed when change layer
|
||||
if (m_current_fan_speed != -1 && m_set_fan_changing_layer) {
|
||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed);
|
||||
m_set_fan_changing_layer = false;
|
||||
}
|
||||
if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) {
|
||||
new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed);
|
||||
m_set_addition_fan_changing_layer = false;
|
||||
}
|
||||
}
|
||||
else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
|
||||
// Just remove this comment.
|
||||
} else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
|
||||
// Find the start of a comment, or roll to the end of line.
|
||||
const char *end = line_start;
|
||||
for (; end < line_end && *end != ';'; ++ end);
|
||||
// Find the 'F' word.
|
||||
const char *fpos = strstr(line_start + 2, " F") + 2;
|
||||
int new_feedrate = current_feedrate;
|
||||
// Modify the F word of the current G-code line.
|
||||
bool modify = false;
|
||||
// Remove the F word from the current G-code line.
|
||||
bool remove = false;
|
||||
assert(fpos != nullptr);
|
||||
new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos);
|
||||
if (new_feedrate == current_feedrate) {
|
||||
// No need to change the F value.
|
||||
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
|
||||
// Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
|
||||
end = line_end;
|
||||
else
|
||||
// Remove the feedrate from the G0/G1 line. The G-code line may become empty!
|
||||
remove = true;
|
||||
} else if (line->slowdown) {
|
||||
// The F value will be overwritten.
|
||||
modify = true;
|
||||
} else {
|
||||
// The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified.
|
||||
// Emit the line without the comment.
|
||||
new_gcode.append(line_start, end - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
}
|
||||
if (modify || remove) {
|
||||
if (modify) {
|
||||
// Replace the feedrate.
|
||||
new_gcode.append(line_start, fpos - line_start);
|
||||
current_feedrate = new_feedrate;
|
||||
char buf[64];
|
||||
sprintf(buf, "%d", int(current_feedrate));
|
||||
new_gcode += buf;
|
||||
} else {
|
||||
// Remove the feedrate word.
|
||||
const char *f = fpos;
|
||||
// Roll the pointer before the 'F' word.
|
||||
for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f);
|
||||
|
||||
if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) {
|
||||
// QDS: only remain "G1" or "G0" of this line after remove 'F' part, don't save
|
||||
} else {
|
||||
// Append up to the F word, without the trailing whitespace.
|
||||
new_gcode.append(line_start, f - line_start + 1);
|
||||
}
|
||||
}
|
||||
// Skip the non-whitespaces of the F parameter up the comment or end of line.
|
||||
for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos);
|
||||
// Append the rest of the line without the comment.
|
||||
if (fpos < end)
|
||||
// The G-code line is not empty yet. Emit the rest of it.
|
||||
new_gcode.append(fpos, end - fpos);
|
||||
else if (remove && new_gcode == "G1") {
|
||||
// The G-code line only contained the F word, now it is empty. Remove it completely including the comments.
|
||||
new_gcode.resize(new_gcode.size() - 2);
|
||||
end = line_end;
|
||||
}
|
||||
}
|
||||
// Process the rest of the line.
|
||||
if (end < line_end) {
|
||||
if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
|
||||
// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
|
||||
std::string comment(end, line_end);
|
||||
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
|
||||
if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
|
||||
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
|
||||
if (line->type & CoolingLine::TYPE_WIPE)
|
||||
boost::replace_all(comment, ";_WIPE", "");
|
||||
new_gcode += comment;
|
||||
} else {
|
||||
// Just attach the rest of the source line.
|
||||
new_gcode.append(end, line_end - end);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_gcode.append(line_start, line_end - line_start);
|
||||
}
|
||||
pos = line_end;
|
||||
}
|
||||
const char *gcode_end = gcode.c_str() + gcode.size();
|
||||
if (pos < gcode_end)
|
||||
new_gcode.append(pos, gcode_end - pos);
|
||||
|
||||
return new_gcode;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
69
src/libslic3r/GCode/CoolingBuffer.hpp
Normal file
69
src/libslic3r/GCode/CoolingBuffer.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef slic3r_CoolingBuffer_hpp_
|
||||
#define slic3r_CoolingBuffer_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
// A standalone G-code filter, to control cooling of the print.
|
||||
// The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited
|
||||
// and the print is modified to stretch over a minimum layer time.
|
||||
//
|
||||
// The simple it sounds, the actual implementation is significantly more complex.
|
||||
// Namely, for a multi-extruder print, each material may require a different cooling logic.
|
||||
// For example, some materials may not like to print too slowly, while with some materials
|
||||
// we may slow down significantly.
|
||||
//
|
||||
class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
void reset(const Vec3d &position);
|
||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||
std::string process_layer(std::string &&gcode, size_t layer_id, bool flush);
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// QDS: X,Y,Z,E,F,I,J
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
int m_additional_fan_speed;
|
||||
// Cached from GCodeWriter.
|
||||
// Printing extruder IDs, zero based.
|
||||
std::vector<unsigned int> m_extruder_ids;
|
||||
// Highest of m_extruder_ids plus 1.
|
||||
unsigned int m_num_extruders { 0 };
|
||||
const std::string m_toolchange_prefix;
|
||||
// Referencs GCode::m_config, which is FullPrintConfig. While the PrintObjectConfig slice of FullPrintConfig is being modified,
|
||||
// the PrintConfig slice of FullPrintConfig is constant, thus no thread synchronization is required.
|
||||
const PrintConfig &m_config;
|
||||
unsigned int m_current_extruder;
|
||||
|
||||
// Old logic: proportional.
|
||||
bool m_cooling_logic_proportional = false;
|
||||
//QDS: current fan speed
|
||||
int m_current_fan_speed;
|
||||
//QDS:
|
||||
bool m_set_fan_changing_layer = false;
|
||||
bool m_set_addition_fan_changing_layer = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
4523
src/libslic3r/GCode/GCodeProcessor.cpp
Normal file
4523
src/libslic3r/GCode/GCodeProcessor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
960
src/libslic3r/GCode/GCodeProcessor.hpp
Normal file
960
src/libslic3r/GCode/GCodeProcessor.hpp
Normal file
@@ -0,0 +1,960 @@
|
||||
#ifndef slic3r_GCodeProcessor_hpp_
|
||||
#define slic3r_GCodeProcessor_hpp_
|
||||
|
||||
#include "libslic3r/GCodeReader.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// slice warnings enum strings
|
||||
#define NOZZLE_HRC_CHECKER "the_actual_nozzle_hrc_smaller_than_the_required_nozzle_hrc"
|
||||
#define BED_TEMP_TOO_HIGH_THAN_FILAMENT "bed_temperature_too_high_than_filament"
|
||||
#define NOT_SUPPORT_TRADITIONAL_TIMELAPSE "not_support_traditional_timelapse"
|
||||
#define NOT_GENERATE_TIMELAPSE "not_generate_timelapse"
|
||||
#define LONG_RETRACTION_WHEN_CUT "activate_long_retraction_when_cut"
|
||||
|
||||
enum class EMoveType : unsigned char
|
||||
{
|
||||
Noop,
|
||||
Retract,
|
||||
Unretract,
|
||||
Seam,
|
||||
Tool_change,
|
||||
Color_change,
|
||||
Pause_Print,
|
||||
Custom_GCode,
|
||||
Travel,
|
||||
Wipe,
|
||||
Extrude,
|
||||
Count
|
||||
};
|
||||
|
||||
struct PrintEstimatedStatistics
|
||||
{
|
||||
enum class ETimeMode : unsigned char
|
||||
{
|
||||
Normal,
|
||||
Stealth,
|
||||
Count
|
||||
};
|
||||
|
||||
struct Mode
|
||||
{
|
||||
float time;
|
||||
float prepare_time;
|
||||
std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> custom_gcode_times;
|
||||
std::vector<std::pair<EMoveType, float>> moves_times;
|
||||
std::vector<std::pair<ExtrusionRole, float>> roles_times;
|
||||
std::vector<float> layers_times;
|
||||
|
||||
void reset() {
|
||||
time = 0.0f;
|
||||
prepare_time = 0.0f;
|
||||
custom_gcode_times.clear();
|
||||
custom_gcode_times.shrink_to_fit();
|
||||
moves_times.clear();
|
||||
moves_times.shrink_to_fit();
|
||||
roles_times.clear();
|
||||
roles_times.shrink_to_fit();
|
||||
layers_times.clear();
|
||||
layers_times.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<double> volumes_per_color_change;
|
||||
std::map<size_t, double> model_volumes_per_extruder;
|
||||
std::map<size_t, double> wipe_tower_volumes_per_extruder;
|
||||
std::map<size_t, double> support_volumes_per_extruder;
|
||||
std::map<size_t, double> total_volumes_per_extruder;
|
||||
//QDS: the flush amount of every filament
|
||||
std::map<size_t, double> flush_per_filament;
|
||||
std::map<ExtrusionRole, std::pair<double, double>> used_filaments_per_role;
|
||||
|
||||
std::array<Mode, static_cast<size_t>(ETimeMode::Count)> modes;
|
||||
unsigned int total_filamentchanges;
|
||||
|
||||
PrintEstimatedStatistics() { reset(); }
|
||||
|
||||
void reset() {
|
||||
for (auto m : modes) {
|
||||
m.reset();
|
||||
}
|
||||
volumes_per_color_change.clear();
|
||||
volumes_per_color_change.shrink_to_fit();
|
||||
wipe_tower_volumes_per_extruder.clear();
|
||||
model_volumes_per_extruder.clear();
|
||||
support_volumes_per_extruder.clear();
|
||||
total_volumes_per_extruder.clear();
|
||||
flush_per_filament.clear();
|
||||
used_filaments_per_role.clear();
|
||||
total_filamentchanges = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct ConflictResult
|
||||
{
|
||||
std::string _objName1;
|
||||
std::string _objName2;
|
||||
float _height;
|
||||
const void *_obj1; // nullptr means wipe tower
|
||||
const void *_obj2;
|
||||
int layer = -1;
|
||||
ConflictResult(const std::string &objName1, const std::string &objName2, float height, const void *obj1, const void *obj2)
|
||||
: _objName1(objName1), _objName2(objName2), _height(height), _obj1(obj1), _obj2(obj2)
|
||||
{}
|
||||
ConflictResult() = default;
|
||||
};
|
||||
|
||||
struct BedMatchResult
|
||||
{
|
||||
bool match;
|
||||
std::string bed_type_name;
|
||||
int extruder_id;
|
||||
BedMatchResult():match(true),bed_type_name(""),extruder_id(-1) {}
|
||||
BedMatchResult(bool _match,const std::string& _bed_type_name="",int _extruder_id=-1)
|
||||
:match(_match),bed_type_name(_bed_type_name),extruder_id(_extruder_id)
|
||||
{}
|
||||
};
|
||||
|
||||
using ConflictResultOpt = std::optional<ConflictResult>;
|
||||
|
||||
struct GCodeProcessorResult
|
||||
{
|
||||
ConflictResultOpt conflict_result;
|
||||
BedMatchResult bed_match_result;
|
||||
|
||||
struct SettingsIds
|
||||
{
|
||||
std::string print;
|
||||
std::vector<std::string> filament;
|
||||
std::string printer;
|
||||
|
||||
void reset() {
|
||||
print.clear();
|
||||
filament.clear();
|
||||
printer.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct MoveVertex
|
||||
{
|
||||
unsigned int gcode_id{ 0 };
|
||||
EMoveType type{ EMoveType::Noop };
|
||||
ExtrusionRole extrusion_role{ erNone };
|
||||
unsigned char extruder_id{ 0 };
|
||||
unsigned char cp_color_id{ 0 };
|
||||
Vec3f position{ Vec3f::Zero() }; // mm
|
||||
float delta_extruder{ 0.0f }; // mm
|
||||
float feedrate{ 0.0f }; // mm/s
|
||||
float width{ 0.0f }; // mm
|
||||
float height{ 0.0f }; // mm
|
||||
float mm3_per_mm{ 0.0f };
|
||||
float fan_speed{ 0.0f }; // percentage
|
||||
float temperature{ 0.0f }; // Celsius degrees
|
||||
float time{ 0.0f }; // s
|
||||
float layer_duration{ 0.0f }; // s (layer id before finalize)
|
||||
|
||||
|
||||
//QDS: arc move related data
|
||||
EMovePathType move_path_type{ EMovePathType::Noop_move };
|
||||
Vec3f arc_center_position{ Vec3f::Zero() }; // mm
|
||||
std::vector<Vec3f> interpolation_points; // interpolation points of arc for drawing
|
||||
|
||||
float volumetric_rate() const { return feedrate * mm3_per_mm; }
|
||||
//QDS: new function to support arc move
|
||||
bool is_arc_move_with_interpolation_points() const {
|
||||
return (move_path_type == EMovePathType::Arc_move_ccw || move_path_type == EMovePathType::Arc_move_cw) && interpolation_points.size();
|
||||
}
|
||||
bool is_arc_move() const {
|
||||
return move_path_type == EMovePathType::Arc_move_ccw || move_path_type == EMovePathType::Arc_move_cw;
|
||||
}
|
||||
};
|
||||
|
||||
struct SliceWarning {
|
||||
int level; // 0: normal tips, 1: warning; 2: error
|
||||
std::string msg; // enum string
|
||||
std::string error_code; // error code for studio
|
||||
std::vector<std::string> params; // extra msg info
|
||||
};
|
||||
|
||||
std::string filename;
|
||||
unsigned int id;
|
||||
std::vector<MoveVertex> moves;
|
||||
// Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code.
|
||||
std::vector<size_t> lines_ends;
|
||||
Pointfs printable_area;
|
||||
//QDS: add bed exclude area
|
||||
Pointfs bed_exclude_area;
|
||||
//QDS: add toolpath_outside
|
||||
bool toolpath_outside;
|
||||
//QDS: add object_label_enabled
|
||||
bool label_object_enabled;
|
||||
//QDS : extra retraction when change filament,experiment func
|
||||
bool long_retraction_when_cut {0};
|
||||
int timelapse_warning_code {0};
|
||||
bool support_traditional_timelapse{true};
|
||||
float printable_height;
|
||||
SettingsIds settings_ids;
|
||||
size_t extruders_count;
|
||||
std::vector<std::string> extruder_colors;
|
||||
std::vector<float> filament_diameters;
|
||||
std::vector<int> required_nozzle_HRC;
|
||||
std::vector<float> filament_densities;
|
||||
std::vector<float> filament_costs;
|
||||
std::vector<int> filament_vitrification_temperature;
|
||||
PrintEstimatedStatistics print_statistics;
|
||||
std::vector<CustomGCode::Item> custom_gcode_per_print_z;
|
||||
std::vector<std::pair<float, std::pair<size_t, size_t>>> spiral_vase_layers;
|
||||
//QDS
|
||||
std::vector<SliceWarning> warnings;
|
||||
NozzleType nozzle_type;
|
||||
BedType bed_type = BedType::btCount;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
int64_t time{ 0 };
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
void reset();
|
||||
|
||||
//QDS: add mutex for protection of gcode result
|
||||
mutable std::mutex result_mutex;
|
||||
GCodeProcessorResult& operator=(const GCodeProcessorResult &other)
|
||||
{
|
||||
filename = other.filename;
|
||||
id = other.id;
|
||||
moves = other.moves;
|
||||
lines_ends = other.lines_ends;
|
||||
printable_area = other.printable_area;
|
||||
bed_exclude_area = other.bed_exclude_area;
|
||||
toolpath_outside = other.toolpath_outside;
|
||||
label_object_enabled = other.label_object_enabled;
|
||||
long_retraction_when_cut = other.long_retraction_when_cut;
|
||||
timelapse_warning_code = other.timelapse_warning_code;
|
||||
printable_height = other.printable_height;
|
||||
settings_ids = other.settings_ids;
|
||||
extruders_count = other.extruders_count;
|
||||
extruder_colors = other.extruder_colors;
|
||||
filament_diameters = other.filament_diameters;
|
||||
filament_densities = other.filament_densities;
|
||||
filament_costs = other.filament_costs;
|
||||
print_statistics = other.print_statistics;
|
||||
custom_gcode_per_print_z = other.custom_gcode_per_print_z;
|
||||
spiral_vase_layers = other.spiral_vase_layers;
|
||||
warnings = other.warnings;
|
||||
bed_type = other.bed_type;
|
||||
bed_match_result = other.bed_match_result;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
time = other.time;
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
void lock() const { result_mutex.lock(); }
|
||||
void unlock() const { result_mutex.unlock(); }
|
||||
};
|
||||
|
||||
|
||||
class GCodeProcessor
|
||||
{
|
||||
static const std::vector<std::string> Reserved_Tags;
|
||||
static const std::string Flush_Start_Tag;
|
||||
static const std::string Flush_End_Tag;
|
||||
public:
|
||||
enum class ETags : unsigned char
|
||||
{
|
||||
Role,
|
||||
Wipe_Start,
|
||||
Wipe_End,
|
||||
Height,
|
||||
Width,
|
||||
Layer_Change,
|
||||
Color_Change,
|
||||
Pause_Print,
|
||||
Custom_Code,
|
||||
First_Line_M73_Placeholder,
|
||||
Last_Line_M73_Placeholder,
|
||||
Estimated_Printing_Time_Placeholder,
|
||||
Total_Layer_Number_Placeholder,
|
||||
Wipe_Tower_Start,
|
||||
Wipe_Tower_End,
|
||||
};
|
||||
|
||||
static const std::string& reserved_tag(ETags tag) { return Reserved_Tags[static_cast<unsigned char>(tag)]; }
|
||||
// checks the given gcode for reserved tags and returns true when finding the 1st (which is returned into found_tag)
|
||||
static bool contains_reserved_tag(const std::string& gcode, std::string& found_tag);
|
||||
// checks the given gcode for reserved tags and returns true when finding any
|
||||
// (the first max_count found tags are returned into found_tag)
|
||||
static bool contains_reserved_tags(const std::string& gcode, unsigned int max_count, std::vector<std::string>& found_tag);
|
||||
|
||||
static int get_gcode_last_filament(const std::string &gcode_str);
|
||||
static bool get_last_z_from_gcode(const std::string& gcode_str, double& z);
|
||||
|
||||
static const float Wipe_Width;
|
||||
static const float Wipe_Height;
|
||||
|
||||
static bool s_IsQDTPrinter;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
static const std::string Mm3_Per_Mm_Tag;
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
private:
|
||||
using AxisCoords = std::array<double, 4>;
|
||||
using ExtruderColors = std::vector<unsigned char>;
|
||||
using ExtruderTemps = std::vector<float>;
|
||||
|
||||
enum class EUnits : unsigned char
|
||||
{
|
||||
Millimeters,
|
||||
Inches
|
||||
};
|
||||
|
||||
enum class EPositioningType : unsigned char
|
||||
{
|
||||
Absolute,
|
||||
Relative
|
||||
};
|
||||
|
||||
struct CachedPosition
|
||||
{
|
||||
AxisCoords position; // mm
|
||||
float feedrate; // mm/s
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct CpColor
|
||||
{
|
||||
unsigned char counter;
|
||||
unsigned char current;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
public:
|
||||
struct FeedrateProfile
|
||||
{
|
||||
float entry{ 0.0f }; // mm/s
|
||||
float cruise{ 0.0f }; // mm/s
|
||||
float exit{ 0.0f }; // mm/s
|
||||
};
|
||||
|
||||
struct Trapezoid
|
||||
{
|
||||
float accelerate_until{ 0.0f }; // mm
|
||||
float decelerate_after{ 0.0f }; // mm
|
||||
float cruise_feedrate{ 0.0f }; // mm/sec
|
||||
|
||||
float acceleration_time(float entry_feedrate, float acceleration) const;
|
||||
float cruise_time() const;
|
||||
float deceleration_time(float distance, float acceleration) const;
|
||||
float cruise_distance() const;
|
||||
};
|
||||
|
||||
struct TimeBlock
|
||||
{
|
||||
struct Flags
|
||||
{
|
||||
bool recalculate{ false };
|
||||
bool nominal_length{ false };
|
||||
bool prepare_stage{ false };
|
||||
};
|
||||
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
ExtrusionRole role{ erNone };
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int layer_id{ 0 };
|
||||
float distance{ 0.0f }; // mm
|
||||
float acceleration{ 0.0f }; // mm/s^2
|
||||
float max_entry_speed{ 0.0f }; // mm/s
|
||||
float safe_feedrate{ 0.0f }; // mm/s
|
||||
Flags flags;
|
||||
FeedrateProfile feedrate_profile;
|
||||
Trapezoid trapezoid;
|
||||
|
||||
// Calculates this block's trapezoid
|
||||
void calculate_trapezoid();
|
||||
|
||||
float time() const;
|
||||
};
|
||||
|
||||
|
||||
private:
|
||||
struct TimeMachine
|
||||
{
|
||||
struct State
|
||||
{
|
||||
float feedrate; // mm/s
|
||||
float safe_feedrate; // mm/s
|
||||
//QDS: feedrate of X-Y-Z-E axis. But when the move is G2 and G3, X-Y will be
|
||||
//same value which means feedrate in X-Y plane.
|
||||
AxisCoords axis_feedrate; // mm/s
|
||||
AxisCoords abs_axis_feedrate; // mm/s
|
||||
|
||||
//QDS: unit vector of enter speed and exit speed in x-y-z space.
|
||||
//For line move, there are same. For arc move, there are different.
|
||||
Vec3f enter_direction;
|
||||
Vec3f exit_direction;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct CustomGCodeTime
|
||||
{
|
||||
bool needed;
|
||||
float cache;
|
||||
std::vector<std::pair<CustomGCode::Type, float>> times;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
struct G1LinesCacheItem
|
||||
{
|
||||
unsigned int id;
|
||||
float elapsed_time;
|
||||
};
|
||||
|
||||
bool enabled;
|
||||
float acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_acceleration; // mm/s^2
|
||||
float retract_acceleration; // mm/s^2
|
||||
// hard limit for the acceleration, to which the firmware will clamp.
|
||||
float max_retract_acceleration; // mm/s^2
|
||||
float travel_acceleration; // mm/s^2
|
||||
// hard limit for the travel acceleration, to which the firmware will clamp.
|
||||
float max_travel_acceleration; // mm/s^2
|
||||
float extrude_factor_override_percentage;
|
||||
float time; // s
|
||||
struct StopTime
|
||||
{
|
||||
unsigned int g1_line_id;
|
||||
float elapsed_time;
|
||||
};
|
||||
std::vector<StopTime> stop_times;
|
||||
std::string line_m73_main_mask;
|
||||
std::string line_m73_stop_mask;
|
||||
State curr;
|
||||
State prev;
|
||||
CustomGCodeTime gcode_time;
|
||||
std::vector<TimeBlock> blocks;
|
||||
std::vector<G1LinesCacheItem> g1_times_cache;
|
||||
std::array<float, static_cast<size_t>(EMoveType::Count)> moves_time;
|
||||
std::array<float, static_cast<size_t>(ExtrusionRole::erCount)> roles_time;
|
||||
std::vector<float> layers_time;
|
||||
//QDS: prepare stage time before print model, including start gcode time and mostly same with start gcode time
|
||||
float prepare_time;
|
||||
|
||||
void reset();
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f);
|
||||
};
|
||||
|
||||
struct TimeProcessor
|
||||
{
|
||||
struct Planner
|
||||
{
|
||||
// Size of the firmware planner queue. The old 8-bit Marlins usually just managed 16 trapezoidal blocks.
|
||||
// Let's be conservative and plan for newer boards with more memory.
|
||||
static constexpr size_t queue_size = 64;
|
||||
// The firmware recalculates last planner_queue_size trapezoidal blocks each time a new block is added.
|
||||
// We are not simulating the firmware exactly, we calculate a sequence of blocks once a reasonable number of blocks accumulate.
|
||||
static constexpr size_t refresh_threshold = queue_size * 4;
|
||||
};
|
||||
|
||||
// extruder_id is currently used to correctly calculate filament load / unload times into the total print time.
|
||||
// This is currently only really used by the MK3 MMU2:
|
||||
// extruder_unloaded = true means no filament is loaded yet, all the filaments are parked in the MK3 MMU2 unit.
|
||||
bool extruder_unloaded;
|
||||
// allow to skip the lines M201/M203/M204/M205 generated by GCode::print_machine_envelope() for non-Normal time estimate mode
|
||||
bool machine_envelope_processing_enabled;
|
||||
MachineEnvelopeConfig machine_limits;
|
||||
// Additional load / unload times for a filament exchange sequence.
|
||||
float filament_load_times;
|
||||
float filament_unload_times;
|
||||
|
||||
std::array<TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> machines;
|
||||
|
||||
void reset();
|
||||
|
||||
// post process the file with the given filename to add remaining time lines M73
|
||||
// and updates moves' gcode ids accordingly
|
||||
void post_process(const std::string& filename, std::vector<GCodeProcessorResult::MoveVertex>& moves, std::vector<size_t>& lines_ends, size_t total_layer_num);
|
||||
};
|
||||
|
||||
struct UsedFilaments // filaments per ColorChange
|
||||
{
|
||||
double color_change_cache;
|
||||
std::vector<double> volumes_per_color_change;
|
||||
|
||||
double model_extrude_cache;
|
||||
std::map<size_t, double> model_volumes_per_extruder;
|
||||
|
||||
double wipe_tower_cache;
|
||||
std::map<size_t, double>wipe_tower_volumes_per_extruder;
|
||||
|
||||
double support_volume_cache;
|
||||
std::map<size_t, double>support_volumes_per_extruder;
|
||||
|
||||
//QDS: the flush amount of every filament
|
||||
std::map<size_t, double> flush_per_filament;
|
||||
|
||||
double total_volume_cache;
|
||||
std::map<size_t, double>total_volumes_per_extruder;
|
||||
|
||||
double role_cache;
|
||||
std::map<ExtrusionRole, std::pair<double, double>> filaments_per_role;
|
||||
|
||||
void reset();
|
||||
|
||||
void increase_support_caches(double extruded_volume);
|
||||
void increase_model_caches(double extruded_volume);
|
||||
void increase_wipe_tower_caches(double extruded_volume);
|
||||
|
||||
void process_color_change_cache();
|
||||
void process_model_cache(GCodeProcessor* processor);
|
||||
void process_wipe_tower_cache(GCodeProcessor* processor);
|
||||
void process_support_cache(GCodeProcessor* processor);
|
||||
void process_total_volume_cache(GCodeProcessor* processor);
|
||||
|
||||
void update_flush_per_filament(size_t extrude_id, float flush_length);
|
||||
void process_role_cache(GCodeProcessor* processor);
|
||||
void process_caches(GCodeProcessor* processor);
|
||||
|
||||
friend class GCodeProcessor;
|
||||
};
|
||||
|
||||
public:
|
||||
class SeamsDetector
|
||||
{
|
||||
bool m_active{ false };
|
||||
std::optional<Vec3f> m_first_vertex;
|
||||
|
||||
public:
|
||||
void activate(bool active) {
|
||||
if (m_active != active) {
|
||||
m_active = active;
|
||||
if (m_active)
|
||||
m_first_vertex.reset();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<Vec3f> get_first_vertex() const { return m_first_vertex; }
|
||||
void set_first_vertex(const Vec3f& vertex) { m_first_vertex = vertex; }
|
||||
|
||||
bool is_active() const { return m_active; }
|
||||
bool has_first_vertex() const { return m_first_vertex.has_value(); }
|
||||
};
|
||||
|
||||
// Helper class used to fix the z for color change, pause print and
|
||||
// custom gcode markes
|
||||
class OptionsZCorrector
|
||||
{
|
||||
GCodeProcessorResult& m_result;
|
||||
std::optional<size_t> m_move_id;
|
||||
std::optional<size_t> m_custom_gcode_per_print_z_id;
|
||||
|
||||
public:
|
||||
explicit OptionsZCorrector(GCodeProcessorResult& result) : m_result(result) {
|
||||
}
|
||||
|
||||
void set() {
|
||||
m_move_id = m_result.moves.size() - 1;
|
||||
m_custom_gcode_per_print_z_id = m_result.custom_gcode_per_print_z.size() - 1;
|
||||
}
|
||||
|
||||
void update(float height) {
|
||||
if (!m_move_id.has_value() || !m_custom_gcode_per_print_z_id.has_value())
|
||||
return;
|
||||
|
||||
const Vec3f position = m_result.moves.back().position;
|
||||
|
||||
GCodeProcessorResult::MoveVertex& move = m_result.moves.emplace_back(m_result.moves[*m_move_id]);
|
||||
move.position = position;
|
||||
move.height = height;
|
||||
m_result.moves.erase(m_result.moves.begin() + *m_move_id);
|
||||
m_result.custom_gcode_per_print_z[*m_custom_gcode_per_print_z_id].print_z = position.z();
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_move_id.reset();
|
||||
m_custom_gcode_per_print_z_id.reset();
|
||||
}
|
||||
};
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
struct DataChecker
|
||||
{
|
||||
struct Error
|
||||
{
|
||||
float value;
|
||||
float tag_value;
|
||||
ExtrusionRole role;
|
||||
};
|
||||
|
||||
std::string type;
|
||||
float threshold{ 0.01f };
|
||||
float last_tag_value{ 0.0f };
|
||||
unsigned int count{ 0 };
|
||||
std::vector<Error> errors;
|
||||
|
||||
DataChecker(const std::string& type, float threshold)
|
||||
: type(type), threshold(threshold)
|
||||
{}
|
||||
|
||||
void update(float value, ExtrusionRole role) {
|
||||
if (role != erCustom) {
|
||||
++count;
|
||||
if (last_tag_value != 0.0f) {
|
||||
if (std::abs(value - last_tag_value) / last_tag_value > threshold)
|
||||
errors.push_back({ value, last_tag_value, role });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; }
|
||||
|
||||
std::pair<float, float> get_min() const {
|
||||
float delta_min = FLT_MAX;
|
||||
float perc_min = 0.0f;
|
||||
for (const Error& e : errors) {
|
||||
if (delta_min > e.value - e.tag_value) {
|
||||
delta_min = e.value - e.tag_value;
|
||||
perc_min = 100.0f * delta_min / e.tag_value;
|
||||
}
|
||||
}
|
||||
return { delta_min, perc_min };
|
||||
}
|
||||
|
||||
std::pair<float, float> get_max() const {
|
||||
float delta_max = -FLT_MAX;
|
||||
float perc_max = 0.0f;
|
||||
for (const Error& e : errors) {
|
||||
if (delta_max < e.value - e.tag_value) {
|
||||
delta_max = e.value - e.tag_value;
|
||||
perc_max = 100.0f * delta_max / e.tag_value;
|
||||
}
|
||||
}
|
||||
return { delta_max, perc_max };
|
||||
}
|
||||
|
||||
void output() const {
|
||||
if (!errors.empty()) {
|
||||
std::cout << type << ":\n";
|
||||
std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n";
|
||||
auto [min, perc_min] = get_min();
|
||||
auto [max, perc_max] = get_max();
|
||||
std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
private:
|
||||
GCodeReader m_parser;
|
||||
EUnits m_units;
|
||||
EPositioningType m_global_positioning_type;
|
||||
EPositioningType m_e_local_positioning_type;
|
||||
std::vector<Vec3f> m_extruder_offsets;
|
||||
GCodeFlavor m_flavor;
|
||||
float m_nozzle_volume;
|
||||
AxisCoords m_start_position; // mm
|
||||
AxisCoords m_end_position; // mm
|
||||
AxisCoords m_origin; // mm
|
||||
CachedPosition m_cached_position;
|
||||
bool m_wiping;
|
||||
bool m_flushing;
|
||||
bool m_wipe_tower;
|
||||
float m_remaining_volume;
|
||||
|
||||
//QDS: x, y offset for gcode generated
|
||||
double m_x_offset{ 0 };
|
||||
double m_y_offset{ 0 };
|
||||
//QDS: arc move related data
|
||||
EMovePathType m_move_path_type{ EMovePathType::Noop_move };
|
||||
Vec3f m_arc_center{ Vec3f::Zero() }; // mm
|
||||
std::vector<Vec3f> m_interpolation_points;
|
||||
|
||||
unsigned int m_line_id;
|
||||
unsigned int m_last_line_id;
|
||||
float m_feedrate; // mm/s
|
||||
float m_width; // mm
|
||||
float m_height; // mm
|
||||
float m_forced_width; // mm
|
||||
float m_forced_height; // mm
|
||||
float m_mm3_per_mm;
|
||||
float m_fan_speed; // percentage
|
||||
ExtrusionRole m_extrusion_role;
|
||||
unsigned char m_extruder_id;
|
||||
unsigned char m_last_extruder_id;
|
||||
ExtruderColors m_extruder_colors;
|
||||
ExtruderTemps m_extruder_temps;
|
||||
int m_highest_bed_temp;
|
||||
float m_extruded_last_z;
|
||||
float m_first_layer_height; // mm
|
||||
float m_zero_layer_height; // mm
|
||||
bool m_processing_start_custom_gcode;
|
||||
unsigned int m_g1_line_id;
|
||||
unsigned int m_layer_id;
|
||||
CpColor m_cp_color;
|
||||
SeamsDetector m_seams_detector;
|
||||
OptionsZCorrector m_options_z_corrector;
|
||||
size_t m_last_default_color_id;
|
||||
bool m_detect_layer_based_on_tag {false};
|
||||
int m_seams_count;
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_start_time;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
enum class EProducer
|
||||
{
|
||||
Unknown,
|
||||
QIDIStudio,
|
||||
Slic3rPE,
|
||||
Slic3r,
|
||||
SuperSlicer,
|
||||
Cura,
|
||||
Simplify3D,
|
||||
CraftWare,
|
||||
ideaMaker,
|
||||
KissSlicer
|
||||
};
|
||||
|
||||
static const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> Producers;
|
||||
EProducer m_producer;
|
||||
|
||||
TimeProcessor m_time_processor;
|
||||
UsedFilaments m_used_filaments;
|
||||
|
||||
GCodeProcessorResult m_result;
|
||||
static unsigned int s_result_id;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f };
|
||||
DataChecker m_height_compare{ "height", 0.01f };
|
||||
DataChecker m_width_compare{ "width", 0.01f };
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
public:
|
||||
GCodeProcessor();
|
||||
|
||||
void apply_config(const PrintConfig& config);
|
||||
void enable_stealth_time_estimator(bool enabled);
|
||||
bool is_stealth_time_estimator_enabled() const {
|
||||
return m_time_processor.machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled;
|
||||
}
|
||||
void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; }
|
||||
void reset();
|
||||
|
||||
const GCodeProcessorResult& get_result() const { return m_result; }
|
||||
GCodeProcessorResult& result() { return m_result; }
|
||||
GCodeProcessorResult&& extract_result() { return std::move(m_result); }
|
||||
|
||||
// Load a G-code into a stand-alone G-code viewer.
|
||||
// throws CanceledException through print->throw_if_canceled() (sent by the caller as callback).
|
||||
void process_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
|
||||
// Streaming interface, for processing G-codes just generated by PrusaSlicer in a pipelined fashion.
|
||||
void initialize(const std::string& filename);
|
||||
void process_buffer(const std::string& buffer);
|
||||
void finalize(bool post_process);
|
||||
|
||||
float get_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
float get_prepare_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::vector<std::pair<CustomGCode::Type, std::pair<float, float>>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const;
|
||||
|
||||
std::vector<std::pair<EMoveType, float>> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::vector<std::pair<ExtrusionRole, float>> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
std::vector<float> get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
|
||||
//QDS: set offset for gcode writer
|
||||
void set_xy_offset(double x, double y) { m_x_offset = x; m_y_offset = y; }
|
||||
// Orca: if true, only change new layer if ETags::Layer_Change occurs
|
||||
// otherwise when we got a lift of z during extrusion, a new layer will be added
|
||||
void detect_layer_based_on_tag(bool enabled) {
|
||||
m_detect_layer_based_on_tag = enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
void apply_config(const DynamicPrintConfig& config);
|
||||
void apply_config_simplify3d(const std::string& filename);
|
||||
void apply_config_superslicer(const std::string& filename);
|
||||
void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled);
|
||||
|
||||
// Process tags embedded into comments
|
||||
void process_tags(const std::string_view comment, bool producers_enabled);
|
||||
bool process_producers_tags(const std::string_view comment);
|
||||
bool process_qidislicer_tags(const std::string_view comment);
|
||||
bool process_cura_tags(const std::string_view comment);
|
||||
bool process_simplify3d_tags(const std::string_view comment);
|
||||
bool process_craftware_tags(const std::string_view comment);
|
||||
bool process_ideamaker_tags(const std::string_view comment);
|
||||
bool process_kissslicer_tags(const std::string_view comment);
|
||||
|
||||
bool detect_producer(const std::string_view comment);
|
||||
|
||||
// Move
|
||||
void process_G0(const GCodeReader::GCodeLine& line);
|
||||
void process_G1(const GCodeReader::GCodeLine& line);
|
||||
void process_G2_G3(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// QDS: handle delay command
|
||||
void process_G4(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Retract
|
||||
void process_G10(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unretract
|
||||
void process_G11(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Units to Inches
|
||||
void process_G20(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Units to Millimeters
|
||||
void process_G21(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Firmware controlled Retract
|
||||
void process_G22(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Firmware controlled Unretract
|
||||
void process_G23(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Move to origin
|
||||
void process_G28(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// QDS
|
||||
void process_G29(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Absolute Positioning
|
||||
void process_G90(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set to Relative Positioning
|
||||
void process_G91(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set Position
|
||||
void process_G92(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Sleep or Conditional stop
|
||||
void process_M1(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to absolute mode
|
||||
void process_M82(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder to relative mode
|
||||
void process_M83(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder temperature
|
||||
void process_M104(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set fan speed
|
||||
void process_M106(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Disable fan
|
||||
void process_M107(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set tool (Sailfish)
|
||||
void process_M108(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extruder temperature and wait
|
||||
void process_M109(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Recall stored home offsets
|
||||
void process_M132(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set tool (MakerWare)
|
||||
void process_M135(const GCodeReader::GCodeLine& line);
|
||||
|
||||
//QDS: Set bed temperature
|
||||
void process_M140(const GCodeReader::GCodeLine& line);
|
||||
|
||||
//QDS: wait bed temperature
|
||||
void process_M190(const GCodeReader::GCodeLine& line);
|
||||
|
||||
//QDS: wait chamber temperature
|
||||
void process_M191(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set max printing acceleration
|
||||
void process_M201(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set maximum feedrate
|
||||
void process_M203(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set default acceleration
|
||||
void process_M204(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Advanced settings
|
||||
void process_M205(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Klipper SET_VELOCITY_LIMIT
|
||||
void process_SET_VELOCITY_LIMIT(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set extrude factor override percentage
|
||||
void process_M221(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// QDS: handle delay command. M400 is defined by QDT only
|
||||
void process_M400(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Repetier: Store x, y and z position
|
||||
void process_M401(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Repetier: Go to stored position
|
||||
void process_M402(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Set allowable instantaneous speed change
|
||||
void process_M566(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Unload the current filament into the MK3 MMU2 unit at the end of print.
|
||||
void process_M702(const GCodeReader::GCodeLine& line);
|
||||
|
||||
// Processes T line (Select Tool)
|
||||
void process_T(const GCodeReader::GCodeLine& line);
|
||||
void process_T(const std::string_view command);
|
||||
|
||||
//QDS: different path_type is only used for arc move
|
||||
void store_move_vertex(EMoveType type, EMovePathType path_type = EMovePathType::Noop_move);
|
||||
|
||||
void set_extrusion_role(ExtrusionRole role);
|
||||
|
||||
float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const;
|
||||
float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const;
|
||||
Vec3f get_xyz_max_jerk(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const;
|
||||
void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value);
|
||||
float get_filament_load_time(size_t extruder_id);
|
||||
float get_filament_unload_time(size_t extruder_id);
|
||||
int get_filament_vitrification_temperature(size_t extrude_id);
|
||||
void process_custom_gcode_time(CustomGCode::Type code);
|
||||
void process_filaments(CustomGCode::Type code);
|
||||
|
||||
// Simulates firmware st_synchronize() call
|
||||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
|
||||
void update_estimated_times_stats();
|
||||
//QDS:
|
||||
void update_slice_warnings();
|
||||
};
|
||||
|
||||
} /* namespace Slic3r */
|
||||
|
||||
#endif /* slic3r_GCodeProcessor_hpp_ */
|
||||
|
||||
|
||||
379
src/libslic3r/GCode/PostProcessor.cpp
Normal file
379
src/libslic3r/GCode/PostProcessor.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
#include "PostProcessor.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/cenv.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
// QDS
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
// https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||
// This routine appends the given argument to a command line such that CommandLineToArgvW will return the argument string unchanged.
|
||||
// Arguments in a command line should be separated by spaces; this function does not add these spaces.
|
||||
// Argument - Supplies the argument to encode.
|
||||
// CommandLine - Supplies the command line to which we append the encoded argument string.
|
||||
static void quote_argv_winapi(const std::wstring &argument, std::wstring &commmand_line_out)
|
||||
{
|
||||
// Don't quote unless we actually need to do so --- hopefully avoid problems if programs won't parse quotes properly.
|
||||
if (argument.empty() == false && argument.find_first_of(L" \t\n\v\"") == argument.npos)
|
||||
commmand_line_out.append(argument);
|
||||
else {
|
||||
commmand_line_out.push_back(L'"');
|
||||
for (auto it = argument.begin(); ; ++ it) {
|
||||
unsigned number_backslashes = 0;
|
||||
while (it != argument.end() && *it == L'\\') {
|
||||
++ it;
|
||||
++ number_backslashes;
|
||||
}
|
||||
if (it == argument.end()) {
|
||||
// Escape all backslashes, but let the terminating double quotation mark we add below be interpreted as a metacharacter.
|
||||
commmand_line_out.append(number_backslashes * 2, L'\\');
|
||||
break;
|
||||
} else if (*it == L'"') {
|
||||
// Escape all backslashes and the following double quotation mark.
|
||||
commmand_line_out.append(number_backslashes * 2 + 1, L'\\');
|
||||
commmand_line_out.push_back(*it);
|
||||
} else {
|
||||
// Backslashes aren't special here.
|
||||
commmand_line_out.append(number_backslashes, L'\\');
|
||||
commmand_line_out.push_back(*it);
|
||||
}
|
||||
}
|
||||
commmand_line_out.push_back(L'"');
|
||||
}
|
||||
}
|
||||
|
||||
static DWORD execute_process_winapi(const std::wstring &command_line)
|
||||
{
|
||||
// Extract the current environment to be passed to the child process.
|
||||
std::wstring envstr;
|
||||
{
|
||||
wchar_t *env = GetEnvironmentStrings();
|
||||
assert(env != nullptr);
|
||||
const wchar_t* var = env;
|
||||
size_t totallen = 0;
|
||||
size_t len;
|
||||
while ((len = wcslen(var)) > 0) {
|
||||
totallen += len + 1;
|
||||
var += len + 1;
|
||||
}
|
||||
envstr = std::wstring(env, totallen);
|
||||
FreeEnvironmentStrings(env);
|
||||
}
|
||||
|
||||
STARTUPINFOW startup_info;
|
||||
memset(&startup_info, 0, sizeof(startup_info));
|
||||
startup_info.cb = sizeof(STARTUPINFO);
|
||||
#if 0
|
||||
startup_info.dwFlags = STARTF_USESHOWWINDOW;
|
||||
startup_info.wShowWindow = SW_HIDE;
|
||||
#endif
|
||||
PROCESS_INFORMATION process_info;
|
||||
if (! ::CreateProcessW(
|
||||
nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */,
|
||||
CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info))
|
||||
throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError())));
|
||||
::WaitForSingleObject(process_info.hProcess, INFINITE);
|
||||
ULONG rc = 0;
|
||||
::GetExitCodeProcess(process_info.hProcess, &rc);
|
||||
::CloseHandle(process_info.hThread);
|
||||
::CloseHandle(process_info.hProcess);
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Run the script. If it is a perl script, run it through the bundled perl interpreter.
|
||||
// If it is a batch file, run it through the cmd.exe.
|
||||
// Otherwise run it directly.
|
||||
static int run_script(const std::string &script, const std::string &gcode, std::string &/*std_err*/)
|
||||
{
|
||||
// Unpack the argument list provided by the user.
|
||||
int nArgs;
|
||||
LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs);
|
||||
if (szArglist == nullptr || nArgs <= 0) {
|
||||
// CommandLineToArgvW failed. Maybe the command line escapment is invalid?
|
||||
throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path.");
|
||||
}
|
||||
|
||||
std::wstring command_line;
|
||||
std::wstring command = szArglist[0];
|
||||
if (! boost::filesystem::exists(boost::filesystem::path(command)))
|
||||
throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command));
|
||||
if (boost::iends_with(command, L".pl")) {
|
||||
// This is a perl script. Run it through the perl interpreter.
|
||||
// The current process may be slic3r.exe or slic3r-console.exe.
|
||||
// Find the path of the process:
|
||||
wchar_t wpath_exe[_MAX_PATH + 1];
|
||||
::GetModuleFileNameW(nullptr, wpath_exe, _MAX_PATH);
|
||||
boost::filesystem::path path_exe(wpath_exe);
|
||||
boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe";
|
||||
if (! boost::filesystem::exists(path_perl)) {
|
||||
LocalFree(szArglist);
|
||||
throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist.");
|
||||
}
|
||||
// Replace it with the current perl interpreter.
|
||||
quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line);
|
||||
command_line += L" ";
|
||||
} else if (boost::iends_with(command, ".bat")) {
|
||||
// Run a batch file through the command line interpreter.
|
||||
command_line = L"cmd.exe /C ";
|
||||
}
|
||||
|
||||
for (int i = 0; i < nArgs; ++ i) {
|
||||
quote_argv_winapi(szArglist[i], command_line);
|
||||
command_line += L" ";
|
||||
}
|
||||
LocalFree(szArglist);
|
||||
quote_argv_winapi(boost::nowide::widen(gcode), command_line);
|
||||
return (int)execute_process_winapi(command_line);
|
||||
}
|
||||
|
||||
#else
|
||||
// POSIX
|
||||
|
||||
#include <cstdlib> // getenv()
|
||||
#include <sstream>
|
||||
#include <boost/process.hpp>
|
||||
|
||||
namespace process = boost::process;
|
||||
|
||||
static int run_script(const std::string &script, const std::string &gcode, std::string &std_err)
|
||||
{
|
||||
// Try to obtain user's default shell
|
||||
const char *shell = ::getenv("SHELL");
|
||||
if (shell == nullptr) { shell = "sh"; }
|
||||
|
||||
// Quote and escape the gcode path argument
|
||||
std::string command { script };
|
||||
command.append(" '");
|
||||
for (char c : gcode) {
|
||||
if (c == '\'') { command.append("'\\''"); }
|
||||
else { command.push_back(c); }
|
||||
}
|
||||
command.push_back('\'');
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("Executing script, shell: %1%, command: %2%") % shell % command;
|
||||
|
||||
process::ipstream istd_err;
|
||||
process::child child(shell, "-c", command, process::std_err > istd_err);
|
||||
|
||||
std_err.clear();
|
||||
std::string line;
|
||||
|
||||
while (child.running() && std::getline(istd_err, line)) {
|
||||
std_err.append(line);
|
||||
std_err.push_back('\n');
|
||||
}
|
||||
|
||||
child.wait();
|
||||
return child.exit_code();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) (s)
|
||||
#define _(s) Slic3r::I18N::translate(s)
|
||||
|
||||
// QDS
|
||||
void gcode_add_line_number(const std::string& path, const DynamicPrintConfig& config)
|
||||
{
|
||||
const ConfigOptionBool* opt = config.opt<ConfigOptionBool>("gcode_add_line_number");
|
||||
if (!opt->getBool())
|
||||
return;
|
||||
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (!boost::filesystem::exists(gcode_file))
|
||||
return;
|
||||
|
||||
std::fstream fs;
|
||||
std::string new_gcode;
|
||||
fs.open(gcode_file.c_str(), std::fstream::in | std::fstream::out);
|
||||
|
||||
size_t line_number = 1;
|
||||
std::string gcode_line;
|
||||
while (std::getline(fs, gcode_line)) {
|
||||
char num_str[128];
|
||||
memset(num_str, 0, sizeof(num_str));
|
||||
snprintf(num_str, sizeof(num_str), "%d", line_number);
|
||||
new_gcode += std::string("N") + num_str + " " + gcode_line + "\n";
|
||||
line_number++;
|
||||
}
|
||||
|
||||
fs.clear();
|
||||
fs.seekp(0, std::ios_base::beg);
|
||||
fs.write(new_gcode.c_str(), new_gcode.length());
|
||||
fs.close();
|
||||
}
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
|
||||
{
|
||||
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
|
||||
if (// likely running in SLA mode
|
||||
post_process == nullptr ||
|
||||
// no post-processing script
|
||||
post_process->values.empty())
|
||||
return false;
|
||||
|
||||
std::string path;
|
||||
if (make_copy) {
|
||||
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
|
||||
// Make a copy.
|
||||
path = src_path + ".pp";
|
||||
// First delete an old file if it exists.
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
|
||||
}
|
||||
// Second make a copy.
|
||||
std::string error_message;
|
||||
if (copy_file(src_path, path, error_message, false) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
|
||||
} else {
|
||||
// Don't make a copy of the G-code before running the post-processing script.
|
||||
path = src_path;
|
||||
}
|
||||
|
||||
auto delete_copy = [&path, &src_path, make_copy]() {
|
||||
if (make_copy)
|
||||
try {
|
||||
if (boost::filesystem::exists(path))
|
||||
boost::filesystem::remove(path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
|
||||
}
|
||||
};
|
||||
|
||||
auto gcode_file = boost::filesystem::path(path);
|
||||
if (! boost::filesystem::exists(gcode_file))
|
||||
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
|
||||
|
||||
// Store print configuration into environment variables.
|
||||
config.setenv_();
|
||||
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
|
||||
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
|
||||
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
|
||||
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
|
||||
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
|
||||
|
||||
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
|
||||
std::string path_output_name = path + ".output_name";
|
||||
auto remove_output_name_file = [&path_output_name, &src_path]() {
|
||||
try {
|
||||
if (boost::filesystem::exists(path_output_name))
|
||||
boost::filesystem::remove(path_output_name);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
|
||||
}
|
||||
};
|
||||
// Remove possible stalled path_output_name of the previous run.
|
||||
remove_output_name_file();
|
||||
|
||||
try {
|
||||
for (const std::string &scripts : post_process->values) {
|
||||
std::vector<std::string> lines;
|
||||
boost::split(lines, scripts, boost::is_any_of("\r\n"));
|
||||
for (std::string script : lines) {
|
||||
// Ignore empty post processing script lines.
|
||||
boost::trim(script);
|
||||
if (script.empty())
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
|
||||
std::string std_err;
|
||||
const int result = run_script(script, gcode_file.string(), std_err);
|
||||
if (result != 0) {
|
||||
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
|
||||
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
delete_copy();
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
if (! boost::filesystem::exists(gcode_file)) {
|
||||
const std::string msg = (boost::format(_(L(
|
||||
"Post-processing script %1% failed.\n\n"
|
||||
"The post-processing script is expected to change the G-code file %2% in place, but the G-code file was deleted and likely saved under a new name.\n"
|
||||
"Please adjust the post-processing script to change the G-code in place and consult the manual on how to optionally rename the post-processed G-code file.\n")))
|
||||
% script % path).str();
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
throw Slic3r::RuntimeError(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boost::filesystem::exists(path_output_name)) {
|
||||
try {
|
||||
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
|
||||
boost::nowide::fstream f;
|
||||
f.open(path_output_name, std::ios::in);
|
||||
std::string new_output_name;
|
||||
std::getline(f, new_output_name);
|
||||
f.close();
|
||||
|
||||
if (host == "File") {
|
||||
namespace fs = boost::filesystem;
|
||||
fs::path op(new_output_name);
|
||||
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
|
||||
// Is this just a filename? Make it an absolute path.
|
||||
auto outpath = fs::path(output_name).parent_path();
|
||||
outpath /= op.string();
|
||||
new_output_name = outpath.string();
|
||||
}
|
||||
else {
|
||||
if (! op.is_absolute() || ! op.has_filename())
|
||||
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
|
||||
}
|
||||
if (! fs::exists(fs::path(new_output_name).parent_path()))
|
||||
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
|
||||
fs::path(new_output_name).parent_path().string()));
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
|
||||
output_name = new_output_name;
|
||||
} catch (const std::exception &err) {
|
||||
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
|
||||
"carrying the final name / path of a G-code file: %2%",
|
||||
path_output_name, err.what()));
|
||||
}
|
||||
remove_output_name_file();
|
||||
}
|
||||
} catch (...) {
|
||||
remove_output_name_file();
|
||||
delete_copy();
|
||||
throw;
|
||||
}
|
||||
|
||||
src_path = std::move(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
34
src/libslic3r/GCode/PostProcessor.hpp
Normal file
34
src/libslic3r/GCode/PostProcessor.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef slic3r_GCode_PostProcessor_hpp_
|
||||
#define slic3r_GCode_PostProcessor_hpp_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Run post processing script / scripts if defined.
|
||||
// Returns true if a post-processing script was executed.
|
||||
// Returns false if no post-processing script was defined.
|
||||
// Throws an exception on error.
|
||||
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
|
||||
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
|
||||
// In that case the caller is responsible to delete the temp file created.
|
||||
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
|
||||
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
|
||||
// The post-processing script may change the output_name.
|
||||
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
|
||||
|
||||
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
|
||||
{
|
||||
std::string src_path_name = src_path;
|
||||
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
|
||||
}
|
||||
|
||||
// QDS
|
||||
extern void gcode_add_line_number(const std::string &path, const DynamicPrintConfig &config);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GCode_PostProcessor_hpp_ */
|
||||
623
src/libslic3r/GCode/PressureEqualizer.cpp
Normal file
623
src/libslic3r/GCode/PressureEqualizer.cpp
Normal file
@@ -0,0 +1,623 @@
|
||||
#include <memory.h>
|
||||
#include <string.h>
|
||||
#include <float.h>
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../LocalesUtils.hpp"
|
||||
|
||||
#include "PressureEqualizer.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig *config) :
|
||||
m_config(config)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
PressureEqualizer::~PressureEqualizer()
|
||||
{
|
||||
}
|
||||
|
||||
void PressureEqualizer::reset()
|
||||
{
|
||||
circular_buffer_pos = 0;
|
||||
circular_buffer_size = 100;
|
||||
circular_buffer_items = 0;
|
||||
circular_buffer.assign(circular_buffer_size, GCodeLine());
|
||||
|
||||
// Preallocate some data, so that output_buffer.data() will return an empty string.
|
||||
output_buffer.assign(32, 0);
|
||||
output_buffer_length = 0;
|
||||
|
||||
m_current_extruder = 0;
|
||||
// Zero the position of the XYZE axes + the current feed
|
||||
memset(m_current_pos, 0, sizeof(float) * 5);
|
||||
m_current_extrusion_role = erNone;
|
||||
// Expect the first command to fill the nozzle (deretract).
|
||||
m_retracted = true;
|
||||
|
||||
// Calculate filamet crossections for the multiple extruders.
|
||||
m_filament_crossections.clear();
|
||||
for (size_t i = 0; i < m_config->filament_diameter.values.size(); ++ i) {
|
||||
double r = m_config->filament_diameter.values[i];
|
||||
double a = 0.25f*M_PI*r*r;
|
||||
m_filament_crossections.push_back(float(a));
|
||||
}
|
||||
|
||||
m_max_segment_length = 20.f;
|
||||
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min
|
||||
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min
|
||||
// Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2
|
||||
m_max_volumetric_extrusion_rate_slope_positive = (m_config == NULL) ? 6480.f :
|
||||
m_config->max_volumetric_extrusion_rate_slope_positive.value * 60.f * 60.f;
|
||||
m_max_volumetric_extrusion_rate_slope_negative = (m_config == NULL) ? 6480.f :
|
||||
m_config->max_volumetric_extrusion_rate_slope_negative.value * 60.f * 60.f;
|
||||
|
||||
for (size_t i = 0; i < numExtrusionRoles; ++ i) {
|
||||
m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative;
|
||||
m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive;
|
||||
}
|
||||
|
||||
// Don't regulate the pressure in infill.
|
||||
m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0;
|
||||
m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0;
|
||||
// Don't regulate the pressure in gap fill.
|
||||
m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0;
|
||||
m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0;
|
||||
|
||||
m_stat.reset();
|
||||
line_idx = 0;
|
||||
}
|
||||
|
||||
const char* PressureEqualizer::process(const char *szGCode, bool flush)
|
||||
{
|
||||
// Reset length of the output_buffer.
|
||||
output_buffer_length = 0;
|
||||
|
||||
if (szGCode != 0) {
|
||||
const char *p = szGCode;
|
||||
while (*p != 0) {
|
||||
// Find end of the line.
|
||||
const char *endl = p;
|
||||
// Slic3r always generates end of lines in a Unix style.
|
||||
for (; *endl != 0 && *endl != '\n'; ++ endl) ;
|
||||
if (circular_buffer_items == circular_buffer_size)
|
||||
// Buffer is full. Push out the oldest line.
|
||||
output_gcode_line(circular_buffer[circular_buffer_pos]);
|
||||
else
|
||||
++ circular_buffer_items;
|
||||
// Process a G-code line, store it into the provided GCodeLine object.
|
||||
size_t idx_tail = circular_buffer_pos;
|
||||
circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos);
|
||||
if (! process_line(p, endl - p, circular_buffer[idx_tail])) {
|
||||
// The line has to be forgotten. It contains comment marks, which shall be
|
||||
// filtered out of the target g-code.
|
||||
circular_buffer_pos = idx_tail;
|
||||
-- circular_buffer_items;
|
||||
}
|
||||
p = endl;
|
||||
if (*p == '\n')
|
||||
++ p;
|
||||
}
|
||||
}
|
||||
|
||||
if (flush) {
|
||||
// Flush the remaining valid lines of the circular buffer.
|
||||
for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) {
|
||||
output_gcode_line(circular_buffer[idx]);
|
||||
if (++ idx == circular_buffer_size)
|
||||
idx = 0;
|
||||
}
|
||||
// Reset the index pointer.
|
||||
assert(circular_buffer_items == 0);
|
||||
circular_buffer_pos = 0;
|
||||
|
||||
#if 1
|
||||
printf("Statistics: \n");
|
||||
printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min);
|
||||
printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max);
|
||||
if (m_stat.extrusion_length > 0)
|
||||
m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length;
|
||||
printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg);
|
||||
m_stat.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
return output_buffer.data();
|
||||
}
|
||||
|
||||
// Is a white space?
|
||||
static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; }
|
||||
// Is it an end of line? Consider a comment to be an end of line as well.
|
||||
static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; };
|
||||
// Is it a white space or end of line?
|
||||
static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); };
|
||||
|
||||
// Eat whitespaces.
|
||||
static void eatws(const char *&line)
|
||||
{
|
||||
while (is_ws(*line))
|
||||
++ line;
|
||||
}
|
||||
|
||||
// Parse an int starting at the current position of a line.
|
||||
// If succeeded, the line pointer is advanced.
|
||||
static inline int parse_int(const char *&line)
|
||||
{
|
||||
char *endptr = NULL;
|
||||
long result = strtol(line, &endptr, 10);
|
||||
if (endptr == NULL || !is_ws_or_eol(*endptr))
|
||||
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int");
|
||||
line = endptr;
|
||||
return int(result);
|
||||
};
|
||||
|
||||
// Parse an int starting at the current position of a line.
|
||||
// If succeeded, the line pointer is advanced.
|
||||
static inline float parse_float(const char *&line)
|
||||
{
|
||||
char *endptr = NULL;
|
||||
float result = string_to_double_decimal_point(line, &endptr);
|
||||
if (endptr == NULL || !is_ws_or_eol(*endptr))
|
||||
throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float");
|
||||
line = endptr;
|
||||
return result;
|
||||
};
|
||||
|
||||
bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLine &buf)
|
||||
{
|
||||
static constexpr const char *EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:";
|
||||
|
||||
if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
|
||||
line += strlen(EXTRUSION_ROLE_TAG);
|
||||
int role = atoi(line);
|
||||
m_current_extrusion_role = ExtrusionRole(role);
|
||||
++ line_idx;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the type, copy the line to the buffer.
|
||||
buf.type = GCODELINETYPE_OTHER;
|
||||
buf.modified = false;
|
||||
if (buf.raw.size() < len + 1)
|
||||
buf.raw.assign(line, line + len + 1);
|
||||
else
|
||||
memcpy(buf.raw.data(), line, len);
|
||||
buf.raw[len] = 0;
|
||||
buf.raw_length = len;
|
||||
|
||||
memcpy(buf.pos_start, m_current_pos, sizeof(float)*5);
|
||||
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
|
||||
memset(buf.pos_provided, 0, 5);
|
||||
|
||||
buf.volumetric_extrusion_rate = 0.f;
|
||||
buf.volumetric_extrusion_rate_start = 0.f;
|
||||
buf.volumetric_extrusion_rate_end = 0.f;
|
||||
buf.max_volumetric_extrusion_rate_slope_positive = 0.f;
|
||||
buf.max_volumetric_extrusion_rate_slope_negative = 0.f;
|
||||
buf.extrusion_role = m_current_extrusion_role;
|
||||
|
||||
// Parse the G-code line, store the result into the buf.
|
||||
switch (toupper(*line ++)) {
|
||||
case 'G': {
|
||||
int gcode = parse_int(line);
|
||||
eatws(line);
|
||||
switch (gcode) {
|
||||
case 0:
|
||||
case 1:
|
||||
{
|
||||
// G0, G1: A FFF 3D printer does not make a difference between the two.
|
||||
float new_pos[5];
|
||||
memcpy(new_pos, m_current_pos, sizeof(float)*5);
|
||||
bool changed[5] = { false, false, false, false, false };
|
||||
while (!is_eol(*line)) {
|
||||
char axis = toupper(*line++);
|
||||
int i = -1;
|
||||
switch (axis) {
|
||||
case 'X':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
i = axis - 'X';
|
||||
break;
|
||||
case 'E':
|
||||
i = 3;
|
||||
break;
|
||||
case 'F':
|
||||
i = 4;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
if (i == -1)
|
||||
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
|
||||
buf.pos_provided[i] = true;
|
||||
new_pos[i] = parse_float(line);
|
||||
if (i == 3 && m_config->use_relative_e_distances.value)
|
||||
new_pos[i] += m_current_pos[i];
|
||||
changed[i] = new_pos[i] != m_current_pos[i];
|
||||
eatws(line);
|
||||
}
|
||||
if (changed[3]) {
|
||||
// Extrusion, retract or unretract.
|
||||
float diff = new_pos[3] - m_current_pos[3];
|
||||
if (diff < 0) {
|
||||
buf.type = GCODELINETYPE_RETRACT;
|
||||
m_retracted = true;
|
||||
} else if (! changed[0] && ! changed[1] && ! changed[2]) {
|
||||
// assert(m_retracted);
|
||||
buf.type = GCODELINETYPE_UNRETRACT;
|
||||
m_retracted = false;
|
||||
} else {
|
||||
assert(changed[0] || changed[1]);
|
||||
// Moving in XY plane.
|
||||
buf.type = GCODELINETYPE_EXTRUDE;
|
||||
// Calculate the volumetric extrusion rate.
|
||||
float diff[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
diff[i] = new_pos[i] - m_current_pos[i];
|
||||
// volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min]
|
||||
float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2];
|
||||
float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2);
|
||||
buf.volumetric_extrusion_rate = rate;
|
||||
buf.volumetric_extrusion_rate_start = rate;
|
||||
buf.volumetric_extrusion_rate_end = rate;
|
||||
m_stat.update(rate, sqrt(len2));
|
||||
if (rate < 40.f) {
|
||||
printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n",
|
||||
rate,
|
||||
int(line_idx),
|
||||
sqrt(len2), sqrt((diff[3]*diff[3])/len2),
|
||||
m_current_pos[0], m_current_pos[1], m_current_pos[2],
|
||||
new_pos[0], new_pos[1], new_pos[2]);
|
||||
}
|
||||
}
|
||||
} else if (changed[0] || changed[1] || changed[2]) {
|
||||
// Moving without extrusion.
|
||||
buf.type = GCODELINETYPE_MOVE;
|
||||
}
|
||||
memcpy(m_current_pos, new_pos, sizeof(float) * 5);
|
||||
break;
|
||||
}
|
||||
case 92:
|
||||
{
|
||||
// G92 : Set Position
|
||||
// Set a logical coordinate position to a new value without actually moving the machine motors.
|
||||
// Which axes to set?
|
||||
bool set = false;
|
||||
while (!is_eol(*line)) {
|
||||
char axis = toupper(*line++);
|
||||
switch (axis) {
|
||||
case 'X':
|
||||
case 'Y':
|
||||
case 'Z':
|
||||
m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
|
||||
set = true;
|
||||
break;
|
||||
case 'E':
|
||||
m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
|
||||
set = true;
|
||||
break;
|
||||
default:
|
||||
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
|
||||
}
|
||||
eatws(line);
|
||||
}
|
||||
assert(set);
|
||||
break;
|
||||
}
|
||||
case 10:
|
||||
case 22:
|
||||
// Firmware retract.
|
||||
buf.type = GCODELINETYPE_RETRACT;
|
||||
m_retracted = true;
|
||||
break;
|
||||
case 11:
|
||||
case 23:
|
||||
// Firmware unretract.
|
||||
buf.type = GCODELINETYPE_UNRETRACT;
|
||||
m_retracted = false;
|
||||
break;
|
||||
default:
|
||||
// Ignore the rest.
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'M': {
|
||||
int mcode = parse_int(line);
|
||||
eatws(line);
|
||||
switch (mcode) {
|
||||
default:
|
||||
// Ignore the rest of the M-codes.
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'T':
|
||||
{
|
||||
// Activate an extruder head.
|
||||
int new_extruder = parse_int(line);
|
||||
if (new_extruder != m_current_extruder) {
|
||||
m_current_extruder = new_extruder;
|
||||
m_retracted = true;
|
||||
buf.type = GCODELINETYPE_TOOL_CHANGE;
|
||||
} else {
|
||||
buf.type = GCODELINETYPE_NOOP;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buf.extruder_id = m_current_extruder;
|
||||
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
|
||||
|
||||
adjust_volumetric_rate();
|
||||
++ line_idx;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PressureEqualizer::output_gcode_line(GCodeLine &line)
|
||||
{
|
||||
if (! line.modified) {
|
||||
push_to_output(line.raw.data(), line.raw_length, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// The line was modified.
|
||||
// Find the comment.
|
||||
const char *comment = line.raw.data();
|
||||
while (*comment != ';' && *comment != 0) ++comment;
|
||||
if (*comment != ';')
|
||||
comment = NULL;
|
||||
|
||||
// Emit the line with lowered extrusion rates.
|
||||
float l2 = line.dist_xyz2();
|
||||
float l = sqrt(l2);
|
||||
size_t nSegments = size_t(ceil(l / m_max_segment_length));
|
||||
if (nSegments == 1) {
|
||||
// Just update this segment.
|
||||
push_line_to_output(line, line.feedrate() * line.volumetric_correction_avg(), comment);
|
||||
} else {
|
||||
bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end;
|
||||
// Update the initial and final feed rate values.
|
||||
line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate;
|
||||
line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate;
|
||||
float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]);
|
||||
// Limiting volumetric extrusion rate slope for this segment.
|
||||
float max_volumetric_extrusion_rate_slope = accelerating ?
|
||||
line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative;
|
||||
// Total time for the segment, corrected for the possibly lowered volumetric feed rate,
|
||||
// if accelerating / decelerating over the complete segment.
|
||||
float t_total = line.dist_xyz() / feed_avg;
|
||||
// Time of the acceleration / deceleration part of the segment, if accelerating / decelerating
|
||||
// with the maximum volumetric extrusion rate slope.
|
||||
float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope;
|
||||
float l_acc = l;
|
||||
float l_steady = 0.f;
|
||||
if (t_acc < t_total) {
|
||||
// One may achieve higher print speeds if part of the segment is not speed limited.
|
||||
float l_acc = t_acc * feed_avg;
|
||||
float l_steady = l - l_acc;
|
||||
if (l_steady < 0.5f * m_max_segment_length) {
|
||||
l_acc = l;
|
||||
l_steady = 0.f;
|
||||
} else
|
||||
nSegments = size_t(ceil(l_acc / m_max_segment_length));
|
||||
}
|
||||
float pos_start[5];
|
||||
float pos_end [5];
|
||||
float pos_end2 [4];
|
||||
memcpy(pos_start, line.pos_start, sizeof(float)*5);
|
||||
memcpy(pos_end , line.pos_end , sizeof(float)*5);
|
||||
if (l_steady > 0.f) {
|
||||
// There will be a steady feed segment emitted.
|
||||
if (accelerating) {
|
||||
// Prepare the final steady feed rate segment.
|
||||
memcpy(pos_end2, pos_end, sizeof(float)*4);
|
||||
float t = l_acc / l;
|
||||
for (int i = 0; i < 4; ++ i) {
|
||||
pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
|
||||
line.pos_provided[i] = true;
|
||||
}
|
||||
} else {
|
||||
// Emit the steady feed rate segment.
|
||||
float t = l_steady / l;
|
||||
for (int i = 0; i < 4; ++ i) {
|
||||
line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t;
|
||||
line.pos_provided[i] = true;
|
||||
}
|
||||
push_line_to_output(line, pos_start[4], comment);
|
||||
comment = NULL;
|
||||
memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
|
||||
memcpy(pos_start, line.pos_end, sizeof(float)*5);
|
||||
}
|
||||
}
|
||||
// Split the segment into pieces.
|
||||
for (size_t i = 1; i < nSegments; ++ i) {
|
||||
float t = float(i) / float(nSegments);
|
||||
for (size_t j = 0; j < 4; ++ j) {
|
||||
line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t;
|
||||
line.pos_provided[j] = true;
|
||||
}
|
||||
// Interpolate the feed rate at the center of the segment.
|
||||
push_line_to_output(line, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment);
|
||||
comment = NULL;
|
||||
memcpy(line.pos_start, line.pos_end, sizeof(float)*5);
|
||||
}
|
||||
if (l_steady > 0.f && accelerating) {
|
||||
for (int i = 0; i < 4; ++ i) {
|
||||
line.pos_end[i] = pos_end2[i];
|
||||
line.pos_provided[i] = true;
|
||||
}
|
||||
push_line_to_output(line, pos_end[4], comment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PressureEqualizer::adjust_volumetric_rate()
|
||||
{
|
||||
if (circular_buffer_items < 2)
|
||||
return;
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
const size_t idx_head = circular_buffer_idx_head();
|
||||
const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail());
|
||||
size_t idx = idx_tail;
|
||||
if (idx == idx_head || ! circular_buffer[idx].extruding())
|
||||
// Nothing to do, the last move is not extruding.
|
||||
return;
|
||||
|
||||
float feedrate_per_extrusion_role[numExtrusionRoles];
|
||||
for (size_t i = 0; i < numExtrusionRoles; ++ i)
|
||||
feedrate_per_extrusion_role[i] = FLT_MAX;
|
||||
feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start;
|
||||
|
||||
bool modified = true;
|
||||
while (modified && idx != idx_head) {
|
||||
size_t idx_prev = circular_buffer_idx_prev(idx);
|
||||
for (; ! circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev)) ;
|
||||
if (! circular_buffer[idx_prev].extruding())
|
||||
break;
|
||||
// Volumetric extrusion rate at the start of the succeding segment.
|
||||
float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start;
|
||||
// What is the gradient of the extrusion rate between idx_prev and idx?
|
||||
idx = idx_prev;
|
||||
GCodeLine &line = circular_buffer[idx];
|
||||
for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
|
||||
float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative;
|
||||
if (rate_slope == 0)
|
||||
// The negative rate is unlimited.
|
||||
continue;
|
||||
float rate_end = feedrate_per_extrusion_role[iRole];
|
||||
if (iRole == line.extrusion_role && rate_succ < rate_end)
|
||||
// Limit by the succeeding volumetric flow rate.
|
||||
rate_end = rate_succ;
|
||||
if (line.volumetric_extrusion_rate_end > rate_end) {
|
||||
line.volumetric_extrusion_rate_end = rate_end;
|
||||
line.modified = true;
|
||||
} else if (iRole == line.extrusion_role) {
|
||||
rate_end = line.volumetric_extrusion_rate_end;
|
||||
} else if (rate_end == FLT_MAX) {
|
||||
// The rate for ExtrusionRole iRole is unlimited.
|
||||
continue;
|
||||
} else {
|
||||
// Use the original, 'floating' extrusion rate as a starting point for the limiter.
|
||||
}
|
||||
// modified = false;
|
||||
float rate_start = rate_end + rate_slope * line.time_corrected();
|
||||
if (rate_start < line.volumetric_extrusion_rate_start) {
|
||||
// Limit the volumetric extrusion rate at the start of this segment due to a segment
|
||||
// of ExtrusionType iRole, which will be extruded in the future.
|
||||
line.volumetric_extrusion_rate_start = rate_start;
|
||||
line.max_volumetric_extrusion_rate_slope_negative = rate_slope;
|
||||
line.modified = true;
|
||||
// modified = true;
|
||||
}
|
||||
feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
|
||||
}
|
||||
}
|
||||
|
||||
// Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
for (size_t i = 0; i < numExtrusionRoles; ++ i)
|
||||
feedrate_per_extrusion_role[i] = FLT_MAX;
|
||||
feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end;
|
||||
|
||||
assert(circular_buffer[idx].extruding());
|
||||
while (idx != idx_tail) {
|
||||
size_t idx_next = circular_buffer_idx_next(idx);
|
||||
for (; ! circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next)) ;
|
||||
if (! circular_buffer[idx_next].extruding())
|
||||
break;
|
||||
float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end;
|
||||
// What is the gradient of the extrusion rate between idx_prev and idx?
|
||||
idx = idx_next;
|
||||
GCodeLine &line = circular_buffer[idx];
|
||||
for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
|
||||
float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive;
|
||||
if (rate_slope == 0)
|
||||
// The positive rate is unlimited.
|
||||
continue;
|
||||
float rate_start = feedrate_per_extrusion_role[iRole];
|
||||
if (iRole == line.extrusion_role && rate_prec < rate_start)
|
||||
rate_start = rate_prec;
|
||||
if (line.volumetric_extrusion_rate_start > rate_start) {
|
||||
line.volumetric_extrusion_rate_start = rate_start;
|
||||
line.modified = true;
|
||||
} else if (iRole == line.extrusion_role) {
|
||||
rate_start = line.volumetric_extrusion_rate_start;
|
||||
} else if (rate_start == FLT_MAX) {
|
||||
// The rate for ExtrusionRole iRole is unlimited.
|
||||
continue;
|
||||
} else {
|
||||
// Use the original, 'floating' extrusion rate as a starting point for the limiter.
|
||||
}
|
||||
float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected();
|
||||
if (rate_end < line.volumetric_extrusion_rate_end) {
|
||||
// Limit the volumetric extrusion rate at the start of this segment due to a segment
|
||||
// of ExtrusionType iRole, which was extruded before.
|
||||
line.volumetric_extrusion_rate_end = rate_end;
|
||||
line.max_volumetric_extrusion_rate_slope_positive = rate_slope;
|
||||
line.modified = true;
|
||||
}
|
||||
feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol)
|
||||
{
|
||||
char buf[2048];
|
||||
int len = sprintf(buf,
|
||||
(axis == 'E') ? " %c%.3f" : " %c%.5f",
|
||||
axis, value);
|
||||
push_to_output(buf, len, add_eol);
|
||||
}
|
||||
|
||||
void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol)
|
||||
{
|
||||
// New length of the output buffer content.
|
||||
size_t len_new = output_buffer_length + len + 1;
|
||||
if (add_eol)
|
||||
++ len_new;
|
||||
|
||||
// Resize the output buffer to a power of 2 higher than the required memory.
|
||||
if (output_buffer.size() < len_new) {
|
||||
size_t v = len_new;
|
||||
// Compute the next highest power of 2 of 32-bit v
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html
|
||||
v--;
|
||||
v |= v >> 1;
|
||||
v |= v >> 2;
|
||||
v |= v >> 4;
|
||||
v |= v >> 8;
|
||||
v |= v >> 16;
|
||||
v++;
|
||||
output_buffer.resize(v);
|
||||
}
|
||||
|
||||
// Copy the text to the output.
|
||||
if (len != 0) {
|
||||
memcpy(output_buffer.data() + output_buffer_length, text, len);
|
||||
output_buffer_length += len;
|
||||
}
|
||||
if (add_eol)
|
||||
output_buffer[output_buffer_length ++] = '\n';
|
||||
output_buffer[output_buffer_length] = 0;
|
||||
}
|
||||
|
||||
void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment)
|
||||
{
|
||||
push_to_output("G1", 2, false);
|
||||
for (char i = 0; i < 3; ++ i)
|
||||
if (line.pos_provided[i])
|
||||
push_axis_to_output('X'+i, line.pos_end[i]);
|
||||
push_axis_to_output('E', m_config->use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]);
|
||||
// if (line.pos_provided[4] || fabs(line.feedrate() - new_feedrate) > 1e-5)
|
||||
push_axis_to_output('F', new_feedrate);
|
||||
// output comment and EOL
|
||||
push_to_output(comment, (comment == NULL) ? 0 : strlen(comment), true);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
212
src/libslic3r/GCode/PressureEqualizer.hpp
Normal file
212
src/libslic3r/GCode/PressureEqualizer.hpp
Normal file
@@ -0,0 +1,212 @@
|
||||
#ifndef slic3r_GCode_PressureEqualizer_hpp_
|
||||
#define slic3r_GCode_PressureEqualizer_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Processes a G-code. Finds changes in the volumetric extrusion speed and adjusts the transitions
|
||||
// between these paths to limit fast changes in the volumetric extrusion speed.
|
||||
class PressureEqualizer
|
||||
{
|
||||
public:
|
||||
PressureEqualizer(const Slic3r::GCodeConfig *config);
|
||||
~PressureEqualizer();
|
||||
|
||||
void reset();
|
||||
|
||||
// Process a next batch of G-code lines. Flush the internal buffers if asked for.
|
||||
const char* process(const char *szGCode, bool flush);
|
||||
|
||||
size_t get_output_buffer_length() const { return output_buffer_length; }
|
||||
|
||||
private:
|
||||
struct Statistics
|
||||
{
|
||||
void reset() {
|
||||
volumetric_extrusion_rate_min = std::numeric_limits<float>::max();
|
||||
volumetric_extrusion_rate_max = 0.f;
|
||||
volumetric_extrusion_rate_avg = 0.f;
|
||||
extrusion_length = 0.f;
|
||||
}
|
||||
void update(float volumetric_extrusion_rate, float length) {
|
||||
volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate);
|
||||
volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate);
|
||||
volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length;
|
||||
extrusion_length += length;
|
||||
}
|
||||
float volumetric_extrusion_rate_min;
|
||||
float volumetric_extrusion_rate_max;
|
||||
float volumetric_extrusion_rate_avg;
|
||||
float extrusion_length;
|
||||
};
|
||||
|
||||
struct Statistics m_stat;
|
||||
|
||||
// Keeps the reference, does not own the config.
|
||||
const Slic3r::GCodeConfig *m_config;
|
||||
|
||||
// Private configuration values
|
||||
// How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2
|
||||
struct ExtrusionRateSlope {
|
||||
float positive;
|
||||
float negative;
|
||||
};
|
||||
enum { numExtrusionRoles = erSupportMaterialInterface + 1 };
|
||||
ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles];
|
||||
float m_max_volumetric_extrusion_rate_slope_positive;
|
||||
float m_max_volumetric_extrusion_rate_slope_negative;
|
||||
// Maximum segment length to split a long segment, if the initial and the final flow rate differ.
|
||||
float m_max_segment_length;
|
||||
|
||||
// Configuration extracted from config.
|
||||
// Area of the crossestion of each filament. Necessary to calculate the volumetric flow rate.
|
||||
std::vector<float> m_filament_crossections;
|
||||
|
||||
// Internal data.
|
||||
// X,Y,Z,E,F
|
||||
float m_current_pos[5];
|
||||
size_t m_current_extruder;
|
||||
ExtrusionRole m_current_extrusion_role;
|
||||
bool m_retracted;
|
||||
|
||||
enum GCodeLineType
|
||||
{
|
||||
GCODELINETYPE_INVALID,
|
||||
GCODELINETYPE_NOOP,
|
||||
GCODELINETYPE_OTHER,
|
||||
GCODELINETYPE_RETRACT,
|
||||
GCODELINETYPE_UNRETRACT,
|
||||
GCODELINETYPE_TOOL_CHANGE,
|
||||
GCODELINETYPE_MOVE,
|
||||
GCODELINETYPE_EXTRUDE,
|
||||
};
|
||||
|
||||
struct GCodeLine
|
||||
{
|
||||
GCodeLine() :
|
||||
type(GCODELINETYPE_INVALID),
|
||||
raw_length(0),
|
||||
modified(false),
|
||||
extruder_id(0),
|
||||
volumetric_extrusion_rate(0.f),
|
||||
volumetric_extrusion_rate_start(0.f),
|
||||
volumetric_extrusion_rate_end(0.f)
|
||||
{}
|
||||
|
||||
bool moving_xy() const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; }
|
||||
bool moving_z () const { return fabs(pos_end[2] - pos_start[2]) > 0.f; }
|
||||
bool extruding() const { return moving_xy() && pos_end[3] > pos_start[3]; }
|
||||
bool retracting() const { return pos_end[3] < pos_start[3]; }
|
||||
bool deretracting() const { return ! moving_xy() && pos_end[3] > pos_start[3]; }
|
||||
|
||||
float dist_xy2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); }
|
||||
float dist_xyz2() const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); }
|
||||
float dist_xy() const { return sqrt(dist_xy2()); }
|
||||
float dist_xyz() const { return sqrt(dist_xyz2()); }
|
||||
float dist_e() const { return fabs(pos_end[3] - pos_start[3]); }
|
||||
|
||||
float feedrate() const { return pos_end[4]; }
|
||||
float time() const { return dist_xyz() / feedrate(); }
|
||||
float time_inv() const { return feedrate() / dist_xyz(); }
|
||||
float volumetric_correction_avg() const {
|
||||
float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate;
|
||||
assert(avg_correction > 0.f);
|
||||
assert(avg_correction <= 1.00000001f);
|
||||
return avg_correction;
|
||||
}
|
||||
float time_corrected() const { return time() * volumetric_correction_avg(); }
|
||||
|
||||
GCodeLineType type;
|
||||
|
||||
// We try to keep the string buffer once it has been allocated, so it will not be reallocated over and over.
|
||||
std::vector<char> raw;
|
||||
size_t raw_length;
|
||||
// If modified, the raw text has to be adapted by the new extrusion rate,
|
||||
// or maybe the line needs to be split into multiple lines.
|
||||
bool modified;
|
||||
|
||||
// float timeStart;
|
||||
// float timeEnd;
|
||||
// X,Y,Z,E,F. Storing the state of the currently active extruder only.
|
||||
float pos_start[5];
|
||||
float pos_end[5];
|
||||
// Was the axis found on the G-code line? X,Y,Z,F
|
||||
bool pos_provided[5];
|
||||
|
||||
// Index of the active extruder.
|
||||
size_t extruder_id;
|
||||
// Extrusion role of this segment.
|
||||
ExtrusionRole extrusion_role;
|
||||
|
||||
// Current volumetric extrusion rate.
|
||||
float volumetric_extrusion_rate;
|
||||
// Volumetric extrusion rate at the start of this segment.
|
||||
float volumetric_extrusion_rate_start;
|
||||
// Volumetric extrusion rate at the end of this segment.
|
||||
float volumetric_extrusion_rate_end;
|
||||
|
||||
// Volumetric extrusion rate slope limiting this segment.
|
||||
// If set to zero, the slope is unlimited.
|
||||
float max_volumetric_extrusion_rate_slope_positive;
|
||||
float max_volumetric_extrusion_rate_slope_negative;
|
||||
};
|
||||
|
||||
// Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size.
|
||||
std::vector<GCodeLine> circular_buffer;
|
||||
// Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten).
|
||||
size_t circular_buffer_pos;
|
||||
// Circular buffer size, configuration value.
|
||||
size_t circular_buffer_size;
|
||||
// Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size.
|
||||
size_t circular_buffer_items;
|
||||
|
||||
// Output buffer will only grow. It will not be reallocated over and over.
|
||||
std::vector<char> output_buffer;
|
||||
size_t output_buffer_length;
|
||||
|
||||
// For debugging purposes. Index of the G-code line processed.
|
||||
size_t line_idx;
|
||||
|
||||
bool process_line(const char *line, const size_t len, GCodeLine &buf);
|
||||
void output_gcode_line(GCodeLine &buf);
|
||||
|
||||
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
|
||||
// Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
|
||||
void adjust_volumetric_rate();
|
||||
|
||||
// Push the text to the end of the output_buffer.
|
||||
void push_to_output(const char *text, const size_t len, bool add_eol = true);
|
||||
// Push an axis assignment to the end of the output buffer.
|
||||
void push_axis_to_output(const char axis, const float value, bool add_eol = false);
|
||||
// Push a G-code line to the output,
|
||||
void push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment);
|
||||
|
||||
size_t circular_buffer_idx_head() const {
|
||||
size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items;
|
||||
if (idx >= circular_buffer_size)
|
||||
idx -= circular_buffer_size;
|
||||
return idx;
|
||||
}
|
||||
|
||||
size_t circular_buffer_idx_tail() const { return circular_buffer_pos; }
|
||||
|
||||
size_t circular_buffer_idx_prev(size_t idx) const {
|
||||
idx += circular_buffer_size - 1;
|
||||
if (idx >= circular_buffer_size)
|
||||
idx -= circular_buffer_size;
|
||||
return idx;
|
||||
}
|
||||
|
||||
size_t circular_buffer_idx_next(size_t idx) const {
|
||||
if (++ idx >= circular_buffer_size)
|
||||
idx -= circular_buffer_size;
|
||||
return idx;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GCode_PressureEqualizer_hpp_ */
|
||||
192
src/libslic3r/GCode/PrintExtents.cpp
Normal file
192
src/libslic3r/GCode/PrintExtents.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
// Calculate extents of the extrusions assigned to Print / PrintObject.
|
||||
// The extents are used for assessing collisions of the print with the priming towers,
|
||||
// to decide whether to pause the print after the priming towers are extruded
|
||||
// to let the operator remove them from the print bed.
|
||||
|
||||
#include "../BoundingBox.hpp"
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Print.hpp"
|
||||
|
||||
#include "PrintExtents.hpp"
|
||||
#include "WipeTower.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, const coord_t radius)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
if (! polyline.points.empty())
|
||||
bbox.merge(polyline.points.front());
|
||||
for (const Point &pt : polyline.points) {
|
||||
bbox.min(0) = std::min(bbox.min(0), pt(0) - radius);
|
||||
bbox.min(1) = std::min(bbox.min(1), pt(1) - radius);
|
||||
bbox.max(0) = std::max(bbox.max(0), pt(0) + radius);
|
||||
bbox.max(1) = std::max(bbox.max(1), pt(1) + radius);
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path)
|
||||
{
|
||||
BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
bboxf.max = unscale(bbox.max);
|
||||
bboxf.defined = true;
|
||||
}
|
||||
return bboxf;
|
||||
}
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusion_loop)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionPath &extrusion_path : extrusion_loop.paths)
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
bboxf.max = unscale(bbox.max);
|
||||
bboxf.defined = true;
|
||||
}
|
||||
return bboxf;
|
||||
}
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &extrusion_multi_path)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths)
|
||||
bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))));
|
||||
BoundingBoxf bboxf;
|
||||
if (! empty(bbox)) {
|
||||
bboxf.min = unscale(bbox.min);
|
||||
bboxf.max = unscale(bbox.max);
|
||||
bboxf.defined = true;
|
||||
}
|
||||
return bboxf;
|
||||
}
|
||||
|
||||
static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity);
|
||||
|
||||
static inline BoundingBoxf extrusionentity_extents(const ExtrusionEntityCollection &extrusion_entity_collection)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
for (const ExtrusionEntity *extrusion_entity : extrusion_entity_collection.entities)
|
||||
bbox.merge(extrusionentity_extents(extrusion_entity));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_entity)
|
||||
{
|
||||
if (extrusion_entity == nullptr)
|
||||
return BoundingBoxf();
|
||||
auto *extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
|
||||
if (extrusion_path != nullptr)
|
||||
return extrusionentity_extents(*extrusion_path);
|
||||
auto *extrusion_loop = dynamic_cast<const ExtrusionLoop*>(extrusion_entity);
|
||||
if (extrusion_loop != nullptr)
|
||||
return extrusionentity_extents(*extrusion_loop);
|
||||
auto *extrusion_multi_path = dynamic_cast<const ExtrusionMultiPath*>(extrusion_entity);
|
||||
if (extrusion_multi_path != nullptr)
|
||||
return extrusionentity_extents(*extrusion_multi_path);
|
||||
auto *extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
|
||||
if (extrusion_entity_collection != nullptr)
|
||||
return extrusionentity_extents(*extrusion_entity_collection);
|
||||
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()");
|
||||
return BoundingBoxf();
|
||||
}
|
||||
|
||||
BoundingBoxf get_print_extrusions_extents(const Print &print)
|
||||
{
|
||||
//QDS: usage of m_brim are deleted, the bbx of skrit is always larger than that of brim
|
||||
BoundingBoxf bbox(extrusionentity_extents(print.skirt()));
|
||||
return bbox;
|
||||
}
|
||||
|
||||
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
for (const Layer *layer : print_object.layers()) {
|
||||
if (layer->print_z > max_print_z)
|
||||
break;
|
||||
BoundingBoxf bbox_this;
|
||||
for (const LayerRegion *layerm : layer->regions()) {
|
||||
bbox_this.merge(extrusionentity_extents(layerm->perimeters));
|
||||
for (const ExtrusionEntity *ee : layerm->fills.entities)
|
||||
// fill represents infill extrusions of a single island.
|
||||
bbox_this.merge(extrusionentity_extents(*dynamic_cast<const ExtrusionEntityCollection*>(ee)));
|
||||
}
|
||||
const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
|
||||
if (support_layer)
|
||||
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
|
||||
bbox_this.merge(extrusionentity_extents(extrusion_entity));
|
||||
for (const PrintInstance &instance : print_object.instances()) {
|
||||
BoundingBoxf bbox_translated(bbox_this);
|
||||
bbox_translated.translate(unscale(instance.shift));
|
||||
bbox.merge(bbox_translated);
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
|
||||
// The projection does not contain the priming regions.
|
||||
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z)
|
||||
{
|
||||
// Wipe tower extrusions are saved as if the tower was at the origin with no rotation
|
||||
// We need to get position and angle of the wipe tower to transform them to actual position.
|
||||
int plate_idx = print.get_plate_index();
|
||||
Vec3d plate_origin = print.get_plate_origin();
|
||||
double wipe_tower_x = print.config().wipe_tower_x.get_at(plate_idx) + plate_origin(0);
|
||||
double wipe_tower_y = print.config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
||||
Transform2d trafo =
|
||||
Eigen::Translation2d(wipe_tower_x, wipe_tower_y) *
|
||||
Eigen::Rotation2Dd(Geometry::deg2rad(print.config().wipe_tower_rotation_angle.value));
|
||||
|
||||
BoundingBoxf bbox;
|
||||
for (const std::vector<WipeTower::ToolChangeResult> &tool_changes : print.wipe_tower_data().tool_changes) {
|
||||
if (! tool_changes.empty() && tool_changes.front().print_z > max_print_z)
|
||||
break;
|
||||
for (const WipeTower::ToolChangeResult &tcr : tool_changes) {
|
||||
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
|
||||
const WipeTower::Extrusion &e = tcr.extrusions[i];
|
||||
if (e.width > 0) {
|
||||
Vec2d delta = 0.5 * Vec2d(e.width, e.width);
|
||||
Vec2d p1 = trafo * (&e - 1)->pos.cast<double>();
|
||||
Vec2d p2 = trafo * e.pos.cast<double>();
|
||||
bbox.merge(p1.cwiseMin(p2) - delta);
|
||||
bbox.merge(p1.cwiseMax(p2) + delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
// Returns a bounding box of the wipe tower priming extrusions.
|
||||
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print)
|
||||
{
|
||||
BoundingBoxf bbox;
|
||||
if (print.wipe_tower_data().priming != nullptr) {
|
||||
for (const WipeTower::ToolChangeResult &tcr : *print.wipe_tower_data().priming) {
|
||||
for (size_t i = 1; i < tcr.extrusions.size(); ++ i) {
|
||||
const WipeTower::Extrusion &e = tcr.extrusions[i];
|
||||
if (e.width > 0) {
|
||||
const Vec2d& p1 = (&e - 1)->pos.cast<double>();
|
||||
const Vec2d& p2 = e.pos.cast<double>();
|
||||
bbox.merge(p1);
|
||||
coordf_t radius = 0.5 * e.width;
|
||||
bbox.min(0) = std::min(bbox.min(0), std::min(p1(0), p2(0)) - radius);
|
||||
bbox.min(1) = std::min(bbox.min(1), std::min(p1(1), p2(1)) - radius);
|
||||
bbox.max(0) = std::max(bbox.max(0), std::max(p1(0), p2(0)) + radius);
|
||||
bbox.max(1) = std::max(bbox.max(1), std::max(p1(1), p2(1)) + radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
}
|
||||
|
||||
}
|
||||
30
src/libslic3r/GCode/PrintExtents.hpp
Normal file
30
src/libslic3r/GCode/PrintExtents.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
// Measure extents of the planned extrusions.
|
||||
// To be used for collision reporting.
|
||||
|
||||
#ifndef slic3r_PrintExtents_hpp_
|
||||
#define slic3r_PrintExtents_hpp_
|
||||
|
||||
#include "libslic3r.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class BoundingBoxf;
|
||||
|
||||
// Returns a bounding box of a projection of the brim and skirt.
|
||||
BoundingBoxf get_print_extrusions_extents(const Print &print);
|
||||
|
||||
// Returns a bounding box of a projection of the object extrusions at z <= max_print_z.
|
||||
BoundingBoxf get_print_object_extrusions_extents(const PrintObject &print_object, const coordf_t max_print_z);
|
||||
|
||||
// Returns a bounding box of a projection of the wipe tower for the layers <= max_print_z.
|
||||
// The projection does not contain the priming regions.
|
||||
BoundingBoxf get_wipe_tower_extrusions_extents(const Print &print, const coordf_t max_print_z);
|
||||
|
||||
// Returns a bounding box of the wipe tower priming extrusions.
|
||||
BoundingBoxf get_wipe_tower_priming_extrusions_extents(const Print &print);
|
||||
|
||||
};
|
||||
|
||||
#endif /* slic3r_PrintExtents_hpp_ */
|
||||
54
src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp
Normal file
54
src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
#include "RetractWhenCrossingPerimeters.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &layer, const Polyline &travel)
|
||||
{
|
||||
if (m_layer != &layer) {
|
||||
// Update cache.
|
||||
m_layer = &layer;
|
||||
m_internal_islands.clear();
|
||||
m_aabbtree_internal_islands.clear();
|
||||
// Collect expolygons of internal slices.
|
||||
for (const LayerRegion *layerm : layer.regions())
|
||||
for (const Surface &surface : layerm->get_slices().surfaces)
|
||||
if (surface.is_internal())
|
||||
m_internal_islands.emplace_back(&surface.expolygon);
|
||||
// Calculate bounding boxes of internal slices.
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(m_internal_islands.size());
|
||||
for (size_t i = 0; i < m_internal_islands.size(); ++ i)
|
||||
bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));
|
||||
// Build AABB tree over bounding boxes of internal slices.
|
||||
m_aabbtree_internal_islands.build_modify_input(bboxes);
|
||||
}
|
||||
|
||||
BoundingBox bbox_travel = get_extents(travel);
|
||||
AABBTree::BoundingBox bbox_travel_eigen{ bbox_travel.min, bbox_travel.max };
|
||||
int result = -1;
|
||||
bbox_travel.offset(SCALED_EPSILON);
|
||||
AABBTreeIndirect::traverse(m_aabbtree_internal_islands,
|
||||
[&bbox_travel_eigen](const AABBTree::Node &node) {
|
||||
return bbox_travel_eigen.intersects(node.bbox);
|
||||
},
|
||||
[&travel, &bbox_travel, &result, &islands = m_internal_islands](const AABBTree::Node &node) {
|
||||
assert(node.is_leaf());
|
||||
assert(node.is_valid());
|
||||
Polygons clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*islands[node.idx], bbox_travel);
|
||||
if (diff_pl(travel, clipped).empty()) {
|
||||
// Travel path is completely inside an "internal" island. Don't retract.
|
||||
result = int(node.idx);
|
||||
// Stop traversal.
|
||||
return false;
|
||||
}
|
||||
// Continue traversal.
|
||||
return true;
|
||||
});
|
||||
return result != -1;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
32
src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp
Normal file
32
src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef slic3r_RetractWhenCrossingPerimeters_hpp_
|
||||
#define slic3r_RetractWhenCrossingPerimeters_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "../AABBTreeIndirect.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class ExPolygon;
|
||||
class Layer;
|
||||
class Polyline;
|
||||
|
||||
class RetractWhenCrossingPerimeters
|
||||
{
|
||||
public:
|
||||
bool travel_inside_internal_regions(const Layer &layer, const Polyline &travel);
|
||||
|
||||
private:
|
||||
// Last object layer visited, for which a cache of internal islands was created.
|
||||
const Layer *m_layer;
|
||||
// Internal islands only, referencing data owned by m_layer->regions()->surfaces().
|
||||
std::vector<const ExPolygon*> m_internal_islands;
|
||||
// Search structure over internal islands.
|
||||
using AABBTree = AABBTreeIndirect::Tree<2, coord_t>;
|
||||
AABBTree m_aabbtree_internal_islands;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_RetractWhenCrossingPerimeters_hpp_
|
||||
1385
src/libslic3r/GCode/SeamPlacer.cpp
Normal file
1385
src/libslic3r/GCode/SeamPlacer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
167
src/libslic3r/GCode/SeamPlacer.hpp
Normal file
167
src/libslic3r/GCode/SeamPlacer.hpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#ifndef libslic3r_SeamPlacer_hpp_
|
||||
#define libslic3r_SeamPlacer_hpp_
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/ExtrusionEntity.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/AABBTreeIndirect.hpp"
|
||||
#include "libslic3r/KDTreeIndirect.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintObject;
|
||||
class ExtrusionLoop;
|
||||
class Print;
|
||||
class Layer;
|
||||
|
||||
namespace EdgeGrid {
|
||||
class Grid;
|
||||
}
|
||||
|
||||
namespace SeamPlacerImpl {
|
||||
|
||||
// ************ FOR BACKPORT COMPATIBILITY ONLY ***************
|
||||
// Angle from v1 to v2, returning double atan2(y, x) normalized to <-PI, PI>.
|
||||
template<typename Derived, typename Derived2> inline double angle(const Eigen::MatrixBase<Derived> &v1, const Eigen::MatrixBase<Derived2> &v2)
|
||||
{
|
||||
static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "angle(): first parameter is not a 2D vector");
|
||||
static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "angle(): second parameter is not a 2D vector");
|
||||
auto v1d = v1.template cast<double>();
|
||||
auto v2d = v2.template cast<double>();
|
||||
return atan2(cross2(v1d, v2d), v1d.dot(v2d));
|
||||
}
|
||||
// ***************************
|
||||
|
||||
struct GlobalModelInfo;
|
||||
struct SeamComparator;
|
||||
|
||||
enum class EnforcedBlockedSeamPoint {
|
||||
Blocked = 0,
|
||||
Neutral = 1,
|
||||
Enforced = 2,
|
||||
};
|
||||
|
||||
// struct representing single perimeter loop
|
||||
struct Perimeter
|
||||
{
|
||||
size_t start_index{};
|
||||
size_t end_index{}; // inclusive!
|
||||
size_t seam_index{};
|
||||
float flow_width{};
|
||||
|
||||
// During alignment, a final position may be stored here. In that case, finalized is set to true.
|
||||
// Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position
|
||||
// Random position also uses this flexibility to set final seam point position
|
||||
bool finalized = false;
|
||||
Vec3f final_seam_position = Vec3f::Zero();
|
||||
};
|
||||
|
||||
// Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created,
|
||||
// then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam.
|
||||
// This seam position can be then further aligned
|
||||
struct SeamCandidate
|
||||
{
|
||||
SeamCandidate(const Vec3f &pos, Perimeter &perimeter, float local_ccw_angle, EnforcedBlockedSeamPoint type)
|
||||
: position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle(local_ccw_angle), type(type), central_enforcer(false)
|
||||
{}
|
||||
const Vec3f position;
|
||||
// pointer to Perimeter loop of this point. It is shared across all points of the loop
|
||||
Perimeter &perimeter;
|
||||
float visibility;
|
||||
float overhang;
|
||||
// distance inside the merged layer regions, for detecting perimeter points which are hidden indside the print (e.g. multimaterial join)
|
||||
// Negative sign means inside the print, comes from EdgeGrid structure
|
||||
float embedded_distance;
|
||||
float local_ccw_angle;
|
||||
EnforcedBlockedSeamPoint type;
|
||||
bool central_enforcer; // marks this candidate as central point of enforced segment on the perimeter - important for alignment
|
||||
};
|
||||
|
||||
struct SeamCandidateCoordinateFunctor
|
||||
{
|
||||
SeamCandidateCoordinateFunctor(const std::vector<SeamCandidate> &seam_candidates) : seam_candidates(seam_candidates) {}
|
||||
const std::vector<SeamCandidate> &seam_candidates;
|
||||
float operator()(size_t index, size_t dim) const { return seam_candidates[index].position[dim]; }
|
||||
};
|
||||
} // namespace SeamPlacerImpl
|
||||
|
||||
struct PrintObjectSeamData
|
||||
{
|
||||
using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>;
|
||||
|
||||
struct LayerSeams
|
||||
{
|
||||
Slic3r::deque<SeamPlacerImpl::Perimeter> perimeters;
|
||||
std::vector<SeamPlacerImpl::SeamCandidate> points;
|
||||
std::unique_ptr<SeamCandidatesTree> points_tree;
|
||||
};
|
||||
// Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter
|
||||
std::vector<LayerSeams> layers;
|
||||
// Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD
|
||||
// tree of all points of the given layer
|
||||
|
||||
void clear() { layers.clear(); }
|
||||
};
|
||||
|
||||
class SeamPlacer
|
||||
{
|
||||
public:
|
||||
// Number of samples generated on the mesh. There are sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples
|
||||
static constexpr size_t raycasting_visibility_samples_count = 30000;
|
||||
static constexpr size_t fast_decimation_triangle_count_target = 16000;
|
||||
//square of number of rays per sample point
|
||||
static constexpr size_t sqr_rays_per_sample_point = 5;
|
||||
|
||||
// snapping angle - angles larger than this value will be snapped to during seam painting
|
||||
static constexpr float sharp_angle_snapping_threshold = 55.0f * float(PI) / 180.0f;
|
||||
// overhang angle for seam placement that still yields good results, in degrees, measured from vertical direction
|
||||
//QDS
|
||||
static constexpr float overhang_angle_threshold = 45.0f * float(PI) / 180.0f;
|
||||
|
||||
// determines angle importance compared to visibility ( neutral value is 1.0f. )
|
||||
static constexpr float angle_importance_aligned = 0.6f;
|
||||
static constexpr float angle_importance_nearest = 1.0f; // use much higher angle importance for nearest mode, to combat the visibility info noise
|
||||
|
||||
// For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size
|
||||
static constexpr float enforcer_oversampling_distance = 0.2f;
|
||||
|
||||
// When searching for seam clusters for alignment:
|
||||
// following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer
|
||||
static constexpr float seam_align_score_tolerance = 0.3f;
|
||||
// seam_align_tolerable_dist_factor - how far to search for seam from current position, final dist is seam_align_tolerable_dist_factor * flow_width
|
||||
static constexpr float seam_align_tolerable_dist_factor = 4.0f;
|
||||
// minimum number of seams needed in cluster to make alignment happen
|
||||
static constexpr size_t seam_align_minimum_string_seams = 6;
|
||||
// millimeters covered by spline; determines number of splines for the given string
|
||||
static constexpr size_t seam_align_mm_per_segment = 4.0f;
|
||||
|
||||
// The following data structures hold all perimeter points for all PrintObject.
|
||||
std::unordered_map<const PrintObject *, PrintObjectSeamData> m_seam_per_object;
|
||||
|
||||
void init(const Print &print, std::function<void(void)> throw_if_canceled_func);
|
||||
|
||||
void place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
|
||||
|
||||
private:
|
||||
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info, const SeamPosition configured_seam_preference);
|
||||
void calculate_candidates_visibility(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);
|
||||
void calculate_overhangs_and_layer_embedding(const PrintObject *po);
|
||||
void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator);
|
||||
std::vector<std::pair<size_t, size_t>> find_seam_string(const PrintObject *po, std::pair<size_t, size_t> start_seam, const SeamPlacerImpl::SeamComparator &comparator) const;
|
||||
std::optional<std::pair<size_t, size_t>> find_next_seam_in_layer(const std::vector<PrintObjectSeamData::LayerSeams> &layers,
|
||||
const Vec3f & projected_position,
|
||||
const size_t layer_idx,
|
||||
const float max_distance,
|
||||
const SeamPlacerImpl::SeamComparator & comparator) const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // libslic3r_SeamPlacer_hpp_
|
||||
218
src/libslic3r/GCode/SpiralVase.cpp
Normal file
218
src/libslic3r/GCode/SpiralVase.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
#include "SpiralVase.hpp"
|
||||
#include "GCode.hpp"
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace SpiralVaseHelpers {
|
||||
/** == Smooth Spiral Helpers == */
|
||||
/** Distance between a and b */
|
||||
float distance(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) {
|
||||
return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
|
||||
}
|
||||
|
||||
SpiralVase::SpiralPoint subtract(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
|
||||
{
|
||||
return SpiralVase::SpiralPoint(a.x - b.x, a.y - b.y);
|
||||
}
|
||||
|
||||
SpiralVase::SpiralPoint add(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) {
|
||||
return SpiralVase::SpiralPoint(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
SpiralVase::SpiralPoint scale(SpiralVase::SpiralPoint a, float factor) {
|
||||
return SpiralVase::SpiralPoint(a.x * factor, a.y * factor);
|
||||
}
|
||||
|
||||
/** dot product */
|
||||
float dot(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
/** Find the point on line ab closes to point c */
|
||||
SpiralVase::SpiralPoint nearest_point_on_line(SpiralVase::SpiralPoint c, SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b, float& dist)
|
||||
{
|
||||
SpiralVase::SpiralPoint ab = subtract(b, a);
|
||||
SpiralVase::SpiralPoint ac = subtract(c, a);
|
||||
float t = dot(ac, ab) / dot(ab, ab);
|
||||
t = t > 1 ? 1 : t;
|
||||
t = t < 0 ? 0 : t;
|
||||
SpiralVase::SpiralPoint closest = SpiralVase::SpiralPoint(add(a, scale(ab, t)));
|
||||
dist = distance(c, closest);
|
||||
return closest;
|
||||
}
|
||||
|
||||
/** Given a set of lines defined by points such as line[n] is the line from points[n] to points[n+1],
|
||||
* find the closest point to p that falls on any of the lines */
|
||||
SpiralVase::SpiralPoint nearest_point_on_lines(SpiralVase::SpiralPoint p,
|
||||
std::shared_ptr<std::vector<SpiralVase::SpiralPoint>> points,
|
||||
bool& found,
|
||||
float& dist)
|
||||
{
|
||||
if (points->size() < 2) {
|
||||
found = false;
|
||||
return SpiralVase::SpiralPoint(0, 0);
|
||||
}
|
||||
float min = std::numeric_limits<float>::max();
|
||||
SpiralVase::SpiralPoint closest(0, 0);
|
||||
for (unsigned long i = 0; i < points->size() - 1; i++) {
|
||||
float currentDist = 0;
|
||||
SpiralVase::SpiralPoint current = nearest_point_on_line(p, points->at(i), points->at(i + 1), currentDist);
|
||||
if (currentDist < min) {
|
||||
min = currentDist;
|
||||
closest = current;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
dist = min;
|
||||
return closest;
|
||||
}
|
||||
} // namespace SpiralVase
|
||||
|
||||
std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer)
|
||||
{
|
||||
/* This post-processor relies on several assumptions:
|
||||
- all layers are processed through it, including those that are not supposed
|
||||
to be transformed, in order to update the reader with the XY positions
|
||||
- each call to this method includes a full layer, with a single Z move
|
||||
at the beginning
|
||||
- each layer is composed by suitable geometry (i.e. a single complete loop)
|
||||
- loops were not clipped before calling this method */
|
||||
|
||||
// If we're not going to modify G-code, just feed it to the reader
|
||||
// in order to update positions.
|
||||
if (! m_enabled) {
|
||||
m_reader.parse_buffer(gcode);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Get total XY length for this layer by summing all extrusion moves.
|
||||
float total_layer_length = 0;
|
||||
float layer_height = 0;
|
||||
float z = 0.f;
|
||||
|
||||
{
|
||||
//FIXME Performance warning: This copies the GCodeConfig of the reader.
|
||||
GCodeReader r = m_reader; // clone
|
||||
bool set_z = false;
|
||||
r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z]
|
||||
(GCodeReader &reader, const GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.extruding(reader)) {
|
||||
total_layer_length += line.dist_XY(reader);
|
||||
} else if (line.has(Z)) {
|
||||
layer_height += line.dist_Z(reader);
|
||||
if (!set_z) {
|
||||
z = line.new_Z(reader);
|
||||
set_z = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Remove layer height from initial Z.
|
||||
z -= layer_height;
|
||||
|
||||
std::shared_ptr<std::vector<SpiralVase::SpiralPoint>> current_layer = std::make_shared<std::vector<SpiralVase::SpiralPoint>>();
|
||||
std::shared_ptr<std::vector<SpiralVase::SpiralPoint>> previous_layer = m_previous_layer;
|
||||
|
||||
bool smooth_spiral = m_smooth_spiral;
|
||||
std::string new_gcode;
|
||||
std::string transition_gcode;
|
||||
float max_xy_dist_for_smoothing = m_max_xy_smoothing;
|
||||
//FIXME Tapering of the transition layer only works reliably with relative extruder distances.
|
||||
// For absolute extruder distances it will be switched off.
|
||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||
// layer.
|
||||
bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||
bool transition_out = last_layer && m_config.use_relative_e_distances.value;
|
||||
float len = 0.f;
|
||||
//set initial point
|
||||
SpiralVase::SpiralPoint last_point = previous_layer != NULL && previous_layer->size() > 0 ? previous_layer->at(previous_layer->size()-1): SpiralVase::SpiralPoint(0,0);
|
||||
|
||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, ¤t_layer, &previous_layer, &transition_gcode, transition_out,
|
||||
smooth_spiral, &max_xy_dist_for_smoothing, &last_point]
|
||||
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.has_z()) {
|
||||
// If this is the initial Z move of the layer, replace it with a
|
||||
// (redundant) move to the last Z of previous layer.
|
||||
line.set(reader, Z, z);
|
||||
new_gcode += line.raw() + '\n';
|
||||
return;
|
||||
} else {
|
||||
float dist_XY = line.dist_XY(reader);
|
||||
if (dist_XY > 0) {
|
||||
if (line.extruding(reader)) { // Exclude wipe and retract
|
||||
len += dist_XY;
|
||||
float factor = len / total_layer_length;
|
||||
if (transition_in)
|
||||
// Transition layer, interpolate the amount of extrusion from zero to the final value.
|
||||
line.set(reader, E, line.e() * factor, 5 /*decimal_digits*/);
|
||||
else if (transition_out) {
|
||||
// We want the last layer to ramp down extrusion, but without changing z height!
|
||||
// So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E
|
||||
// We add this new layer at the very end
|
||||
GCodeReader::GCodeLine transitionLine(line);
|
||||
transitionLine.set(reader, E, line.e() * (1 - factor), 5 /*decimal_digits*/);
|
||||
transition_gcode += transitionLine.raw() + '\n';
|
||||
}
|
||||
// This line is the core of Spiral Vase mode, ramp up the Z smoothly
|
||||
line.set(reader, Z, z + factor * layer_height);
|
||||
if (smooth_spiral) {
|
||||
// Now we also need to try to interpolate X and Y
|
||||
SpiralVase::SpiralPoint p(line.x(), line.y()); // Get current x/y coordinates
|
||||
current_layer->push_back(p); // Store that point for later use on the next layer
|
||||
if (previous_layer != NULL) {
|
||||
bool found = false;
|
||||
float dist = 0;
|
||||
SpiralVase::SpiralPoint nearestp = SpiralVaseHelpers::nearest_point_on_lines(p, previous_layer, found, dist);
|
||||
if (found && dist < max_xy_dist_for_smoothing) {
|
||||
// Interpolate between the point on this layer and the point on the previous layer
|
||||
SpiralVase::SpiralPoint target = SpiralVaseHelpers::add(SpiralVaseHelpers::scale(nearestp, 1 - factor), SpiralVaseHelpers::scale(p, factor));
|
||||
|
||||
// QDS: remove too short movement
|
||||
// We need to figure out the distance of this new line!
|
||||
float modified_dist_XY = SpiralVaseHelpers::distance(last_point, target);
|
||||
if (modified_dist_XY < 0.001)
|
||||
line.clear();
|
||||
else {
|
||||
line.set(reader, X, target.x);
|
||||
line.set(reader, Y, target.y);
|
||||
// Scale the extrusion amount according to change in length
|
||||
line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5 /*decimal_digits*/);
|
||||
last_point = target;
|
||||
}
|
||||
} else {
|
||||
last_point = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
new_gcode += line.raw() + '\n';
|
||||
}
|
||||
return;
|
||||
/* Skip travel moves: the move to first perimeter point will
|
||||
cause a visible seam when loops are not aligned in XY; by skipping
|
||||
it we blend the first loop move in the XY plane (although the smoothness
|
||||
of such blend depend on how long the first segment is; maybe we should
|
||||
enforce some minimum length?).
|
||||
When smooth_spiral is enabled, we're gonna end up exactly where the next layer should
|
||||
start anyway, so we don't need the travel move */
|
||||
}
|
||||
}
|
||||
}
|
||||
new_gcode += line.raw() + '\n';
|
||||
if(transition_out) {
|
||||
transition_gcode += line.raw() + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
m_previous_layer = current_layer;
|
||||
|
||||
return new_gcode + transition_gcode;
|
||||
}
|
||||
|
||||
}
|
||||
53
src/libslic3r/GCode/SpiralVase.hpp
Normal file
53
src/libslic3r/GCode/SpiralVase.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef slic3r_SpiralVase_hpp_
|
||||
#define slic3r_SpiralVase_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../GCodeReader.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SpiralVase
|
||||
{
|
||||
public:
|
||||
class SpiralPoint
|
||||
{
|
||||
public:
|
||||
SpiralPoint(float paramx, float paramy) : x(paramx), y(paramy) {}
|
||||
|
||||
public:
|
||||
float x, y;
|
||||
};
|
||||
SpiralVase(const PrintConfig &config) : m_config(config)
|
||||
{
|
||||
//QDS
|
||||
//m_reader.z() = (float)m_config.z_offset;
|
||||
m_reader.z() = 0.0f;
|
||||
m_reader.apply_config(m_config);
|
||||
m_previous_layer = NULL;
|
||||
m_smooth_spiral = config.spiral_mode_smooth;
|
||||
};
|
||||
|
||||
void enable(bool en) {
|
||||
m_transition_layer = en && ! m_enabled;
|
||||
m_enabled = en;
|
||||
}
|
||||
|
||||
std::string process_layer(const std::string &gcode, bool last_layer);
|
||||
void set_max_xy_smoothing(float max) {
|
||||
m_max_xy_smoothing = max;
|
||||
}
|
||||
private:
|
||||
const PrintConfig &m_config;
|
||||
GCodeReader m_reader;
|
||||
float m_max_xy_smoothing = 0.f;
|
||||
|
||||
bool m_enabled = false;
|
||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||
bool m_transition_layer = false;
|
||||
// Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes
|
||||
bool m_smooth_spiral = false;
|
||||
std::shared_ptr<std::vector<SpiralPoint>> m_previous_layer;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // slic3r_SpiralVase_hpp_
|
||||
32
src/libslic3r/GCode/ThumbnailData.cpp
Normal file
32
src/libslic3r/GCode/ThumbnailData.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "ThumbnailData.hpp"
|
||||
|
||||
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.clear();
|
||||
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
|
||||
121
src/libslic3r/GCode/ThumbnailData.hpp
Normal file
121
src/libslic3r/GCode/ThumbnailData.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
#ifndef slic3r_ThumbnailData_hpp_
|
||||
#define slic3r_ThumbnailData_hpp_
|
||||
|
||||
#include <vector>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
//QDS: thumbnail_size in gcode file
|
||||
static std::vector<Vec2d> THUMBNAIL_SIZE = { Vec2d(50, 50) };
|
||||
|
||||
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;
|
||||
void load_from(ThumbnailData &data) {
|
||||
this->set(data.width, data.height);
|
||||
pixels = data.pixels;
|
||||
}
|
||||
};
|
||||
|
||||
//QDS: add plate id into thumbnail render logic
|
||||
using ThumbnailsList = std::vector<ThumbnailData>;
|
||||
|
||||
struct ThumbnailsParams
|
||||
{
|
||||
const Vec2ds sizes;
|
||||
bool printable_only;
|
||||
bool parts_only;
|
||||
bool show_bed;
|
||||
bool transparent_background;
|
||||
int plate_id;
|
||||
};
|
||||
|
||||
typedef std::function<ThumbnailsList(const ThumbnailsParams&)> ThumbnailsGeneratorCallback;
|
||||
|
||||
struct BBoxData
|
||||
{
|
||||
int id; // object id
|
||||
std::vector<coordf_t> bbox; // first layer bounding box: min.{x,y}, max.{x,y}
|
||||
float area; // first layer area
|
||||
float layer_height;
|
||||
std::string name;
|
||||
void to_json(nlohmann::json& j) const{
|
||||
j = nlohmann::json{
|
||||
{"id",id},
|
||||
{"bbox",bbox},
|
||||
{"area",area},
|
||||
{"layer_height",layer_height},
|
||||
{"name",name}
|
||||
};
|
||||
}
|
||||
void from_json(const nlohmann::json& j) {
|
||||
j.at("id").get_to(id);
|
||||
j.at("bbox").get_to(bbox);
|
||||
j.at("area").get_to(area);
|
||||
j.at("layer_height").get_to(layer_height);
|
||||
j.at("name").get_to(name);
|
||||
}
|
||||
};
|
||||
|
||||
struct PlateBBoxData
|
||||
{
|
||||
std::vector<coordf_t> bbox_all; // total bounding box of all objects including brim
|
||||
std::vector<BBoxData> bbox_objs; // BBoxData of seperate object
|
||||
std::vector<int> filament_ids; // filament id used in curr plate
|
||||
std::vector<std::string> filament_colors;
|
||||
bool is_seq_print = false;
|
||||
int first_extruder = 0;
|
||||
float nozzle_diameter = 0.4;
|
||||
std::string bed_type;
|
||||
// version 1: use view type ColorPrint (filament color)
|
||||
// version 2: use view type FilamentId (filament id)
|
||||
int version = 2;
|
||||
|
||||
void to_json(nlohmann::json& j) const{
|
||||
j = nlohmann::json{ {"bbox_all",bbox_all} };
|
||||
j["filament_ids"] = filament_ids;
|
||||
j["filament_colors"] = filament_colors;
|
||||
j["is_seq_print"] = is_seq_print;
|
||||
j["first_extruder"] = first_extruder;
|
||||
j["nozzle_diameter"] = nozzle_diameter;
|
||||
j["version"] = version;
|
||||
j["bed_type"] = bed_type;
|
||||
for (const auto& bbox : bbox_objs) {
|
||||
nlohmann::json j_bbox;
|
||||
bbox.to_json(j_bbox);
|
||||
j["bbox_objects"].push_back(j_bbox);
|
||||
}
|
||||
}
|
||||
void from_json(const nlohmann::json& j) {
|
||||
j.at("bbox_all").get_to(bbox_all);
|
||||
j.at("filament_ids").get_to(filament_ids);
|
||||
j.at("filament_colors").get_to(filament_colors);
|
||||
j.at("is_seq_print").get_to(is_seq_print);
|
||||
j.at("first_extruder").get_to(first_extruder);
|
||||
j.at("nozzle_diameter").get_to(nozzle_diameter);
|
||||
j.at("version").get_to(version);
|
||||
j.at("bed_type").get_to(bed_type);
|
||||
for (auto& bbox_j : j.at("bbox_objects")) {
|
||||
BBoxData bbox_data;
|
||||
bbox_data.from_json(bbox_j);
|
||||
bbox_objs.push_back(bbox_data);
|
||||
}
|
||||
}
|
||||
bool is_valid() const {
|
||||
return !bbox_objs.empty();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_ThumbnailData_hpp_
|
||||
441
src/libslic3r/GCode/Thumbnails.cpp
Normal file
441
src/libslic3r/GCode/Thumbnails.cpp
Normal file
@@ -0,0 +1,441 @@
|
||||
#include "Thumbnails.hpp"
|
||||
#include "../miniz_extension.hpp"
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <jerror.h>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r::GCodeThumbnails {
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
struct CompressedPNG : CompressedImageBuffer
|
||||
{
|
||||
~CompressedPNG() override { if (data) mz_free(data); }
|
||||
std::string_view tag() const override { return "thumbnail"sv; }
|
||||
};
|
||||
|
||||
struct CompressedQidi : CompressedImageBuffer
|
||||
{
|
||||
~CompressedQidi() override { free(data); }
|
||||
std::string_view tag() const override { return "thumbnail_QIDI"sv; }
|
||||
};
|
||||
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_png(const ThumbnailData& data)
|
||||
{
|
||||
auto out = std::make_unique<CompressedPNG>();
|
||||
out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1);
|
||||
return out;
|
||||
}
|
||||
|
||||
int Qidi_EncodeStr(unsigned short* fromcolor16, int picw, int pich, unsigned char* outputdata, int outputmaxtsize, int colorsmax);
|
||||
|
||||
std::string compress_thumbnail_qidi(const ThumbnailData& data)
|
||||
{
|
||||
auto out = std::make_unique<CompressedPNG>();
|
||||
// BOOST_LOG_TRIVIAL(error) << data.width;
|
||||
int width = int(data.width);
|
||||
int height = int(data.height);
|
||||
|
||||
if (data.width * data.height > 500 * 500) {
|
||||
width = 500;
|
||||
height = 500;
|
||||
}
|
||||
U16 color16[500 * 500];
|
||||
// U16 *color16 = new U16[data.width * data.height];
|
||||
// for (int i = 0; i < 200*200; i++) color16[i] = 522240;
|
||||
unsigned char outputdata[500 * 500 * 10];
|
||||
// unsigned char *outputdata = new unsigned char[data.width * data.height * 10];
|
||||
|
||||
std::vector<uint8_t> rgba_pixels(data.pixels.size() * 4);
|
||||
size_t row_size = width * 4;
|
||||
for (size_t y = 0; y < height; ++y)
|
||||
memcpy(rgba_pixels.data() + y * row_size, data.pixels.data() + y * row_size, row_size);
|
||||
const unsigned char* pixels;
|
||||
pixels = (const unsigned char*)rgba_pixels.data();
|
||||
int rrrr = 0, gggg = 0, bbbb = 0, aaaa = 0, rgb = 0;
|
||||
|
||||
int time = width * height - 1; // 200*200-1;
|
||||
|
||||
for (unsigned int r = 0; r < height; ++r) {
|
||||
unsigned int rr = r * width;
|
||||
for (unsigned int c = 0; c < width; ++c) {
|
||||
unsigned int cc = width - c - 1;
|
||||
rrrr = int(pixels[4 * (rr + cc) + 0]) >> 3;
|
||||
gggg = int(pixels[4 * (rr + cc) + 1]) >> 2;
|
||||
bbbb = int(pixels[4 * (rr + cc) + 2]) >> 3;
|
||||
aaaa = int(pixels[4 * (rr + cc) + 3]);
|
||||
if (aaaa == 0) {
|
||||
rrrr = 239 >> 3;
|
||||
gggg = 243 >> 2;
|
||||
bbbb = 247 >> 3;
|
||||
}
|
||||
rgb = (rrrr << 11) | (gggg << 5) | bbbb;
|
||||
color16[time--] = rgb;
|
||||
}
|
||||
}
|
||||
|
||||
int res = Qidi_EncodeStr(color16, width, height, outputdata, height * width * 10, 1024);
|
||||
std::string temp;
|
||||
|
||||
// for (unsigned char i : outputdata) { temp += i; }
|
||||
for (unsigned int i = 0; i < sizeof(outputdata); ++i) {
|
||||
temp += outputdata[i];
|
||||
// unsigned char strr = outputdata[i];
|
||||
// temp += strr;
|
||||
}
|
||||
// out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size,
|
||||
// MZ_DEFAULT_LEVEL, 1);
|
||||
return temp;
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData& data, GCodeThumbnailsFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case GCodeThumbnailsFormat::PNG:
|
||||
default:
|
||||
return compress_thumbnail_png(data);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned short colo16;
|
||||
unsigned char A0;
|
||||
unsigned char A1;
|
||||
unsigned char A2;
|
||||
unsigned char res0;
|
||||
unsigned short res1;
|
||||
unsigned int qty;
|
||||
} U16HEAD;
|
||||
typedef struct
|
||||
{
|
||||
unsigned char encodever;
|
||||
unsigned char res0;
|
||||
unsigned short oncelistqty;
|
||||
unsigned int PicW;
|
||||
unsigned int PicH;
|
||||
unsigned int mark;
|
||||
unsigned int ListDataSize;
|
||||
unsigned int ColorDataSize;
|
||||
unsigned int res1;
|
||||
unsigned int res2;
|
||||
} QidiHead3;
|
||||
|
||||
static void colmemmove(unsigned char* dec, unsigned char* src, int lenth)
|
||||
{
|
||||
if (src < dec) {
|
||||
dec += lenth - 1;
|
||||
src += lenth - 1;
|
||||
while (lenth > 0) {
|
||||
*(dec--) = *(src--);
|
||||
lenth--;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (lenth > 0) {
|
||||
*(dec++) = *(src++);
|
||||
lenth--;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void colmemcpy(unsigned char* dec, unsigned char* src, int lenth)
|
||||
{
|
||||
while (lenth > 0) {
|
||||
*(dec++) = *(src++);
|
||||
lenth--;
|
||||
}
|
||||
}
|
||||
static void colmemset(unsigned char* dec, unsigned char val, int lenth)
|
||||
{
|
||||
while (lenth > 0) {
|
||||
*(dec++) = val;
|
||||
lenth--;
|
||||
}
|
||||
}
|
||||
|
||||
static void ADList0(unsigned short val, U16HEAD* listu16, int* listqty, int maxqty)
|
||||
{
|
||||
unsigned char A0;
|
||||
unsigned char A1;
|
||||
unsigned char A2;
|
||||
int qty = *listqty;
|
||||
if (qty >= maxqty)
|
||||
return;
|
||||
for (int i = 0; i < qty; i++) {
|
||||
if (listu16[i].colo16 == val) {
|
||||
listu16[i].qty++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
A0 = (unsigned char)(val >> 11);
|
||||
A1 = (unsigned char)((val << 5) >> 10);
|
||||
A2 = (unsigned char)((val << 11) >> 11);
|
||||
U16HEAD* a = &listu16[qty];
|
||||
a->colo16 = val;
|
||||
a->A0 = A0;
|
||||
a->A1 = A1;
|
||||
a->A2 = A2;
|
||||
a->qty = 1;
|
||||
*listqty = qty + 1;
|
||||
}
|
||||
|
||||
static int Byte8bitEncode(
|
||||
unsigned short* fromcolor16, unsigned short* listu16, int listqty, int dotsqty, unsigned char* outputdata, int decMaxBytesize)
|
||||
{
|
||||
unsigned char tid, sid;
|
||||
int dots = 0;
|
||||
int srcindex = 0;
|
||||
int decindex = 0;
|
||||
int lastid = 0;
|
||||
int temp = 0;
|
||||
while (dotsqty > 0) {
|
||||
dots = 1;
|
||||
for (int i = 0; i < (dotsqty - 1); i++) {
|
||||
if (fromcolor16[srcindex + i] != fromcolor16[srcindex + i + 1])
|
||||
break;
|
||||
dots++;
|
||||
if (dots == 255)
|
||||
break;
|
||||
}
|
||||
temp = 0;
|
||||
for (int i = 0; i < listqty; i++) {
|
||||
if (listu16[i] == fromcolor16[srcindex]) {
|
||||
temp = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
tid = (unsigned char)(temp % 32);
|
||||
sid = (unsigned char)(temp / 32);
|
||||
if (lastid != sid) {
|
||||
if (decindex >= decMaxBytesize)
|
||||
goto IL_END;
|
||||
outputdata[decindex] = 7;
|
||||
outputdata[decindex] <<= 5;
|
||||
outputdata[decindex] += sid;
|
||||
decindex++;
|
||||
lastid = sid;
|
||||
}
|
||||
if (dots <= 6) {
|
||||
if (decindex >= decMaxBytesize)
|
||||
goto IL_END;
|
||||
outputdata[decindex] = (unsigned char)dots;
|
||||
outputdata[decindex] <<= 5;
|
||||
outputdata[decindex] += tid;
|
||||
decindex++;
|
||||
}
|
||||
else {
|
||||
if (decindex >= decMaxBytesize)
|
||||
goto IL_END;
|
||||
outputdata[decindex] = 0;
|
||||
outputdata[decindex] += tid;
|
||||
decindex++;
|
||||
if (decindex >= decMaxBytesize)
|
||||
goto IL_END;
|
||||
outputdata[decindex] = (unsigned char)dots;
|
||||
decindex++;
|
||||
}
|
||||
srcindex += dots;
|
||||
dotsqty -= dots;
|
||||
}
|
||||
IL_END:
|
||||
return decindex;
|
||||
}
|
||||
|
||||
static int QidiEncode(unsigned short* fromcolor16, int picw, int pich, unsigned char* outputdata, int outputmaxtsize, int colorsmax)
|
||||
{
|
||||
U16HEAD l0;
|
||||
int cha0, cha1, cha2, fid, minval;
|
||||
ColPicHead3* Head0 = null;
|
||||
U16HEAD Listu16[1024];
|
||||
int ListQty = 0;
|
||||
int enqty = 0;
|
||||
int dotsqty = picw * pich;
|
||||
if (colorsmax > 1024)
|
||||
colorsmax = 1024;
|
||||
for (int i = 0; i < dotsqty; i++) {
|
||||
int ch = (int)fromcolor16[i];
|
||||
ADList0(ch, Listu16, &ListQty, 1024);
|
||||
}
|
||||
|
||||
for (int index = 1; index < ListQty; index++) {
|
||||
l0 = Listu16[index];
|
||||
for (int i = 0; i < index; i++) {
|
||||
if (l0.qty >= Listu16[i].qty) {
|
||||
colmemmove((U8*)&Listu16[i + 1], (U8*)&Listu16[i], (index - i) * sizeof(U16HEAD));
|
||||
colmemcpy((U8*)&Listu16[i], (U8*)&l0, sizeof(U16HEAD));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (ListQty > colorsmax) {
|
||||
l0 = Listu16[ListQty - 1];
|
||||
minval = 255;
|
||||
fid = -1;
|
||||
for (int i = 0; i < colorsmax; i++) {
|
||||
cha0 = Listu16[i].A0 - l0.A0;
|
||||
if (cha0 < 0)
|
||||
cha0 = 0 - cha0;
|
||||
cha1 = Listu16[i].A1 - l0.A1;
|
||||
if (cha1 < 0)
|
||||
cha1 = 0 - cha1;
|
||||
cha2 = Listu16[i].A2 - l0.A2;
|
||||
if (cha2 < 0)
|
||||
cha2 = 0 - cha2;
|
||||
int chall = cha0 + cha1 + cha2;
|
||||
if (chall < minval) {
|
||||
minval = chall;
|
||||
fid = i;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < dotsqty; i++) {
|
||||
if (fromcolor16[i] == l0.colo16)
|
||||
fromcolor16[i] = Listu16[fid].colo16;
|
||||
}
|
||||
ListQty = ListQty - 1;
|
||||
}
|
||||
Head0 = ((ColPicHead3*)outputdata);
|
||||
colmemset(outputdata, 0, sizeof(ColPicHead3));
|
||||
Head0->encodever = 3;
|
||||
Head0->oncelistqty = 0;
|
||||
Head0->mark = 0x05DDC33C;
|
||||
Head0->ListDataSize = ListQty * 2;
|
||||
for (int i = 0; i < ListQty; i++) {
|
||||
U16* l0 = (U16*)&outputdata[sizeof(ColPicHead3)];
|
||||
l0[i] = Listu16[i].colo16;
|
||||
}
|
||||
enqty = Byte8bitEncode(fromcolor16, (U16*)&outputdata[sizeof(ColPicHead3)], Head0->ListDataSize >> 1, dotsqty,
|
||||
&outputdata[sizeof(ColPicHead3) + Head0->ListDataSize],
|
||||
outputmaxtsize - sizeof(ColPicHead3) - Head0->ListDataSize);
|
||||
Head0->ColorDataSize = enqty;
|
||||
Head0->PicW = picw;
|
||||
Head0->PicH = pich;
|
||||
return sizeof(ColPicHead3) + Head0->ListDataSize + Head0->ColorDataSize;
|
||||
}
|
||||
|
||||
int Qidi_EncodeStr(unsigned short* fromcolor16, int picw, int pich, unsigned char* outputdata, int outputmaxtsize, int colorsmax)
|
||||
{
|
||||
int qty = 0;
|
||||
int temp = 0;
|
||||
int strindex = 0;
|
||||
int hexindex = 0;
|
||||
U8 TempBytes[4];
|
||||
qty = QidiEncode(fromcolor16, picw, pich, outputdata, outputmaxtsize, colorsmax);
|
||||
if (qty == 0)
|
||||
return 0;
|
||||
temp = 3 - (qty % 3);
|
||||
while (temp > 0) {
|
||||
outputdata[qty] = 0;
|
||||
qty++;
|
||||
temp--;
|
||||
}
|
||||
if ((qty * 4 / 3) >= outputmaxtsize)
|
||||
return 0;
|
||||
hexindex = qty;
|
||||
strindex = (qty * 4 / 3);
|
||||
while (hexindex > 0) {
|
||||
hexindex -= 3;
|
||||
strindex -= 4;
|
||||
|
||||
TempBytes[0] = (U8)(outputdata[hexindex] >> 2);
|
||||
TempBytes[1] = (U8)(outputdata[hexindex] & 3);
|
||||
TempBytes[1] <<= 4;
|
||||
TempBytes[1] += ((U8)(outputdata[hexindex + 1] >> 4));
|
||||
TempBytes[2] = (U8)(outputdata[hexindex + 1] & 15);
|
||||
TempBytes[2] <<= 2;
|
||||
TempBytes[2] += ((U8)(outputdata[hexindex + 2] >> 6));
|
||||
TempBytes[3] = (U8)(outputdata[hexindex + 2] & 63);
|
||||
|
||||
TempBytes[0] += 48;
|
||||
if (TempBytes[0] == (U8)'\\')
|
||||
TempBytes[0] = 126;
|
||||
TempBytes[0 + 1] += 48;
|
||||
if (TempBytes[0 + 1] == (U8)'\\')
|
||||
TempBytes[0 + 1] = 126;
|
||||
TempBytes[0 + 2] += 48;
|
||||
if (TempBytes[0 + 2] == (U8)'\\')
|
||||
TempBytes[0 + 2] = 126;
|
||||
TempBytes[0 + 3] += 48;
|
||||
if (TempBytes[0 + 3] == (U8)'\\')
|
||||
TempBytes[0 + 3] = 126;
|
||||
|
||||
outputdata[strindex] = TempBytes[0];
|
||||
outputdata[strindex + 1] = TempBytes[1];
|
||||
outputdata[strindex + 2] = TempBytes[2];
|
||||
outputdata[strindex + 3] = TempBytes[3];
|
||||
}
|
||||
qty = qty * 4 / 3;
|
||||
outputdata[qty] = 0;
|
||||
return qty;
|
||||
}
|
||||
std::string compress_qidi_thumbnail(const ThumbnailData& data, GCodeThumbnailsFormat format) { return compress_thumbnail_qidi(data); }
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext /*= "PNG"sv*/)
|
||||
{
|
||||
if (thumbnails_string.empty())
|
||||
return {};
|
||||
|
||||
std::istringstream is(thumbnails_string);
|
||||
std::string point_str;
|
||||
|
||||
ThumbnailErrors errors;
|
||||
|
||||
GCodeThumbnailDefinitionsList thumbnails_list;
|
||||
while (std::getline(is, point_str, ',')) {
|
||||
Vec2d point(Vec2d::Zero());
|
||||
GCodeThumbnailsFormat format;
|
||||
std::istringstream iss(point_str);
|
||||
std::string coord_str;
|
||||
if (std::getline(iss, coord_str, 'x') && !coord_str.empty()) {
|
||||
std::istringstream(coord_str) >> point(0);
|
||||
if (std::getline(iss, coord_str, '/') && !coord_str.empty()) {
|
||||
std::istringstream(coord_str) >> point(1);
|
||||
|
||||
if (0 < point(0) && point(0) < 1000 && 0 < point(1) && point(1) < 1000) {
|
||||
std::string ext_str;
|
||||
std::getline(iss, ext_str, '/');
|
||||
if (ext_str.empty())
|
||||
ext_str = def_ext.empty() ? "PNG"sv : def_ext;
|
||||
boost::to_upper(ext_str);
|
||||
if (!ConfigOptionEnum<GCodeThumbnailsFormat>::from_string(ext_str, format)) {
|
||||
format = GCodeThumbnailsFormat::PNG;
|
||||
errors = enum_bitmask(errors | ThumbnailError::InvalidExt);
|
||||
}
|
||||
|
||||
thumbnails_list.emplace_back(std::make_pair(format, point));
|
||||
}
|
||||
else
|
||||
errors = enum_bitmask(errors | ThumbnailError::OutOfRange);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
errors = enum_bitmask(errors | ThumbnailError::InvalidVal);
|
||||
}
|
||||
|
||||
return std::make_pair(std::move(thumbnails_list), errors);
|
||||
}
|
||||
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase& config)
|
||||
{
|
||||
|
||||
if (const auto thumbnails_value = config.option<ConfigOptionString>("thumbnail_size"))
|
||||
return make_and_check_thumbnail_list(thumbnails_value->value);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string get_error_string(const ThumbnailErrors& errors)
|
||||
{
|
||||
std::string error_str;
|
||||
|
||||
if (errors.has(ThumbnailError::InvalidVal))
|
||||
error_str += "\n - " + format("Invalid input format. Expected vector of dimensions in the following format: \"%1%\"", "XxY/EXT, XxY/EXT, ...");
|
||||
if (errors.has(ThumbnailError::OutOfRange))
|
||||
error_str += "\n - Input value is out of range";
|
||||
if (errors.has(ThumbnailError::InvalidExt))
|
||||
error_str += "\n - Some extension in the input is invalid";
|
||||
|
||||
return error_str;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCodeThumbnails
|
||||
169
src/libslic3r/GCode/Thumbnails.hpp
Normal file
169
src/libslic3r/GCode/Thumbnails.hpp
Normal file
@@ -0,0 +1,169 @@
|
||||
#ifndef slic3r_GCodeThumbnails_hpp_
|
||||
#define slic3r_GCodeThumbnails_hpp_
|
||||
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "ThumbnailData.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/beast/core/detail/base64.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt };
|
||||
using ThumbnailErrors = enum_bitmask<ThumbnailError>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError);
|
||||
}
|
||||
|
||||
#ifndef U16
|
||||
typedef unsigned short U16;
|
||||
#endif
|
||||
#ifndef U8
|
||||
typedef unsigned char U8;
|
||||
#endif
|
||||
#ifndef U32
|
||||
typedef unsigned int U32;
|
||||
#endif
|
||||
#ifndef null
|
||||
#define null 0
|
||||
#endif
|
||||
typedef struct
|
||||
{
|
||||
U16 colo16;
|
||||
U8 A0;
|
||||
U8 A1;
|
||||
U8 A2;
|
||||
U8 res0;
|
||||
U16 res1;
|
||||
U32 qty;
|
||||
} U16HEAD;
|
||||
typedef struct
|
||||
{
|
||||
U8 encodever;
|
||||
U8 res0;
|
||||
U16 oncelistqty;
|
||||
U32 PicW;
|
||||
U32 PicH;
|
||||
U32 mark;
|
||||
U32 ListDataSize;
|
||||
U32 ColorDataSize;
|
||||
U32 res1;
|
||||
U32 res2;
|
||||
} ColPicHead3;
|
||||
|
||||
namespace Slic3r::GCodeThumbnails {
|
||||
|
||||
struct CompressedImageBuffer
|
||||
{
|
||||
void* data{ nullptr };
|
||||
size_t size{ 0 };
|
||||
virtual ~CompressedImageBuffer() {}
|
||||
virtual std::string_view tag() const = 0;
|
||||
};
|
||||
|
||||
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData& data, GCodeThumbnailsFormat format);
|
||||
std::string get_error_string(const ThumbnailErrors& errors);
|
||||
std::string compress_qidi_thumbnail(const ThumbnailData& data, GCodeThumbnailsFormat format);
|
||||
|
||||
|
||||
typedef std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>> GCodeThumbnailDefinitionsList;
|
||||
using namespace std::literals;
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext = "PNG"sv);
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase& config);
|
||||
|
||||
|
||||
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
|
||||
inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback& thumbnail_cb,
|
||||
int plate_id,
|
||||
const std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>>& thumbnails_list,
|
||||
WriteToOutput output,
|
||||
ThrowIfCanceledCallback throw_if_canceled)
|
||||
{
|
||||
// Write thumbnails using base64 encoding
|
||||
if (thumbnail_cb == nullptr)
|
||||
return;
|
||||
short i = 0;
|
||||
bool first_Qidi = true;
|
||||
for (const auto& [format, size] : thumbnails_list) {
|
||||
static constexpr const size_t max_row_length = 78;
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true, plate_id });
|
||||
for (const ThumbnailData& data : thumbnails) {
|
||||
if (data.is_valid()) {
|
||||
auto compressed = compress_thumbnail(data, format);
|
||||
if (compressed->data && compressed->size) {
|
||||
if (format == GCodeThumbnailsFormat::Qidi) {
|
||||
;
|
||||
}
|
||||
else {
|
||||
output("; THUMBNAIL_BLOCK_START\n");
|
||||
std::string encoded;
|
||||
encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size));
|
||||
encoded.resize(boost::beast::detail::base64::encode((void*)encoded.data(), (const void*)compressed->data,
|
||||
compressed->size));
|
||||
output((boost::format("\n;\n; %s begin %dx%d %d\n") % compressed->tag() % data.width % data.height % encoded.size())
|
||||
.str()
|
||||
.c_str());
|
||||
while (encoded.size() > max_row_length) {
|
||||
output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str());
|
||||
encoded = encoded.substr(max_row_length);
|
||||
}
|
||||
|
||||
if (encoded.size() > 0)
|
||||
output((boost::format("; %s\n") % encoded).str().c_str());
|
||||
|
||||
output((boost::format("; %s end\n") % compressed->tag()).str().c_str());
|
||||
output("; THUMBNAIL_BLOCK_END\n\n");
|
||||
}
|
||||
throw_if_canceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
|
||||
inline void export_qidi_thumbnails_to_file(ThumbnailsGeneratorCallback& thumbnail_cb,
|
||||
int plate_id,
|
||||
const std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>>& thumbnails_list,
|
||||
WriteToOutput output,
|
||||
ThrowIfCanceledCallback throw_if_canceled)
|
||||
{
|
||||
// Write thumbnails using base64 encoding
|
||||
if (thumbnail_cb == nullptr)
|
||||
return;
|
||||
short i = 0;
|
||||
bool first_Qidi = true;
|
||||
for (const auto& [format, size] : thumbnails_list) {
|
||||
static constexpr const size_t max_row_length = 78;
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true, plate_id });
|
||||
for (const ThumbnailData& data : thumbnails) {
|
||||
if (data.is_valid()) {
|
||||
auto compressed = compress_thumbnail(data, format);
|
||||
if (compressed->data && compressed->size) {
|
||||
if (format == GCodeThumbnailsFormat::Qidi) {
|
||||
auto compressed_qidi = compress_qidi_thumbnail(data, format);
|
||||
if (first_Qidi) {
|
||||
output((boost::format("\n\n;gimage:%s\n\n") % compressed_qidi).str().c_str());
|
||||
}
|
||||
else {
|
||||
output((boost::format("\n\n;simage:%s\n\n") % compressed_qidi).str().c_str());
|
||||
}
|
||||
first_Qidi = false;
|
||||
}
|
||||
else {
|
||||
;
|
||||
}
|
||||
throw_if_canceled();
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCodeThumbnails
|
||||
|
||||
#endif // slic3r_GCodeThumbnails_hpp_
|
||||
1362
src/libslic3r/GCode/ToolOrdering.cpp
Normal file
1362
src/libslic3r/GCode/ToolOrdering.cpp
Normal file
File diff suppressed because it is too large
Load Diff
219
src/libslic3r/GCode/ToolOrdering.hpp
Normal file
219
src/libslic3r/GCode/ToolOrdering.hpp
Normal file
@@ -0,0 +1,219 @@
|
||||
// Ordering of the tools to minimize tool switches.
|
||||
|
||||
#ifndef slic3r_ToolOrdering_hpp_
|
||||
#define slic3r_ToolOrdering_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class PrintObject;
|
||||
class LayerTools;
|
||||
namespace CustomGCode { struct Item; }
|
||||
class PrintRegion;
|
||||
|
||||
// Object of this class holds information about whether an extrusion is printed immediately
|
||||
// after a toolchange (as part of infill/perimeter wiping) or not. One extrusion can be a part
|
||||
// of several copies - this has to be taken into account.
|
||||
class WipingExtrusions
|
||||
{
|
||||
public:
|
||||
bool is_anything_overridden() const { // if there are no overrides, all the agenda can be skipped - this function can tell us if that's the case
|
||||
return something_overridden;
|
||||
}
|
||||
|
||||
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
|
||||
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy;
|
||||
|
||||
// This is called from GCode::process_layer - see implementation for further comments:
|
||||
const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, const PrintObject* object, int correct_extruder_id, size_t num_of_copies);
|
||||
int get_support_extruder_overrides(const PrintObject* object);
|
||||
int get_support_interface_extruder_overrides(const PrintObject* object);
|
||||
|
||||
// This function goes through all infill entities, decides which ones will be used for wiping and
|
||||
// marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower:
|
||||
float mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe);
|
||||
|
||||
void ensure_perimeters_infills_order(const Print& print);
|
||||
|
||||
bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const;
|
||||
bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) {
|
||||
bool out = this->is_overriddable(ee, print_config, object, region);
|
||||
this->something_overridable |= out;
|
||||
return out;
|
||||
}
|
||||
|
||||
// QDS
|
||||
bool is_support_overriddable(const ExtrusionRole role, const PrintObject& object) const;
|
||||
bool is_support_overriddable_and_mark(const ExtrusionRole role, const PrintObject& object) {
|
||||
bool out = this->is_support_overriddable(role, object);
|
||||
this->something_overridable |= out;
|
||||
return out;
|
||||
}
|
||||
|
||||
bool is_support_overridden(const PrintObject* object) const {
|
||||
return support_map.find(object) != support_map.end();
|
||||
}
|
||||
|
||||
bool is_support_interface_overridden(const PrintObject* object) const {
|
||||
return support_intf_map.find(object) != support_intf_map.end();
|
||||
}
|
||||
|
||||
void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; }
|
||||
|
||||
private:
|
||||
int first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||
int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const;
|
||||
|
||||
// This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual)
|
||||
void set_extruder_override(const ExtrusionEntity* entity, const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
// QDS
|
||||
void set_support_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
void set_support_interface_extruder_override(const PrintObject* object, size_t copy_id, int extruder, size_t num_of_copies);
|
||||
|
||||
// Returns true in case that entity is not printed with its usual extruder for a given copy:
|
||||
bool is_entity_overridden(const ExtrusionEntity* entity, const PrintObject *object, size_t copy_id) const {
|
||||
auto it = entity_map.find(std::make_tuple(entity, object));
|
||||
return it == entity_map.end() ? false : it->second[copy_id] != -1;
|
||||
}
|
||||
|
||||
std::map<std::tuple<const ExtrusionEntity*, const PrintObject *>, ExtruderPerCopy> entity_map; // to keep track of who prints what
|
||||
// QDS
|
||||
std::map<const PrintObject*, int> support_map;
|
||||
std::map<const PrintObject*, int> support_intf_map;
|
||||
bool something_overridable = false;
|
||||
bool something_overridden = false;
|
||||
const LayerTools* m_layer_tools = nullptr; // so we know which LayerTools object this belongs to
|
||||
};
|
||||
|
||||
class LayerTools
|
||||
{
|
||||
public:
|
||||
LayerTools(const coordf_t z) : print_z(z) {}
|
||||
|
||||
// Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other.
|
||||
// In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports).
|
||||
bool operator< (const LayerTools &rhs) const { return print_z < rhs.print_z; }
|
||||
bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; }
|
||||
|
||||
bool is_extruder_order(unsigned int a, unsigned int b) const;
|
||||
bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); }
|
||||
|
||||
// Return a zero based extruder from the region, or extruder_override if overriden.
|
||||
unsigned int wall_filament(const PrintRegion ®ion) const;
|
||||
unsigned int sparse_infill_filament(const PrintRegion ®ion) const;
|
||||
unsigned int solid_infill_filament(const PrintRegion ®ion) const;
|
||||
// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden.
|
||||
unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const;
|
||||
|
||||
coordf_t print_z = 0.;
|
||||
bool has_object = false;
|
||||
bool has_support = false;
|
||||
// Zero based extruder IDs, ordered to minimize tool switches.
|
||||
std::vector<unsigned int> extruders;
|
||||
// If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with.
|
||||
// If not overriden, it is set to 0.
|
||||
unsigned int extruder_override = 0;
|
||||
// Should a skirt be printed at this layer?
|
||||
// Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed.
|
||||
bool has_skirt = false;
|
||||
// Will there be anything extruded on this layer for the wipe tower?
|
||||
// Due to the support layers possibly interleaving the object layers,
|
||||
// wipe tower will be disabled for some support only layers.
|
||||
bool has_wipe_tower = false;
|
||||
// Number of wipe tower partitions to support the required number of tool switches
|
||||
// and to support the wipe tower partitions above this one.
|
||||
size_t wipe_tower_partitions = 0;
|
||||
coordf_t wipe_tower_layer_height = 0.;
|
||||
// Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print.
|
||||
const CustomGCode::Item *custom_gcode = nullptr;
|
||||
|
||||
WipingExtrusions& wiping_extrusions() {
|
||||
m_wiping_extrusions.set_layer_tools_ptr(this);
|
||||
return m_wiping_extrusions;
|
||||
}
|
||||
|
||||
private:
|
||||
// This object holds list of extrusion that will be used for extruder wiping
|
||||
WipingExtrusions m_wiping_extrusions;
|
||||
};
|
||||
|
||||
class ToolOrdering
|
||||
{
|
||||
public:
|
||||
ToolOrdering() = default;
|
||||
|
||||
// For the use case when each object is printed separately
|
||||
// (print->config().print_sequence == PrintSequence::ByObject is true).
|
||||
ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false);
|
||||
|
||||
// For the use case when all objects are printed at once.
|
||||
// (print->config().print_sequence == PrintSequence::ByObject is false).
|
||||
ToolOrdering(const Print& print, unsigned int first_extruder, bool prime_multi_material = false);
|
||||
|
||||
void clear() {
|
||||
m_layer_tools.clear(); m_tool_order_cache.clear();
|
||||
}
|
||||
|
||||
// Only valid for non-sequential print:
|
||||
// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools.
|
||||
// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer.
|
||||
// If multiple events are planned over a span of a single layer, use the last one.
|
||||
void assign_custom_gcodes(const Print &print);
|
||||
|
||||
// Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed.
|
||||
unsigned int first_extruder() const { return m_first_printing_extruder; }
|
||||
|
||||
// Get the first extruder printing the layer_tools, returns -1 if there is no layer printed.
|
||||
unsigned int last_extruder() const { return m_last_printing_extruder; }
|
||||
|
||||
// For a multi-material print, the printing extruders are ordered in the order they shall be primed.
|
||||
const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; }
|
||||
|
||||
// Find LayerTools with the closest print_z.
|
||||
const LayerTools& tools_for_layer(coordf_t print_z) const;
|
||||
LayerTools& tools_for_layer(coordf_t print_z) { return const_cast<LayerTools&>(std::as_const(*this).tools_for_layer(print_z)); }
|
||||
|
||||
const LayerTools& front() const { return m_layer_tools.front(); }
|
||||
const LayerTools& back() const { return m_layer_tools.back(); }
|
||||
std::vector<LayerTools>::const_iterator begin() const { return m_layer_tools.begin(); }
|
||||
std::vector<LayerTools>::const_iterator end() const { return m_layer_tools.end(); }
|
||||
bool empty() const { return m_layer_tools.empty(); }
|
||||
std::vector<LayerTools>& layer_tools() { return m_layer_tools; }
|
||||
bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; }
|
||||
|
||||
private:
|
||||
void initialize_layers(std::vector<coordf_t> &zs);
|
||||
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
|
||||
void reorder_extruders(unsigned int last_extruder_id);
|
||||
// QDS
|
||||
void reorder_extruders(std::vector<unsigned int> tool_order_layer0);
|
||||
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
|
||||
void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height);
|
||||
void collect_extruder_statistics(bool prime_multi_material);
|
||||
void reorder_extruders_for_minimum_flush_volume();
|
||||
|
||||
// QDS
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const Print& print);
|
||||
std::vector<unsigned int> generate_first_layer_tool_order(const PrintObject& object);
|
||||
|
||||
std::vector<LayerTools> m_layer_tools;
|
||||
// First printing extruder, including the multi-material priming sequence.
|
||||
unsigned int m_first_printing_extruder = (unsigned int)-1;
|
||||
// Final printing extruder.
|
||||
unsigned int m_last_printing_extruder = (unsigned int)-1;
|
||||
// All extruders, which extrude some material over m_layer_tools.
|
||||
std::vector<unsigned int> m_all_printing_extruders;
|
||||
std::unordered_map<uint32_t, std::vector<uint8_t>> m_tool_order_cache;
|
||||
const PrintConfig* m_print_config_ptr = nullptr;
|
||||
const PrintObject* m_print_object_ptr = nullptr;
|
||||
};
|
||||
|
||||
} // namespace SLic3r
|
||||
|
||||
#endif /* slic3r_ToolOrdering_hpp_ */
|
||||
1744
src/libslic3r/GCode/WipeTower.cpp
Normal file
1744
src/libslic3r/GCode/WipeTower.cpp
Normal file
File diff suppressed because it is too large
Load Diff
436
src/libslic3r/GCode/WipeTower.hpp
Normal file
436
src/libslic3r/GCode/WipeTower.hpp
Normal file
@@ -0,0 +1,436 @@
|
||||
#ifndef WipeTower_
|
||||
#define WipeTower_
|
||||
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
class WipeTowerWriter;
|
||||
class PrintConfig;
|
||||
enum GCodeFlavor : unsigned char;
|
||||
|
||||
|
||||
|
||||
class WipeTower
|
||||
{
|
||||
public:
|
||||
static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; }
|
||||
|
||||
// WipeTower height to minimum depth map
|
||||
static const std::map<float, float> min_depth_per_height;
|
||||
|
||||
struct Extrusion
|
||||
{
|
||||
Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {}
|
||||
// End position of this extrusion.
|
||||
Vec2f pos;
|
||||
// Width of a squished extrusion, corrected for the roundings of the squished extrusions.
|
||||
// This is left zero if it is a travel move.
|
||||
float width;
|
||||
// Current extruder index.
|
||||
unsigned int tool;
|
||||
};
|
||||
|
||||
struct ToolChangeResult
|
||||
{
|
||||
// Print heigh of this tool change.
|
||||
float print_z;
|
||||
float layer_height;
|
||||
// G-code section to be directly included into the output G-code.
|
||||
std::string gcode;
|
||||
// For path preview.
|
||||
std::vector<Extrusion> extrusions;
|
||||
// Initial position, at which the wipe tower starts its action.
|
||||
// At this position the extruder is loaded and there is no Z-hop applied.
|
||||
Vec2f start_pos;
|
||||
// Last point, at which the normal G-code generator of Slic3r shall continue.
|
||||
// At this position the extruder is loaded and there is no Z-hop applied.
|
||||
Vec2f end_pos;
|
||||
// Time elapsed over this tool change.
|
||||
// This is useful not only for the print time estimation, but also for the control of layer cooling.
|
||||
float elapsed_time;
|
||||
|
||||
// Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later)
|
||||
bool priming;
|
||||
|
||||
// Pass a polyline so that normal G-code generator can do a wipe for us.
|
||||
// The wipe cannot be done by the wipe tower because it has to pass back
|
||||
// a loaded extruder, so it would have to either do a wipe with no retraction
|
||||
// (leading to https://github.com/prusa3d/PrusaSlicer/issues/2834) or do
|
||||
// an extra retraction-unretraction pair.
|
||||
std::vector<Vec2f> wipe_path;
|
||||
|
||||
// QDS
|
||||
float purge_volume = 0.f;
|
||||
|
||||
// Initial tool
|
||||
int initial_tool;
|
||||
|
||||
// New tool
|
||||
int new_tool;
|
||||
|
||||
// QDS: in qdt filament_change_gcode, toolhead will be moved to the wipe tower automatically.
|
||||
// But if finish_layer_tcr is before tool_change_tcr, we have to travel to the wipe tower before
|
||||
// executing the gcode finish_layer_tcr.
|
||||
bool is_finish_first = false;
|
||||
|
||||
// Sum the total length of the extrusion.
|
||||
float total_extrusion_length_in_plane() {
|
||||
float e_length = 0.f;
|
||||
for (size_t i = 1; i < this->extrusions.size(); ++ i) {
|
||||
const Extrusion &e = this->extrusions[i];
|
||||
if (e.width > 0) {
|
||||
Vec2f v = e.pos - (&e - 1)->pos;
|
||||
e_length += v.norm();
|
||||
}
|
||||
}
|
||||
return e_length;
|
||||
}
|
||||
};
|
||||
|
||||
struct box_coordinates
|
||||
{
|
||||
box_coordinates(float left, float bottom, float width, float height) :
|
||||
ld(left , bottom ),
|
||||
lu(left , bottom + height),
|
||||
rd(left + width, bottom ),
|
||||
ru(left + width, bottom + height) {}
|
||||
box_coordinates(const Vec2f &pos, float width, float height) : box_coordinates(pos(0), pos(1), width, height) {}
|
||||
void translate(const Vec2f &shift) {
|
||||
ld += shift; lu += shift;
|
||||
rd += shift; ru += shift;
|
||||
}
|
||||
void translate(const float dx, const float dy) { translate(Vec2f(dx, dy)); }
|
||||
void expand(const float offset) {
|
||||
ld += Vec2f(- offset, - offset);
|
||||
lu += Vec2f(- offset, offset);
|
||||
rd += Vec2f( offset, - offset);
|
||||
ru += Vec2f( offset, offset);
|
||||
}
|
||||
void expand(const float offset_x, const float offset_y) {
|
||||
ld += Vec2f(- offset_x, - offset_y);
|
||||
lu += Vec2f(- offset_x, offset_y);
|
||||
rd += Vec2f( offset_x, - offset_y);
|
||||
ru += Vec2f( offset_x, offset_y);
|
||||
}
|
||||
Vec2f ld; // left down
|
||||
Vec2f lu; // left upper
|
||||
Vec2f rd; // right lower
|
||||
Vec2f ru; // right upper
|
||||
};
|
||||
|
||||
// Construct ToolChangeResult from current state of WipeTower and WipeTowerWriter.
|
||||
// WipeTowerWriter is moved from !
|
||||
ToolChangeResult construct_tcr(WipeTowerWriter& writer,
|
||||
bool priming,
|
||||
size_t old_tool,
|
||||
bool is_finish,
|
||||
float purge_volume) const;
|
||||
|
||||
// x -- x coordinates of wipe tower in mm ( left bottom corner )
|
||||
// y -- y coordinates of wipe tower in mm ( left bottom corner )
|
||||
// width -- width of wipe tower in mm ( default 60 mm - leave as it is )
|
||||
// wipe_area -- space available for one toolchange in mm
|
||||
// QDS: add partplate logic
|
||||
WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origin, const float wipe_volume, size_t initial_tool, const float wipe_tower_height);
|
||||
|
||||
|
||||
// Set the extruder properties.
|
||||
void set_extruder(size_t idx, const PrintConfig& config);
|
||||
|
||||
// Appends into internal structure m_plan containing info about the future wipe tower
|
||||
// to be used before building begins. The entries must be added ordered in z.
|
||||
void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume = 0.f, float prime_volume = 0.f);
|
||||
|
||||
// Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result"
|
||||
void generate(std::vector<std::vector<ToolChangeResult>> &result);
|
||||
|
||||
WipeTower::ToolChangeResult only_generate_out_wall();
|
||||
|
||||
float get_depth() const { return m_wipe_tower_depth; }
|
||||
float get_brim_width() const { return m_wipe_tower_brim_width_real; }
|
||||
float get_height() const { return m_wipe_tower_height; }
|
||||
float get_layer_height() const { return m_layer_height; }
|
||||
|
||||
void set_last_layer_extruder_fill(bool extruder_fill) {
|
||||
if (!m_plan.empty()) {
|
||||
m_plan.back().extruder_fill = extruder_fill;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Switch to a next layer.
|
||||
void set_layer(
|
||||
// Print height of this layer.
|
||||
float print_z,
|
||||
// Layer height, used to calculate extrusion the rate.
|
||||
float layer_height,
|
||||
// Maximum number of tool changes on this layer or the layers below.
|
||||
size_t max_tool_changes,
|
||||
// Is this the first layer of the print? In that case print the brim first.
|
||||
bool is_first_layer,
|
||||
// Is this the last layer of the waste tower?
|
||||
bool is_last_layer)
|
||||
{
|
||||
m_z_pos = print_z;
|
||||
m_layer_height = layer_height;
|
||||
m_depth_traversed = 0.f;
|
||||
m_current_layer_finished = false;
|
||||
//m_current_shape = (! is_first_layer && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL;
|
||||
m_current_shape = SHAPE_NORMAL;
|
||||
if (is_first_layer) {
|
||||
m_num_layer_changes = 0;
|
||||
m_num_tool_changes = 0;
|
||||
} else
|
||||
++ m_num_layer_changes;
|
||||
|
||||
// Calculate extrusion flow from desired line width, nozzle diameter, filament diameter and layer_height:
|
||||
m_extrusion_flow = extrusion_flow(layer_height);
|
||||
|
||||
// Advance m_layer_info iterator, making sure we got it right
|
||||
while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end())
|
||||
++m_layer_info;
|
||||
}
|
||||
|
||||
// Return the wipe tower position.
|
||||
const Vec2f& position() const { return m_wipe_tower_pos; }
|
||||
// Return the wipe tower width.
|
||||
float width() const { return m_wipe_tower_width; }
|
||||
// The wipe tower is finished, there should be no more tool changes or wipe tower prints.
|
||||
bool finished() const { return m_max_color_changes == 0; }
|
||||
|
||||
// Returns gcode to prime the nozzles at the front edge of the print bed.
|
||||
std::vector<ToolChangeResult> prime(
|
||||
// print_z of the first layer.
|
||||
float initial_layer_print_height,
|
||||
// Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object.
|
||||
const std::vector<unsigned int> &tools,
|
||||
// If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower.
|
||||
// If false, the last priming are will be large enough to wipe the last extruder sufficiently.
|
||||
bool last_wipe_inside_wipe_tower);
|
||||
|
||||
// Returns gcode for a toolchange and a final print head position.
|
||||
// On the first layer, extrude a brim around the future wipe tower first.
|
||||
// QDS
|
||||
ToolChangeResult tool_change(size_t new_tool, bool extrude_perimeter = false, bool first_toolchange_to_nonsoluble = false);
|
||||
|
||||
// Fill the unfilled space with a sparse infill.
|
||||
// Call this method only if layer_finished() is false.
|
||||
ToolChangeResult finish_layer(bool extruder_perimeter = true, bool extruder_fill = true);
|
||||
|
||||
// Calculates extrusion flow needed to produce required line width for given layer height
|
||||
float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow
|
||||
{
|
||||
if (layer_height < 0) return m_extrusion_flow;
|
||||
return layer_height * (m_perimeter_width - layer_height * (1.f - float(M_PI) / 4.f)) / filament_area();
|
||||
}
|
||||
|
||||
bool get_floating_area(float& start_pos_y, float& end_pos_y) const;
|
||||
bool need_thick_bridge_flow(float pos_y) const;
|
||||
float get_extrusion_flow() const { return m_extrusion_flow; }
|
||||
|
||||
// Is the current layer finished?
|
||||
bool layer_finished() const {
|
||||
return m_current_layer_finished;
|
||||
}
|
||||
|
||||
std::vector<float> get_used_filament() const { return m_used_filament_length; }
|
||||
int get_number_of_toolchanges() const { return m_num_tool_changes; }
|
||||
|
||||
struct FilamentParameters {
|
||||
std::string material = "PLA";
|
||||
bool is_soluble = false;
|
||||
// QDS
|
||||
bool is_support = false;
|
||||
int nozzle_temperature = 0;
|
||||
int nozzle_temperature_initial_layer = 0;
|
||||
// QDS: remove useless config
|
||||
//float loading_speed = 0.f;
|
||||
//float loading_speed_start = 0.f;
|
||||
//float unloading_speed = 0.f;
|
||||
//float unloading_speed_start = 0.f;
|
||||
//float delay = 0.f ;
|
||||
//int cooling_moves = 0;
|
||||
//float cooling_initial_speed = 0.f;
|
||||
//float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
std::vector<float> ramming_speed;
|
||||
float nozzle_diameter;
|
||||
float filament_area;
|
||||
};
|
||||
|
||||
private:
|
||||
enum wipe_shape // A fill-in direction
|
||||
{
|
||||
SHAPE_NORMAL = 1,
|
||||
SHAPE_REVERSED = -1
|
||||
};
|
||||
|
||||
const float Width_To_Nozzle_Ratio = 1.25f; // desired line width (oval) in multiples of nozzle diameter - may not be actually neccessary to adjust
|
||||
const float WT_EPSILON = 1e-3f;
|
||||
float filament_area() const {
|
||||
return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point
|
||||
}
|
||||
|
||||
bool m_enable_timelapse_print = false;
|
||||
bool m_semm = true; // Are we using a single extruder multimaterial printer?
|
||||
Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm.
|
||||
float m_wipe_tower_width; // Width of the wipe tower.
|
||||
float m_wipe_tower_depth = 0.f; // Depth of the wipe tower
|
||||
// QDS
|
||||
float m_wipe_tower_height = 0.f;
|
||||
float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config
|
||||
float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation
|
||||
float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis)
|
||||
float m_internal_rotation = 0.f;
|
||||
float m_y_shift = 0.f; // y shift passed to writer
|
||||
float m_z_pos = 0.f; // Current Z position.
|
||||
float m_layer_height = 0.f; // Current layer height.
|
||||
size_t m_max_color_changes = 0; // Maximum number of color changes per layer.
|
||||
int m_old_temperature = -1; // To keep track of what was the last temp that we set (so we don't issue the command when not neccessary)
|
||||
float m_travel_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
size_t m_first_layer_idx = size_t(-1);
|
||||
|
||||
// G-code generator parameters.
|
||||
// QDS: remove useless config
|
||||
//float m_cooling_tube_retraction = 0.f;
|
||||
//float m_cooling_tube_length = 0.f;
|
||||
//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;
|
||||
// QDS: remove useless config
|
||||
//bool m_set_extruder_trimpot = false;
|
||||
bool m_adhesion = true;
|
||||
GCodeFlavor m_gcode_flavor;
|
||||
|
||||
// Bed properties
|
||||
enum {
|
||||
RectangularBed,
|
||||
CircularBed,
|
||||
CustomBed
|
||||
} m_bed_shape;
|
||||
float m_bed_width; // width of the bed bounding box
|
||||
Vec2f m_bed_bottom_left; // bottom-left corner coordinates (for rectangular beds)
|
||||
|
||||
float m_perimeter_width = 0.4f * Width_To_Nozzle_Ratio; // Width of an extrusion line, also a perimeter spacing for 100% infill.
|
||||
float m_extrusion_flow = 0.038f; //0.029f;// Extrusion flow is derived from m_perimeter_width, layer height and filament diameter.
|
||||
|
||||
// Extruder specific parameters.
|
||||
std::vector<FilamentParameters> m_filpar;
|
||||
|
||||
|
||||
// State of the wipe tower generator.
|
||||
unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics.
|
||||
unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics.
|
||||
///unsigned int m_idx_tool_change_in_layer = 0; // Layer change counter in this layer. Counting up to m_max_color_changes.
|
||||
bool m_print_brim = true;
|
||||
// A fill-in direction (positive Y, negative Y) alternates with each layer.
|
||||
wipe_shape m_current_shape = SHAPE_NORMAL;
|
||||
size_t m_current_tool = 0;
|
||||
// QDS
|
||||
//const std::vector<std::vector<float>> wipe_volumes;
|
||||
const float m_wipe_volume;
|
||||
|
||||
float m_depth_traversed = 0.f; // Current y position at the wipe tower.
|
||||
bool m_current_layer_finished = false;
|
||||
bool m_left_to_right = true;
|
||||
float m_extra_spacing = 1.f;
|
||||
|
||||
bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; }
|
||||
|
||||
// Calculates length of extrusion line to extrude given volume
|
||||
float volume_to_length(float volume, float line_width, float layer_height) const {
|
||||
return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))));
|
||||
}
|
||||
|
||||
// Calculates depth for all layers and propagates them downwards
|
||||
void plan_tower();
|
||||
|
||||
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
|
||||
void make_wipe_tower_square();
|
||||
|
||||
// Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe
|
||||
void save_on_last_wipe();
|
||||
|
||||
// QDS
|
||||
box_coordinates align_perimeter(const box_coordinates& perimeter_box);
|
||||
|
||||
|
||||
// to store information about tool changes for a given layer
|
||||
struct WipeTowerInfo{
|
||||
struct ToolChange {
|
||||
size_t old_tool;
|
||||
size_t new_tool;
|
||||
float required_depth;
|
||||
float ramming_depth;
|
||||
float first_wipe_line;
|
||||
float wipe_volume;
|
||||
float wipe_length;
|
||||
// QDS
|
||||
float purge_volume;
|
||||
ToolChange(size_t old, size_t newtool, float depth=0.f, float ramming_depth=0.f, float fwl=0.f, float wv=0.f, float wl = 0, float pv = 0)
|
||||
: old_tool{ old }, new_tool{ newtool }, required_depth{ depth }, ramming_depth{ ramming_depth }, first_wipe_line{ fwl }, wipe_volume{ wv }, wipe_length{ wl }, purge_volume{ pv } {}
|
||||
};
|
||||
float z; // z position of the layer
|
||||
float height; // layer height
|
||||
float depth; // depth of the layer based on all layers above
|
||||
float extra_spacing;
|
||||
bool extruder_fill{true};
|
||||
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
|
||||
|
||||
std::vector<ToolChange> tool_changes;
|
||||
|
||||
WipeTowerInfo(float z_par, float layer_height_par)
|
||||
: z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {}
|
||||
};
|
||||
|
||||
std::vector<WipeTowerInfo> m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...))
|
||||
std::vector<WipeTowerInfo>::iterator m_layer_info = m_plan.end();
|
||||
|
||||
// Stores information about used filament length per extruder:
|
||||
std::vector<float> m_used_filament_length;
|
||||
|
||||
// QDS: consider both soluable and support properties
|
||||
// Return index of first toolchange that switches to non-soluble extruder
|
||||
// ot -1 if there is no such toolchange.
|
||||
int first_toolchange_to_nonsoluble_nonsupport(
|
||||
const std::vector<WipeTowerInfo::ToolChange>& tool_changes) const;
|
||||
|
||||
void toolchange_Unload(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
const std::string& current_material,
|
||||
const int new_temperature);
|
||||
|
||||
void toolchange_Change(
|
||||
WipeTowerWriter &writer,
|
||||
const size_t new_tool,
|
||||
const std::string& new_material);
|
||||
|
||||
void toolchange_Load(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box);
|
||||
|
||||
void toolchange_Wipe(
|
||||
WipeTowerWriter &writer,
|
||||
const box_coordinates &cleaning_box,
|
||||
float wipe_volume);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // WipeTowerPrusaMM_hpp_
|
||||
Reference in New Issue
Block a user