This commit is contained in:
QIDI TECH
2024-09-03 09:34:33 +08:00
parent 27f34aa3e8
commit 585146181b
5147 changed files with 1734881 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View 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_

View 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(&regionPtr->perimeters, perimeters[i].paths);
getExtrusionPathsFromEntity(&regionPtr->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

View 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

View 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> &current_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

View 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> &current_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

File diff suppressed because it is too large Load Diff

View 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_ */

View 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

View 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_ */

View 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

View 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_ */

View 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;
}
}

View 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_ */

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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_

View 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, &current_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;
}
}

View 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_

View 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

View 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_

View 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

View 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_

File diff suppressed because it is too large Load Diff

View 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 &region) const;
unsigned int sparse_infill_filament(const PrintRegion &region) const;
unsigned int solid_infill_filament(const PrintRegion &region) 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 &region) 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_ */

File diff suppressed because it is too large Load Diff

View 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_