mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-01 00:18:44 +03:00
PRUSA 2.7.0
This commit is contained in:
@@ -730,7 +730,7 @@ static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vec
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool need_wipe(const GCode &gcodegen,
|
||||
static bool need_wipe(const GCodeGenerator &gcodegen,
|
||||
const ExPolygons &lslices_offset,
|
||||
const std::vector<BoundingBox> &lslices_offset_bboxes,
|
||||
const EdgeGrid::Grid &grid_lslices_offset,
|
||||
@@ -738,7 +738,7 @@ static bool need_wipe(const GCode &gcodegen,
|
||||
const Polyline &result_travel,
|
||||
const size_t intersection_count)
|
||||
{
|
||||
bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
|
||||
bool z_lift_enabled = gcodegen.config().travel_max_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
|
||||
bool wipe_needed = false;
|
||||
|
||||
// If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely
|
||||
@@ -1167,7 +1167,7 @@ static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons
|
||||
}
|
||||
|
||||
// Plan travel, which avoids perimeter crossings by following the boundaries of the layer.
|
||||
Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
||||
Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, const Point &point, bool *could_be_wipe_disabled)
|
||||
{
|
||||
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
|
||||
// Otherwise perform the path planning in the coordinate system of the active object.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
// Forward declarations.
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
class Point;
|
||||
|
||||
@@ -25,13 +25,13 @@ public:
|
||||
|
||||
void init_layer(const Layer &layer);
|
||||
|
||||
Polyline travel_to(const GCode& gcodegen, const Point& point)
|
||||
Polyline travel_to(const GCodeGenerator &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);
|
||||
Polyline travel_to(const GCodeGenerator &gcodegen, const Point& point, bool* could_be_wipe_disabled);
|
||||
|
||||
struct Boundary {
|
||||
// Collection of boundaries used for detection of crossing perimeters for travels
|
||||
|
||||
@@ -89,6 +89,86 @@ inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance
|
||||
}
|
||||
} // namespace RasterizationImpl
|
||||
|
||||
static std::vector<ExtrusionPaths> getFakeExtrusionPathsFromWipeTower(const WipeTowerData& wtd)
|
||||
{
|
||||
float h = wtd.height;
|
||||
float lh = wtd.first_layer_height;
|
||||
int d = scale_(wtd.depth);
|
||||
int w = scale_(wtd.width);
|
||||
int bd = scale_(wtd.brim_width);
|
||||
Point minCorner = { -wtd.brim_width, -wtd.brim_width };
|
||||
Point maxCorner = { minCorner.x() + w + bd, minCorner.y() + d + bd };
|
||||
float width = wtd.width;
|
||||
float depth = wtd.depth;
|
||||
float height = wtd.height;
|
||||
float cone_angle = wtd.cone_angle;
|
||||
const auto& z_and_depth_pairs = wtd.z_and_depth_pairs;
|
||||
|
||||
const auto [cone_base_R, cone_scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth, cone_angle);
|
||||
|
||||
std::vector<ExtrusionPaths> paths;
|
||||
for (float hh = 0.f; hh < h; hh += lh) {
|
||||
|
||||
if (hh != 0.f) {
|
||||
// The wipe tower may be getting smaller. Find the depth for this layer.
|
||||
size_t i = 0;
|
||||
for (i=0; i<z_and_depth_pairs.size()-1; ++i)
|
||||
if (hh >= z_and_depth_pairs[i].first && hh < z_and_depth_pairs[i+1].first)
|
||||
break;
|
||||
d = scale_(z_and_depth_pairs[i].second);
|
||||
minCorner = {0.f, -d/2 + scale_(z_and_depth_pairs.front().second/2.f)};
|
||||
maxCorner = { minCorner.x() + w, minCorner.y() + d };
|
||||
}
|
||||
|
||||
|
||||
ExtrusionPath path({ minCorner, {maxCorner.x(), minCorner.y()}, maxCorner, {minCorner.x(), maxCorner.y()}, minCorner },
|
||||
ExtrusionAttributes{ ExtrusionRole::WipeTower, ExtrusionFlow{ 0.0, 0.0, lh } });
|
||||
paths.push_back({ path });
|
||||
|
||||
// We added the border, now add several parallel lines so we can detect an object that is fully inside the tower.
|
||||
// For now, simply use fixed spacing of 3mm.
|
||||
for (coord_t y=minCorner.y()+scale_(3.); y<maxCorner.y(); y+=scale_(3.)) {
|
||||
path.polyline = { {minCorner.x(), y}, {maxCorner.x(), y} };
|
||||
paths.back().emplace_back(path);
|
||||
}
|
||||
|
||||
// And of course the stabilization cone and its base...
|
||||
if (cone_base_R > 0.) {
|
||||
path.polyline.clear();
|
||||
double r = cone_base_R * (1 - hh/height);
|
||||
for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.)
|
||||
path.polyline.points.emplace_back(Point::new_scale(width/2. + r * std::cos(alpha)/cone_scale_x, depth/2. + r * std::sin(alpha)));
|
||||
paths.back().emplace_back(path);
|
||||
if (hh == 0.f) { // Cone brim.
|
||||
for (float bw=wtd.brim_width; bw>0.f; bw-=3.f) {
|
||||
path.polyline.clear();
|
||||
for (double alpha=0; alpha<2.01*M_PI; alpha+=2*M_PI/20.) // see load_wipe_tower_preview, where the same is a bit clearer
|
||||
path.polyline.points.emplace_back(Point::new_scale(
|
||||
width/2. + cone_base_R * std::cos(alpha)/cone_scale_x * (1. + cone_scale_x*bw/cone_base_R),
|
||||
depth/2. + cone_base_R * std::sin(alpha) * (1. + bw/cone_base_R))
|
||||
);
|
||||
paths.back().emplace_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only the first layer has brim.
|
||||
if (hh == 0.f) {
|
||||
minCorner = minCorner + Point(bd, bd);
|
||||
maxCorner = maxCorner - Point(bd, bd);
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate and translate the tower into the final position.
|
||||
for (ExtrusionPaths& ps : paths) {
|
||||
for (ExtrusionPath& p : ps) {
|
||||
p.polyline.rotate(Geometry::deg2rad(wtd.rotation_angle));
|
||||
p.polyline.translate(scale_(wtd.position.x()), scale_(wtd.position.y()));
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
void LinesBucketQueue::emplace_back_bucket(std::vector<ExtrusionPaths> &&paths, const void *objPtr, Points offsets)
|
||||
{
|
||||
if (_objsPtrToId.find(objPtr) == _objsPtrToId.end()) {
|
||||
@@ -165,14 +245,14 @@ ExtrusionPaths getExtrusionPathsFromLayer(LayerRegionPtrs layerRegionPtrs)
|
||||
return paths;
|
||||
}
|
||||
|
||||
ExtrusionPaths getExtrusionPathsFromSupportLayer(SupportLayer *supportLayer)
|
||||
ExtrusionPaths getExtrusionPathsFromSupportLayer(const SupportLayer *supportLayer)
|
||||
{
|
||||
ExtrusionPaths paths;
|
||||
getExtrusionPathsFromEntity(&supportLayer->support_fills, paths);
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(PrintObject *obj)
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> getAllLayersExtrusionPathsFromObject(const PrintObject *obj)
|
||||
{
|
||||
std::vector<ExtrusionPaths> objPaths, supportPaths;
|
||||
|
||||
@@ -203,17 +283,22 @@ ConflictComputeOpt ConflictChecker::find_inter_of_lines(const LineWithIDs &lines
|
||||
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
|
||||
ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(SpanOfConstPtrs<PrintObject> objs,
|
||||
const WipeTowerData& wipe_tower_data) // find the first intersection point of lines in different objects
|
||||
{
|
||||
if (objs.empty() || (objs.size() == 1 && objs.front()->instances().size() == 1)) { return {}; }
|
||||
|
||||
// The code ported from BS uses void* to identify objects...
|
||||
// Let's use the address of this variable to represent the wipe tower.
|
||||
int wtptr = 0;
|
||||
LinesBucketQueue conflictQueue;
|
||||
if (wtdptr.has_value()) { // wipe tower at 0 by default
|
||||
std::vector<ExtrusionPaths> wtpaths = (*wtdptr)->getFakeExtrusionPathsFromWipeTower();
|
||||
conflictQueue.emplace_back_bucket(std::move(wtpaths), *wtdptr, Points{Point((*wtdptr)->plate_origin)});
|
||||
if (! wipe_tower_data.z_and_depth_pairs.empty()) {
|
||||
// The wipe tower is being generated.
|
||||
const Vec2d plate_origin = Vec2d::Zero();
|
||||
std::vector<ExtrusionPaths> wtpaths = getFakeExtrusionPathsFromWipeTower(wipe_tower_data);
|
||||
conflictQueue.emplace_back_bucket(std::move(wtpaths), &wtptr, Points{Point(plate_origin)});
|
||||
}
|
||||
for (PrintObject *obj : objs) {
|
||||
for (const PrintObject *obj : objs) {
|
||||
std::pair<std::vector<ExtrusionPaths>, std::vector<ExtrusionPaths>> layers = getAllLayersExtrusionPathsFromObject(obj);
|
||||
|
||||
Points instances_shifts;
|
||||
@@ -256,14 +341,12 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(PrintObjectP
|
||||
const void *ptr1 = conflictQueue.idToObjsPtr(conflict[0].first._obj1);
|
||||
const void *ptr2 = conflictQueue.idToObjsPtr(conflict[0].first._obj2);
|
||||
double conflictHeight = conflict[0].second;
|
||||
if (wtdptr.has_value()) {
|
||||
const FakeWipeTower* wtdp = *wtdptr;
|
||||
if (ptr1 == wtdp || ptr2 == wtdp) {
|
||||
if (ptr2 == wtdp) { std::swap(ptr1, ptr2); }
|
||||
if (ptr1 == &wtptr || ptr2 == &wtptr) {
|
||||
assert(! wipe_tower_data.z_and_depth_pairs.empty());
|
||||
if (ptr2 == &wtptr) { std::swap(ptr1, ptr2); }
|
||||
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(ptr2);
|
||||
return std::make_optional<ConflictResult>("WipeTower", obj2->model_object()->name, conflictHeight, 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, conflictHeight, ptr1, ptr2);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
#ifndef slic3r_ConflictChecker_hpp_
|
||||
#define slic3r_ConflictChecker_hpp_
|
||||
|
||||
#include "../Utils.hpp"
|
||||
#include "../Model.hpp"
|
||||
#include "../Print.hpp"
|
||||
#include "../Layer.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
@@ -43,7 +40,7 @@ public:
|
||||
void raise()
|
||||
{
|
||||
if (valid()) {
|
||||
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height; }
|
||||
if (_piles[_curPileIdx].empty() == false) { _curHeight += _piles[_curPileIdx].front().height(); }
|
||||
_curPileIdx++;
|
||||
}
|
||||
}
|
||||
@@ -119,7 +116,7 @@ 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 ConflictResultOpt find_inter_of_lines_in_diff_objs(SpanOfConstPtrs<PrintObject> objs, const WipeTowerData& wtd);
|
||||
static ConflictComputeOpt find_inter_of_lines(const LineWithIDs &lines);
|
||||
static ConflictComputeOpt line_intersect(const LineWithID &l1, const LineWithID &l2);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
CoolingBuffer::CoolingBuffer(GCodeGenerator &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0)
|
||||
{
|
||||
this->reset(gcodegen.writer().get_position());
|
||||
|
||||
@@ -33,39 +33,45 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t
|
||||
|
||||
void CoolingBuffer::reset(const Vec3d &position)
|
||||
{
|
||||
m_current_pos.assign(5, 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;
|
||||
//Y12
|
||||
m_fan_speed = -1;
|
||||
assert(m_current_pos.size() == 5);
|
||||
m_current_pos[AxisIdx::X] = float(position.x());
|
||||
m_current_pos[AxisIdx::Y] = float(position.y());
|
||||
m_current_pos[AxisIdx::Z] = float(position.z());
|
||||
m_current_pos[AxisIdx::E] = 0.f;
|
||||
m_current_pos[AxisIdx::F] = float(m_config.travel_speed.value);
|
||||
m_fan_speed = -1;
|
||||
}
|
||||
|
||||
struct CoolingLine
|
||||
{
|
||||
enum Type {
|
||||
enum Type : uint32_t {
|
||||
TYPE_SET_TOOL = 1 << 0,
|
||||
TYPE_EXTRUDE_END = 1 << 1,
|
||||
TYPE_BRIDGE_FAN_START = 1 << 2,
|
||||
TYPE_BRIDGE_FAN_END = 1 << 3,
|
||||
TYPE_G0 = 1 << 4,
|
||||
TYPE_G1 = 1 << 5,
|
||||
TYPE_ADJUSTABLE = 1 << 6,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||
// G2 or G3: Arc interpolation
|
||||
TYPE_G2G3 = 1 << 6,
|
||||
TYPE_ADJUSTABLE = 1 << 7,
|
||||
TYPE_EXTERNAL_PERIMETER = 1 << 8,
|
||||
// Arc interpolation, counter-clockwise.
|
||||
TYPE_G2G3_CCW = 1 << 9,
|
||||
// Arc interpolation, arc defined by IJ (offset of arc center from its start position).
|
||||
TYPE_G2G3_IJ = 1 << 10,
|
||||
// Arc interpolation, arc defined by R (arc radius, positive - smaller, negative - larger).
|
||||
TYPE_G2G3_R = 1 << 11,
|
||||
// The line sets a feedrate.
|
||||
TYPE_HAS_F = 1 << 8,
|
||||
TYPE_WIPE = 1 << 9,
|
||||
TYPE_G4 = 1 << 10,
|
||||
TYPE_G92 = 1 << 11,
|
||||
TYPE_HAS_F = 1 << 12,
|
||||
TYPE_WIPE = 1 << 13,
|
||||
TYPE_G4 = 1 << 14,
|
||||
TYPE_G92 = 1 << 15,
|
||||
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
|
||||
// cannot have its speed adjusted. This should not happen (sic!).
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 12,
|
||||
TYPE_ADJUSTABLE_EMPTY = 1 << 16,
|
||||
// Custom fan speed (introduced for overhang fan speed)
|
||||
TYPE_SET_FAN_SPEED = 1 << 13,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 14,
|
||||
TYPE_SET_FAN_SPEED = 1 << 17,
|
||||
TYPE_RESET_FAN_SPEED = 1 << 18,
|
||||
};
|
||||
|
||||
CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
|
||||
@@ -327,7 +333,7 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b
|
||||
|
||||
// Parse the layer G-code for the moves, which could be adjusted.
|
||||
// Return the list of parsed lines, bucketed by an extruder.
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const
|
||||
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array<float, 5> ¤t_pos) const
|
||||
{
|
||||
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
|
||||
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
|
||||
@@ -350,7 +356,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
// for a sequence of extrusion moves.
|
||||
size_t active_speed_modifier = size_t(-1);
|
||||
|
||||
std::vector<float> new_pos;
|
||||
std::array<float, AxisIdx::Count> new_pos;
|
||||
for (; *line_start != 0; line_start = line_end)
|
||||
{
|
||||
while (*line_end != '\n' && *line_end != 0)
|
||||
@@ -365,12 +371,20 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = CoolingLine::TYPE_G0;
|
||||
else if (boost::starts_with(sline, "G1 "))
|
||||
line.type = CoolingLine::TYPE_G1;
|
||||
else if (boost::starts_with(sline, "G2 "))
|
||||
// Arc, clockwise.
|
||||
line.type = CoolingLine::TYPE_G2G3;
|
||||
else if (boost::starts_with(sline, "G3 "))
|
||||
// Arc, counter-clockwise.
|
||||
line.type = CoolingLine::TYPE_G2G3 | CoolingLine::TYPE_G2G3_CCW;
|
||||
else if (boost::starts_with(sline, "G92 "))
|
||||
line.type = CoolingLine::TYPE_G92;
|
||||
if (line.type) {
|
||||
// G0, G1 or G92
|
||||
// G0, G1, G2, G3 or G92
|
||||
// Initialize current_pos from new_pos, set IJKR to zero.
|
||||
std::fill(std::copy(std::begin(current_pos), std::end(current_pos), std::begin(new_pos)),
|
||||
std::end(new_pos), 0.f);
|
||||
// Parse the G-code line.
|
||||
new_pos = current_pos;
|
||||
for (auto c = sline.begin() + 3;;) {
|
||||
// Skip whitespaces.
|
||||
for (; c != sline.end() && (*c == ' ' || *c == '\t'); ++ c);
|
||||
@@ -379,21 +393,31 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
|
||||
// Parse the axis.
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||
(*c == extrusion_axis) ? AxisIdx::E : (*c == 'F') ? AxisIdx::F :
|
||||
(*c >= 'I' && *c <= 'K') ? int(AxisIdx::I) + (*c - 'I') :
|
||||
(*c == 'R') ? AxisIdx::R : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
if (axis == 4) {
|
||||
if (axis == AxisIdx::F) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
new_pos[AxisIdx::F] /= 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 >= AxisIdx::I && axis <= AxisIdx::J)
|
||||
line.type |= CoolingLine::TYPE_G2G3_IJ;
|
||||
else if (axis == AxisIdx::R)
|
||||
line.type |= CoolingLine::TYPE_G2G3_R;
|
||||
}
|
||||
// Skip this word.
|
||||
for (; c != sline.end() && *c != ' ' && *c != '\t'; ++ c);
|
||||
}
|
||||
// If G2 or G3, then either center of the arc or radius has to be defined.
|
||||
assert(! (line.type & CoolingLine::TYPE_G2G3) ||
|
||||
(line.type & (CoolingLine::TYPE_G2G3_IJ | CoolingLine::TYPE_G2G3_R)));
|
||||
// Arc is defined either by IJ or by R, not by both.
|
||||
assert(! ((line.type & CoolingLine::TYPE_G2G3_IJ) && (line.type & CoolingLine::TYPE_G2G3_R)));
|
||||
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||
bool wipe = boost::contains(sline, ";_WIPE");
|
||||
if (external_perimeter)
|
||||
@@ -405,23 +429,41 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
active_speed_modifier = adjustment->lines.size();
|
||||
}
|
||||
if ((line.type & CoolingLine::TYPE_G92) == 0) {
|
||||
// G0 or G1. Calculate the duration.
|
||||
// G0, G1, G2, G3. Calculate the duration.
|
||||
assert((line.type & CoolingLine::TYPE_G0) != 0 + (line.type & CoolingLine::TYPE_G1) != 0 + (line.type & CoolingLine::TYPE_G2G3) != 0 == 1);
|
||||
if (m_config.use_relative_e_distances.value)
|
||||
// Reset extruder accumulator.
|
||||
current_pos[3] = 0.f;
|
||||
current_pos[AxisIdx::E] = 0.f;
|
||||
float dif[4];
|
||||
for (size_t i = 0; i < 4; ++ i)
|
||||
dif[i] = new_pos[i] - current_pos[i];
|
||||
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||
float dxy2;
|
||||
if (line.type & CoolingLine::TYPE_G2G3) {
|
||||
// Measure arc length.
|
||||
if (line.type & CoolingLine::TYPE_G2G3_IJ) {
|
||||
dxy2 = sqr(Geometry::ArcWelder::arc_length(
|
||||
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
|
||||
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
|
||||
Vec2d(current_pos[AxisIdx::X] + new_pos[AxisIdx::I], current_pos[AxisIdx::Y] + new_pos[AxisIdx::J]),
|
||||
line.type & CoolingLine::TYPE_G2G3_CCW));
|
||||
} else if (line.type & CoolingLine::TYPE_G2G3_R) {
|
||||
dxy2 = sqr(Geometry::ArcWelder::arc_length(
|
||||
Vec2d(current_pos[AxisIdx::X], current_pos[AxisIdx::Y]),
|
||||
Vec2d(new_pos[AxisIdx::X], new_pos[AxisIdx::Y]),
|
||||
double(new_pos[AxisIdx::R])));
|
||||
} else
|
||||
dxy2 = 0;
|
||||
} else
|
||||
dxy2 = sqr(dif[AxisIdx::X]) + sqr(dif[AxisIdx::Y]);
|
||||
float dxyz2 = dxy2 + sqr(dif[AxisIdx::Z]);
|
||||
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) {
|
||||
} else if (std::abs(dif[AxisIdx::E]) > 0.f) {
|
||||
// Movement in the extruder axis.
|
||||
line.length = std::abs(dif[3]);
|
||||
line.length = std::abs(dif[AxisIdx::E]);
|
||||
}
|
||||
line.feedrate = new_pos[4];
|
||||
line.feedrate = new_pos[AxisIdx::F];
|
||||
assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
|
||||
if (line.length > 0) {
|
||||
assert(line.feedrate > 0);
|
||||
@@ -433,7 +475,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
assert(adjustment->min_print_speed >= 0);
|
||||
line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
|
||||
}
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
|
||||
if (active_speed_modifier < adjustment->lines.size() && (line.type & (CoolingLine::TYPE_G1 | CoolingLine::TYPE_G2G3))) {
|
||||
// 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];
|
||||
@@ -450,7 +492,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
line.type = 0;
|
||||
}
|
||||
}
|
||||
current_pos = std::move(new_pos);
|
||||
std::copy(std::begin(new_pos), std::begin(new_pos) + 5, std::begin(current_pos));
|
||||
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||
// Closing a block of non-zero length extrusion moves.
|
||||
line.type = CoolingLine::TYPE_EXTRUDE_END;
|
||||
@@ -508,16 +550,18 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
} else
|
||||
line.time = 0;
|
||||
line.time_max = line.time;
|
||||
} else if (boost::contains(sline, ";_SET_FAN_SPEED")) {
|
||||
}
|
||||
|
||||
if (boost::contains(sline, ";_SET_FAN_SPEED")) {
|
||||
auto speed_start = sline.find_last_of('D');
|
||||
int speed = 0;
|
||||
for (char num : sline.substr(speed_start + 1)) {
|
||||
speed = speed * 10 + (num - '0');
|
||||
}
|
||||
line.type = CoolingLine::TYPE_SET_FAN_SPEED;
|
||||
line.type |= CoolingLine::TYPE_SET_FAN_SPEED;
|
||||
line.fan_speed = speed;
|
||||
} else if (boost::contains(sline, ";_RESET_FAN_SPEED")) {
|
||||
line.type = CoolingLine::TYPE_RESET_FAN_SPEED;
|
||||
line.type |= CoolingLine::TYPE_RESET_FAN_SPEED;
|
||||
}
|
||||
|
||||
if (line.type != 0)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCode;
|
||||
class GCodeGenerator;
|
||||
class Layer;
|
||||
struct PerExtruderAdjustments;
|
||||
|
||||
@@ -22,7 +22,7 @@ struct PerExtruderAdjustments;
|
||||
//
|
||||
class CoolingBuffer {
|
||||
public:
|
||||
CoolingBuffer(GCode &gcodegen);
|
||||
CoolingBuffer(GCodeGenerator &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);
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
|
||||
private:
|
||||
CoolingBuffer& operator=(const CoolingBuffer&) = delete;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> ¤t_pos) const;
|
||||
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::array<float, 5> ¤t_pos) const;
|
||||
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
|
||||
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
|
||||
// Returns the adjusted G-code.
|
||||
@@ -40,9 +40,11 @@ private:
|
||||
// G-code snippet cached for the support layers preceding an object layer.
|
||||
std::string m_gcode;
|
||||
// Internal data.
|
||||
// X,Y,Z,E,F
|
||||
std::vector<char> m_axis;
|
||||
std::vector<float> m_current_pos;
|
||||
enum AxisIdx : int {
|
||||
X = 0, Y, Z, E, F, I, J, K, R, Count
|
||||
};
|
||||
std::array<float, 5> m_current_pos;
|
||||
// Current known fan speed or -1 if not known yet.
|
||||
int m_fan_speed;
|
||||
//Y12
|
||||
@@ -54,7 +56,7 @@ private:
|
||||
// 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,
|
||||
// Referencs GCodeGenerator::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;
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
#ifndef _DATA_TYPE_H_
|
||||
#define _DATA_TYPE_H_
|
||||
|
||||
//#include "framework.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifndef null
|
||||
#define null 0
|
||||
#endif
|
||||
#ifndef NULL
|
||||
#define NULL 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#ifndef U8
|
||||
typedef unsigned char U8;
|
||||
#endif
|
||||
|
||||
#ifndef S8
|
||||
typedef signed char S8;
|
||||
#endif
|
||||
|
||||
#ifndef U16
|
||||
typedef unsigned short U16;
|
||||
#endif
|
||||
|
||||
#ifndef S16
|
||||
typedef signed short S16;
|
||||
#endif
|
||||
|
||||
#ifndef U32
|
||||
typedef unsigned int U32;
|
||||
#endif
|
||||
|
||||
#ifndef S32
|
||||
typedef signed int S32;
|
||||
#endif
|
||||
|
||||
#ifndef S64
|
||||
typedef signed long long S64;
|
||||
#endif
|
||||
|
||||
#ifndef U64
|
||||
typedef unsigned long long U64;
|
||||
#endif
|
||||
|
||||
#ifndef FP32
|
||||
typedef float FP32;
|
||||
#endif
|
||||
|
||||
#ifndef FP64
|
||||
typedef double FP64;
|
||||
#endif
|
||||
|
||||
#ifndef Pixel_t
|
||||
typedef unsigned short Pixel_t;
|
||||
#endif
|
||||
|
||||
#ifndef UINT32
|
||||
typedef unsigned int UINT32;
|
||||
#endif
|
||||
|
||||
|
||||
#ifndef INT
|
||||
typedef int INT;
|
||||
#endif
|
||||
|
||||
// #ifndef INT32
|
||||
// typedef int INT32;
|
||||
// #endif
|
||||
|
||||
typedef unsigned char INT8U; /* Unsigned 8 bit quantity */
|
||||
typedef signed char INT8S; /* Signed 8 bit quantity */
|
||||
typedef unsigned short INT16U; /* Unsigned 16 bit quantity */
|
||||
typedef signed short INT16S; /* Signed 16 bit quantity */
|
||||
typedef unsigned int INT32U; /* Unsigned 32 bit quantity */
|
||||
typedef signed int INT32S; /* Signed 32 bit quantity */
|
||||
typedef unsigned long long INT64U;
|
||||
typedef signed long long INT64S;
|
||||
|
||||
typedef float FP32; /* Single precision floating point */
|
||||
typedef double FP64; /* Double precision floating point */
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
U16 star;
|
||||
U16 end;
|
||||
}PosLaction;
|
||||
typedef struct
|
||||
{
|
||||
int a0;
|
||||
int a1;
|
||||
int a2;
|
||||
int a3;
|
||||
int a4;
|
||||
int a5;
|
||||
int a6;
|
||||
int a7;
|
||||
int a8;
|
||||
int a9;
|
||||
int a10;
|
||||
int a11;
|
||||
int a12;
|
||||
int a13;
|
||||
int a14;
|
||||
int a15;
|
||||
}bytes_64Bytes;
|
||||
typedef struct
|
||||
{
|
||||
bytes_64Bytes a0;
|
||||
bytes_64Bytes a1;
|
||||
bytes_64Bytes a2;
|
||||
bytes_64Bytes a3;
|
||||
}bytes_256Bytes;
|
||||
typedef struct
|
||||
{
|
||||
bytes_64Bytes a0;
|
||||
bytes_64Bytes a1;
|
||||
bytes_64Bytes a2;
|
||||
bytes_64Bytes a3;
|
||||
bytes_64Bytes a4;
|
||||
bytes_64Bytes a5;
|
||||
bytes_64Bytes a6;
|
||||
bytes_64Bytes a7;
|
||||
bytes_64Bytes a8;
|
||||
bytes_64Bytes a9;
|
||||
bytes_64Bytes a10;
|
||||
bytes_64Bytes a11;
|
||||
bytes_64Bytes a12;
|
||||
bytes_64Bytes a13;
|
||||
bytes_64Bytes a14;
|
||||
bytes_64Bytes a15;
|
||||
}bytes_1024Bytes;
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
216
src/libslic3r/GCode/ExtrusionProcessor.cpp
Normal file
216
src/libslic3r/GCode/ExtrusionProcessor.cpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#include "ExtrusionProcessor.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r { namespace ExtrusionProcessor {
|
||||
|
||||
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
|
||||
{
|
||||
std::vector<ExtendedPoint> extended_points = estimate_points_properties<true, true, true, true>(path.polyline.points,
|
||||
unscaled_prev_layer, path.width());
|
||||
std::vector<std::pair<float, float>> calculated_distances(extended_points.size());
|
||||
|
||||
for (size_t i = 0; i < extended_points.size(); i++) {
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
|
||||
float proximity_to_curled_lines = 0.0;
|
||||
const double dist_limit = 10.0 * path.width();
|
||||
{
|
||||
Vec2d middle = 0.5 * (curr.position + next.position);
|
||||
auto line_indices = prev_layer_curled_lines.all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
|
||||
if (!line_indices.empty()) {
|
||||
double len = (next.position - curr.position).norm();
|
||||
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle
|
||||
// of this long line
|
||||
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
|
||||
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
|
||||
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
|
||||
if (len > 8) {
|
||||
Vec2d dir = Vec2d(next.position - curr.position) / len;
|
||||
Vec2d right = Vec2d(-dir.y(), dir.x());
|
||||
|
||||
Polygon box_of_influence = {
|
||||
scaled(Vec2d(curr.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position - right * dist_limit)),
|
||||
scaled(Vec2d(curr.position - right * dist_limit)),
|
||||
};
|
||||
|
||||
double projected_lengths_sum = 0;
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
|
||||
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
|
||||
if (inside.empty())
|
||||
continue;
|
||||
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
|
||||
projected_lengths_sum += projected_length;
|
||||
}
|
||||
if (projected_lengths_sum < 0.4 * len) {
|
||||
line_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_layer_curled_lines.get_line(idx);
|
||||
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
|
||||
float proximity = (1.0 - (distance_from_curled / dist_limit)) * (1.0 - (distance_from_curled / dist_limit)) *
|
||||
(line.curled_height / (path.height() * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
|
||||
proximity_to_curled_lines = std::max(proximity_to_curled_lines, proximity);
|
||||
}
|
||||
}
|
||||
}
|
||||
calculated_distances[i].first = std::max(curr.distance, next.distance);
|
||||
calculated_distances[i].second = proximity_to_curled_lines;
|
||||
}
|
||||
|
||||
ExtrusionPaths result;
|
||||
ExtrusionAttributes new_attrs = path.attributes();
|
||||
new_attrs.overhang_attributes = std::optional<OverhangAttributes>(
|
||||
{calculated_distances[0].first, calculated_distances[0].first, calculated_distances[0].second});
|
||||
result.emplace_back(new_attrs);
|
||||
result.back().polyline.append(Point::new_scale(extended_points[0].position));
|
||||
size_t sequence_start_index = 0;
|
||||
for (size_t i = 1; i < extended_points.size(); i++) {
|
||||
result.back().polyline.append(Point::new_scale(extended_points[i].position));
|
||||
result.back().overhang_attributes_mutable()->end_distance_from_prev_layer = extended_points[i].distance;
|
||||
|
||||
if (std::abs(calculated_distances[sequence_start_index].first - calculated_distances[i].first) < 0.001 * path.attributes().width &&
|
||||
std::abs(calculated_distances[sequence_start_index].second - calculated_distances[i].second) < 0.001) {
|
||||
// do not start new path, the attributes are similar enough
|
||||
// NOTE: a larger tolerance may be applied here. However, it makes the gcode preview much less smooth
|
||||
// (But it has very likely zero impact on the print quality.)
|
||||
} else if (i + 1 < extended_points.size()) { // do not start new path if this is last point!
|
||||
// start new path, parameters differ
|
||||
new_attrs.overhang_attributes->start_distance_from_prev_layer = calculated_distances[i].first;
|
||||
new_attrs.overhang_attributes->end_distance_from_prev_layer = calculated_distances[i].first;
|
||||
new_attrs.overhang_attributes->proximity_to_curled_lines = calculated_distances[i].second;
|
||||
sequence_start_index = i;
|
||||
result.emplace_back(new_attrs);
|
||||
result.back().polyline.append(Point::new_scale(extended_points[i].position));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const ExtrusionEntityCollection *ecc,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines)
|
||||
{
|
||||
ExtrusionEntityCollection result{};
|
||||
result.no_sort = ecc->no_sort;
|
||||
for (const auto *e : ecc->entities) {
|
||||
if (auto *col = dynamic_cast<const ExtrusionEntityCollection *>(e)) {
|
||||
result.append(calculate_and_split_overhanging_extrusions(col, unscaled_prev_layer, prev_layer_curled_lines));
|
||||
} else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(e)) {
|
||||
ExtrusionLoop new_loop = *loop;
|
||||
new_loop.paths.clear();
|
||||
for (const ExtrusionPath &p : loop->paths) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
new_loop.paths.insert(new_loop.paths.end(), paths.begin(), paths.end());
|
||||
}
|
||||
result.append(new_loop);
|
||||
} else if (auto *mp = dynamic_cast<const ExtrusionMultiPath *>(e)) {
|
||||
ExtrusionMultiPath new_mp = *mp;
|
||||
new_mp.paths.clear();
|
||||
for (const ExtrusionPath &p : mp->paths) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
new_mp.paths.insert(new_mp.paths.end(), paths.begin(), paths.end());
|
||||
}
|
||||
result.append(new_mp);
|
||||
} else if (auto *op = dynamic_cast<const ExtrusionPathOriented *>(e)) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(*op, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
for (const ExtrusionPath &p : paths) {
|
||||
result.append(ExtrusionPathOriented(p.polyline, p.attributes()));
|
||||
}
|
||||
} else if (auto *p = dynamic_cast<const ExtrusionPath *>(e)) {
|
||||
auto paths = calculate_and_split_overhanging_extrusions(*p, unscaled_prev_layer, prev_layer_curled_lines);
|
||||
result.append(paths);
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Unknown extrusion entity type");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
std::pair<float,float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
|
||||
const FullPrintConfig &config,
|
||||
size_t extruder_id,
|
||||
float external_perim_reference_speed,
|
||||
float default_speed)
|
||||
{
|
||||
assert(attributes.overhang_attributes.has_value());
|
||||
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
if (config.enable_dynamic_overhang_speeds) {
|
||||
overhangs_with_speeds = {{0, config.overhang_speed_0},
|
||||
{25, config.overhang_speed_1},
|
||||
{50, config.overhang_speed_2},
|
||||
{75, config.overhang_speed_3},
|
||||
{100, ConfigOptionFloatOrPercent{default_speed, false}}};
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, ConfigOptionInts>> overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}};
|
||||
if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
|
||||
overhang_with_fan_speeds = {{0, config.overhang_fan_speed_0},
|
||||
{25, config.overhang_fan_speed_1},
|
||||
{50, config.overhang_fan_speed_2},
|
||||
{75, config.overhang_fan_speed_3},
|
||||
{100, ConfigOptionInts{0}}};
|
||||
}
|
||||
|
||||
float speed_base = external_perim_reference_speed > 0 ? external_perim_reference_speed : default_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_with_speeds.size(); i++) {
|
||||
float distance = attributes.width * (1.0 - (overhangs_with_speeds[i].first / 100.0));
|
||||
float speed = overhangs_with_speeds[i].second.percent ? (speed_base * overhangs_with_speeds[i].second.value / 100.0) :
|
||||
overhangs_with_speeds[i].second.value;
|
||||
if (speed < EPSILON)
|
||||
speed = speed_base;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
|
||||
std::map<float, float> fan_speed_sections;
|
||||
for (size_t i = 0; i < overhang_with_fan_speeds.size(); i++) {
|
||||
float distance = attributes.width * (1.0 - (overhang_with_fan_speeds[i].first / 100.0));
|
||||
float fan_speed = overhang_with_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
float curled_base_speed = interpolate_speed(speed_sections,
|
||||
attributes.width * attributes.overhang_attributes->proximity_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer),
|
||||
interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer));
|
||||
|
||||
if (!config.enable_dynamic_overhang_speeds) {
|
||||
final_speed = -1;
|
||||
}
|
||||
if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) {
|
||||
fan_speed = -1;
|
||||
}
|
||||
|
||||
return {final_speed, fan_speed};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
||||
@@ -14,19 +14,23 @@
|
||||
#include "../Flow.hpp"
|
||||
#include "../Config.hpp"
|
||||
#include "../Line.hpp"
|
||||
#include "../Exception.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r { namespace ExtrusionProcessor {
|
||||
|
||||
struct ExtendedPoint
|
||||
{
|
||||
@@ -41,6 +45,33 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
float flow_width,
|
||||
float max_line_length = -1.0f)
|
||||
{
|
||||
bool looped = input_points.front() == input_points.back();
|
||||
std::function<size_t(size_t,size_t)> get_prev_index = [](size_t idx, size_t count) {
|
||||
if (idx > 0) {
|
||||
return idx - 1;
|
||||
} else
|
||||
return idx;
|
||||
};
|
||||
if (looped) {
|
||||
get_prev_index = [](size_t idx, size_t count) {
|
||||
if (idx == 0)
|
||||
idx = count;
|
||||
return --idx;
|
||||
};
|
||||
};
|
||||
std::function<size_t(size_t,size_t)> get_next_index = [](size_t idx, size_t size) {
|
||||
if (idx + 1 < size) {
|
||||
return idx + 1;
|
||||
} else
|
||||
return idx;
|
||||
};
|
||||
if (looped) {
|
||||
get_next_index = [](size_t idx, size_t count) {
|
||||
if (++idx == count)
|
||||
idx = 0;
|
||||
return idx;
|
||||
};
|
||||
};
|
||||
using P = typename POINTS::value_type;
|
||||
|
||||
using AABBScalar = typename AABBTreeLines::LinesDistancer<L>::Scalar;
|
||||
@@ -54,19 +85,22 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
|
||||
{
|
||||
ExtendedPoint start_point{maybe_unscale(input_points.front())};
|
||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||
auto [distance, nearest_line,
|
||||
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(start_point.position.cast<AABBScalar>());
|
||||
start_point.distance = distance + boundary_offset;
|
||||
points.push_back(start_point);
|
||||
}
|
||||
for (size_t i = 1; i < input_points.size(); i++) {
|
||||
ExtendedPoint next_point{maybe_unscale(input_points[i])};
|
||||
auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||
auto [distance, nearest_line,
|
||||
x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(next_point.position.cast<AABBScalar>());
|
||||
next_point.distance = distance + boundary_offset;
|
||||
|
||||
if (ADD_INTERSECTIONS &&
|
||||
((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) {
|
||||
const ExtendedPoint &prev_point = points.back();
|
||||
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
|
||||
auto intersections = unscaled_prev_layer.template intersections_with_line<true>(
|
||||
L{prev_point.position.cast<AABBScalar>(), next_point.position.cast<AABBScalar>()});
|
||||
for (const auto &intersection : intersections) {
|
||||
ExtendedPoint p{};
|
||||
p.position = intersection.first.template cast<double>();
|
||||
@@ -85,18 +119,19 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
const ExtendedPoint &curr = points[point_idx];
|
||||
const ExtendedPoint &next = points[point_idx + 1];
|
||||
|
||||
if ((curr.distance > 0 && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > 0 && next.distance < boundary_offset + 2.0f)) {
|
||||
if ((curr.distance > -boundary_offset && curr.distance < boundary_offset + 2.0f) ||
|
||||
(next.distance > -boundary_offset && next.distance < boundary_offset + 2.0f)) {
|
||||
double line_len = (next.position - curr.position).norm();
|
||||
if (line_len > 4.0f) {
|
||||
double a0 = std::clamp((curr.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 2 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a0 = std::clamp((curr.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double a1 = std::clamp(1.0f - (next.distance + 3 * boundary_offset) / line_len, 0.0, 1.0);
|
||||
double t0 = std::min(a0, a1);
|
||||
double t1 = std::max(a0, a1);
|
||||
|
||||
if (t0 < 1.0) {
|
||||
auto p0 = curr.position + t0 * (next.position - curr.position);
|
||||
auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
|
||||
auto [p0_dist, p0_near_l,
|
||||
p0_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p0.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = p0;
|
||||
new_p.distance = float(p0_dist + boundary_offset);
|
||||
@@ -104,7 +139,8 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
}
|
||||
if (t1 > 0.0) {
|
||||
auto p1 = curr.position + t1 * (next.position - curr.position);
|
||||
auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
|
||||
auto [p1_dist, p1_near_l,
|
||||
p1_x] = unscaled_prev_layer.template distance_from_lines_extra<SIGNED_DISTANCE>(p1.cast<AABBScalar>());
|
||||
ExtendedPoint new_p{};
|
||||
new_p.position = p1;
|
||||
new_p.distance = float(p1_dist + boundary_offset);
|
||||
@@ -114,7 +150,7 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
}
|
||||
new_points.push_back(next);
|
||||
}
|
||||
points = new_points;
|
||||
points = std::move(new_points);
|
||||
}
|
||||
|
||||
if (max_line_length > 0) {
|
||||
@@ -140,216 +176,92 @@ std::vector<ExtendedPoint> estimate_points_properties(const POINTS
|
||||
}
|
||||
new_points.push_back(points.back());
|
||||
}
|
||||
points = new_points;
|
||||
points = std::move(new_points);
|
||||
}
|
||||
|
||||
std::vector<float> angles_for_curvature(points.size());
|
||||
float accumulated_distance = 0;
|
||||
std::vector<float> distances_for_curvature(points.size());
|
||||
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
ExtendedPoint &a = points[point_idx];
|
||||
size_t prev = prev_idx_modulo(point_idx, points.size());
|
||||
size_t next = next_idx_modulo(point_idx, points.size());
|
||||
const ExtendedPoint &a = points[point_idx];
|
||||
const ExtendedPoint &b = points[get_prev_index(point_idx, points.size())];
|
||||
|
||||
int iter_limit = points.size();
|
||||
while ((a.position - points[prev].position).squaredNorm() < 1 && iter_limit > 0) {
|
||||
prev = prev_idx_modulo(prev, points.size());
|
||||
iter_limit--;
|
||||
}
|
||||
|
||||
while ((a.position - points[next].position).squaredNorm() < 1 && iter_limit > 0) {
|
||||
next = next_idx_modulo(next, points.size());
|
||||
iter_limit--;
|
||||
}
|
||||
|
||||
distances_for_curvature[point_idx] = (points[prev].position - a.position).norm();
|
||||
float alfa = angle(a.position - points[prev].position, points[next].position - a.position);
|
||||
angles_for_curvature[point_idx] = alfa;
|
||||
distances_for_curvature[point_idx] = (b.position - a.position).norm();
|
||||
accumulated_distance += distances_for_curvature[point_idx];
|
||||
}
|
||||
|
||||
if (std::accumulate(distances_for_curvature.begin(), distances_for_curvature.end(), 0) > EPSILON)
|
||||
if (accumulated_distance > EPSILON)
|
||||
for (float window_size : {3.0f, 9.0f, 16.0f}) {
|
||||
size_t tail_point = 0;
|
||||
float tail_window_acc = 0;
|
||||
float tail_angle_acc = 0;
|
||||
for (int point_idx = 0; point_idx < int(points.size()); ++point_idx) {
|
||||
ExtendedPoint ¤t = points[point_idx];
|
||||
|
||||
size_t head_point = 0;
|
||||
float head_window_acc = 0;
|
||||
float head_angle_acc = 0;
|
||||
Vec2d back_position = current.position;
|
||||
{
|
||||
size_t back_point_index = point_idx;
|
||||
float dist_backwards = 0;
|
||||
while (dist_backwards < window_size * 0.5 && back_point_index != get_prev_index(back_point_index, points.size())) {
|
||||
float line_dist = distances_for_curvature[get_prev_index(back_point_index, points.size())];
|
||||
if (dist_backwards + line_dist > window_size * 0.5) {
|
||||
back_position = points[back_point_index].position +
|
||||
(window_size * 0.5 - dist_backwards) *
|
||||
(points[get_prev_index(back_point_index, points.size())].position -
|
||||
points[back_point_index].position)
|
||||
.normalized();
|
||||
dist_backwards += window_size * 0.5 - dist_backwards + EPSILON;
|
||||
} else {
|
||||
dist_backwards += line_dist;
|
||||
back_point_index = get_prev_index(back_point_index, points.size());
|
||||
}
|
||||
|
||||
for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) {
|
||||
if (point_idx == 0) {
|
||||
while (tail_window_acc < window_size * 0.5) {
|
||||
tail_window_acc += distances_for_curvature[tail_point];
|
||||
tail_angle_acc += angles_for_curvature[tail_point];
|
||||
tail_point = prev_idx_modulo(tail_point, points.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vec2d front_position = current.position;
|
||||
{
|
||||
size_t front_point_index = point_idx;
|
||||
float dist_forwards = 0;
|
||||
while (dist_forwards < window_size * 0.5 && front_point_index != get_next_index(front_point_index, points.size())) {
|
||||
float line_dist = distances_for_curvature[front_point_index];
|
||||
if (dist_forwards + line_dist > window_size * 0.5) {
|
||||
front_position = points[front_point_index].position +
|
||||
(window_size * 0.5 - dist_forwards) *
|
||||
(points[get_next_index(front_point_index, points.size())].position -
|
||||
points[front_point_index].position)
|
||||
.normalized();
|
||||
dist_forwards += window_size * 0.5 - dist_forwards + EPSILON;
|
||||
} else {
|
||||
dist_forwards += line_dist;
|
||||
front_point_index = get_next_index(front_point_index, points.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size;
|
||||
if (abs(current.curvature) < abs(new_curvature)) {
|
||||
current.curvature = new_curvature;
|
||||
}
|
||||
}
|
||||
while (tail_window_acc - distances_for_curvature[next_idx_modulo(tail_point, points.size())] > window_size * 0.5) {
|
||||
tail_point = next_idx_modulo(tail_point, points.size());
|
||||
tail_window_acc -= distances_for_curvature[tail_point];
|
||||
tail_angle_acc -= angles_for_curvature[tail_point];
|
||||
}
|
||||
|
||||
while (head_window_acc < window_size * 0.5) {
|
||||
head_point = next_idx_modulo(head_point, points.size());
|
||||
head_window_acc += distances_for_curvature[head_point];
|
||||
head_angle_acc += angles_for_curvature[head_point];
|
||||
}
|
||||
|
||||
float curvature = (tail_angle_acc + head_angle_acc) / window_size;
|
||||
if (std::abs(curvature) > std::abs(points[point_idx].curvature)) {
|
||||
points[point_idx].curvature = curvature;
|
||||
}
|
||||
|
||||
tail_window_acc += distances_for_curvature[point_idx];
|
||||
tail_angle_acc += angles_for_curvature[point_idx];
|
||||
head_window_acc -= distances_for_curvature[point_idx];
|
||||
head_angle_acc -= angles_for_curvature[point_idx];
|
||||
}
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
struct ProcessedPoint
|
||||
{
|
||||
Point p;
|
||||
float speed = 1.0f;
|
||||
int fan_speed = 0;
|
||||
};
|
||||
|
||||
class ExtrusionQualityEstimator
|
||||
{
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> prev_layer_boundaries;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<Linef>> next_layer_boundaries;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> prev_curled_extrusions;
|
||||
std::unordered_map<const PrintObject *, AABBTreeLines::LinesDistancer<CurledLine>> next_curled_extrusions;
|
||||
const PrintObject *current_object;
|
||||
|
||||
public:
|
||||
void set_current_object(const PrintObject *object) { current_object = object; }
|
||||
|
||||
void prepare_for_new_layer(const Layer *layer)
|
||||
{
|
||||
if (layer == nullptr)
|
||||
return;
|
||||
const PrintObject *object = layer->object();
|
||||
prev_layer_boundaries[object] = next_layer_boundaries[object];
|
||||
next_layer_boundaries[object] = AABBTreeLines::LinesDistancer<Linef>{to_unscaled_linesf(layer->lslices)};
|
||||
prev_curled_extrusions[object] = next_curled_extrusions[object];
|
||||
next_curled_extrusions[object] = AABBTreeLines::LinesDistancer<CurledLine>{layer->curled_lines};
|
||||
}
|
||||
|
||||
std::vector<ProcessedPoint> estimate_speed_from_extrusion_quality(
|
||||
const ExtrusionPath &path,
|
||||
const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
|
||||
const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
|
||||
size_t extruder_id,
|
||||
float ext_perimeter_speed,
|
||||
float original_speed)
|
||||
{
|
||||
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
|
||||
std::map<float, float> speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
|
||||
float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
|
||||
overhangs_w_speeds[i].second.value;
|
||||
if (speed < EPSILON) speed = speed_base;
|
||||
speed_sections[distance] = speed;
|
||||
}
|
||||
|
||||
std::map<float, float> fan_speed_sections;
|
||||
for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
|
||||
float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
|
||||
float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
|
||||
fan_speed_sections[distance] = fan_speed;
|
||||
}
|
||||
|
||||
std::vector<ExtendedPoint> extended_points =
|
||||
estimate_points_properties<true, true, true, true>(path.polyline.points, prev_layer_boundaries[current_object], path.width);
|
||||
|
||||
std::vector<ProcessedPoint> processed_points;
|
||||
processed_points.reserve(extended_points.size());
|
||||
for (size_t i = 0; i < extended_points.size(); i++) {
|
||||
const ExtendedPoint &curr = extended_points[i];
|
||||
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
|
||||
|
||||
// The following code artifically increases the distance to provide slowdown for extrusions that are over curled lines
|
||||
float artificial_distance_to_curled_lines = 0.0;
|
||||
const double dist_limit = 10.0 * path.width;
|
||||
{
|
||||
Vec2d middle = 0.5 * (curr.position + next.position);
|
||||
auto line_indices = prev_curled_extrusions[current_object].all_lines_in_radius(Point::new_scale(middle), scale_(dist_limit));
|
||||
if (!line_indices.empty()) {
|
||||
double len = (next.position - curr.position).norm();
|
||||
// For long lines, there is a problem with the additional slowdown. If by accident, there is small curled line near the middle of this long line
|
||||
// The whole segment gets slower unnecesarily. For these long lines, we do additional check whether it is worth slowing down.
|
||||
// NOTE that this is still quite rough approximation, e.g. we are still checking lines only near the middle point
|
||||
// TODO maybe split the lines into smaller segments before running this alg? but can be demanding, and GCode will be huge
|
||||
if (len > 8) {
|
||||
Vec2d dir = Vec2d(next.position - curr.position) / len;
|
||||
Vec2d right = Vec2d(-dir.y(), dir.x());
|
||||
|
||||
Polygon box_of_influence = {
|
||||
scaled(Vec2d(curr.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position + right * dist_limit)),
|
||||
scaled(Vec2d(next.position - right * dist_limit)),
|
||||
scaled(Vec2d(curr.position - right * dist_limit)),
|
||||
};
|
||||
|
||||
double projected_lengths_sum = 0;
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
|
||||
Lines inside = intersection_ln({{line.a, line.b}}, {box_of_influence});
|
||||
if (inside.empty())
|
||||
continue;
|
||||
double projected_length = abs(dir.dot(unscaled(Vec2d((inside.back().b - inside.back().a).cast<double>()))));
|
||||
projected_lengths_sum += projected_length;
|
||||
}
|
||||
if (projected_lengths_sum < 0.4 * len) {
|
||||
line_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t idx : line_indices) {
|
||||
const CurledLine &line = prev_curled_extrusions[current_object].get_line(idx);
|
||||
float distance_from_curled = unscaled(line_alg::distance_to(line, Point::new_scale(middle)));
|
||||
float dist = path.width * (1.0 - (distance_from_curled / dist_limit)) *
|
||||
(1.0 - (distance_from_curled / dist_limit)) *
|
||||
(line.curled_height / (path.height * 10.0f)); // max_curled_height_factor from SupportSpotGenerator
|
||||
artificial_distance_to_curled_lines = std::max(artificial_distance_to_curled_lines, dist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
|
||||
auto upper_dist = values.lower_bound(distance);
|
||||
if (upper_dist == values.end()) {
|
||||
return values.rbegin()->second;
|
||||
}
|
||||
if (upper_dist == values.begin()) {
|
||||
return upper_dist->second;
|
||||
}
|
||||
|
||||
auto lower_dist = std::prev(upper_dist);
|
||||
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
|
||||
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
|
||||
};
|
||||
ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
|
||||
|
||||
float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
|
||||
interpolate_speed(speed_sections, next.distance));
|
||||
float curled_base_speed = interpolate_speed(speed_sections, artificial_distance_to_curled_lines);
|
||||
float final_speed = std::min(curled_base_speed, extrusion_speed);
|
||||
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
|
||||
interpolate_speed(fan_speed_sections, next.distance));
|
||||
ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(
|
||||
const ExtrusionEntityCollection *ecc,
|
||||
const AABBTreeLines::LinesDistancer<Linef> &unscaled_prev_layer,
|
||||
const AABBTreeLines::LinesDistancer<CurledLine> &prev_layer_curled_lines);
|
||||
|
||||
processed_points.push_back({scaled(curr.position), final_speed, int(fan_speed)});
|
||||
}
|
||||
return processed_points;
|
||||
}
|
||||
};
|
||||
std::pair<float, float> calculate_overhang_speed(const ExtrusionAttributes &attributes,
|
||||
const FullPrintConfig &config,
|
||||
size_t extruder_id,
|
||||
float external_perim_reference_speed,
|
||||
float default_speed);
|
||||
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::ExtrusionProcessor
|
||||
|
||||
#endif // slic3r_ExtrusionProcessor_hpp_
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/GCodeWriter.hpp"
|
||||
#include "libslic3r/GCode/GCodeWriter.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/Geometry/ArcWelder.hpp"
|
||||
#include "GCodeProcessor.hpp"
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
@@ -43,7 +44,6 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero();
|
||||
// taken from QIDITechnology.ini - [printer:Original QIDI i3 MK2.5 MMU2]
|
||||
static const std::vector<std::string> DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" };
|
||||
|
||||
static const std::string INTERNAL_G2G3_TAG = "!#!#! internal only - from G2/G3 expansion !#!#!";
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -65,6 +65,18 @@ const std::vector<std::string> GCodeProcessor::Reserved_Tags = {
|
||||
const float GCodeProcessor::Wipe_Width = 0.05f;
|
||||
const float GCodeProcessor::Wipe_Height = 0.05f;
|
||||
|
||||
bgcode::binarize::BinarizerConfig GCodeProcessor::s_binarizer_config{
|
||||
{
|
||||
bgcode::core::ECompressionType::None, // file metadata
|
||||
bgcode::core::ECompressionType::None, // printer metadata
|
||||
bgcode::core::ECompressionType::Deflate, // print metadata
|
||||
bgcode::core::ECompressionType::Deflate, // slicer metadata
|
||||
bgcode::core::ECompressionType::Heatshrink_12_4, // gcode
|
||||
},
|
||||
bgcode::core::EGCodeEncodingType::MeatPackComments,
|
||||
bgcode::core::EMetadataEncodingType::INI,
|
||||
bgcode::core::EChecksumType::CRC32
|
||||
};
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:";
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
@@ -328,7 +340,7 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, floa
|
||||
}
|
||||
}
|
||||
layers_time[block.layer_id - 1] += block_time;
|
||||
g1_times_cache.push_back({ block.g1_line_id, time });
|
||||
g1_times_cache.push_back({ block.g1_line_id, block.remaining_internal_g1_lines, time });
|
||||
// update times for remaining time to printer stop placeholders
|
||||
auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id,
|
||||
[](const StopTime& t, unsigned int value) { return t.g1_line_id < value; });
|
||||
@@ -441,6 +453,7 @@ void GCodeProcessorResult::reset() {
|
||||
moves = std::vector<GCodeProcessorResult::MoveVertex>();
|
||||
bed_shape = Pointfs();
|
||||
max_print_height = 0.0f;
|
||||
z_offset = 0.0f;
|
||||
settings_ids.reset();
|
||||
extruders_count = 0;
|
||||
backtrace_enabled = false;
|
||||
@@ -456,10 +469,12 @@ void GCodeProcessorResult::reset() {
|
||||
#else
|
||||
void GCodeProcessorResult::reset() {
|
||||
|
||||
is_binary_file = false;
|
||||
moves.clear();
|
||||
lines_ends.clear();
|
||||
bed_shape = Pointfs();
|
||||
max_print_height = 0.0f;
|
||||
z_offset = 0.0f;
|
||||
settings_ids.reset();
|
||||
extruders_count = 0;
|
||||
backtrace_enabled = false;
|
||||
@@ -553,6 +568,8 @@ GCodeProcessor::GCodeProcessor()
|
||||
void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
{
|
||||
m_parser.apply_config(config);
|
||||
m_binarizer.set_enabled(config.gcode_binary);
|
||||
m_result.is_binary_file = config.gcode_binary;
|
||||
|
||||
m_producer = EProducer::QIDISlicer;
|
||||
m_flavor = config.gcode_flavor;
|
||||
@@ -575,8 +592,12 @@ void GCodeProcessor::apply_config(const PrintConfig& config)
|
||||
for (size_t i = 0; i < extruders_count; ++ i) {
|
||||
m_extruder_offsets[i] = to_3d(config.extruder_offset.get_at(i).cast<float>().eval(), 0.f);
|
||||
m_extruder_colors[i] = static_cast<unsigned char>(i);
|
||||
m_extruder_temps_config[i] = static_cast<int>(config.temperature.get_at(i));
|
||||
m_extruder_temps_first_layer_config[i] = static_cast<int>(config.first_layer_temperature.get_at(i));
|
||||
m_extruder_temps_config[i] = static_cast<int>(config.temperature.get_at(i));
|
||||
if (m_extruder_temps_config[i] == 0) {
|
||||
// This means the value should be ignored and first layer temp should be used.
|
||||
m_extruder_temps_config[i] = m_extruder_temps_first_layer_config[i];
|
||||
}
|
||||
m_result.filament_diameters[i] = static_cast<float>(config.filament_diameter.get_at(i));
|
||||
m_result.filament_densities[i] = static_cast<float>(config.filament_density.get_at(i));
|
||||
m_result.filament_cost[i] = static_cast<float>(config.filament_cost.get_at(i));
|
||||
@@ -1023,6 +1044,23 @@ static inline const char* remove_eols(const char *begin, const char *end) {
|
||||
// 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 GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
FILE* file = boost::nowide::fopen(filename.c_str(), "rb");
|
||||
if (file == nullptr)
|
||||
throw Slic3r::RuntimeError(format("Error opening file %1%", filename));
|
||||
|
||||
using namespace bgcode::core;
|
||||
std::vector<std::byte> cs_buffer(65536);
|
||||
const bool is_binary = is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == EResult::Success;
|
||||
fclose(file);
|
||||
|
||||
if (is_binary)
|
||||
process_binary_file(filename, cancel_callback);
|
||||
else
|
||||
process_ascii_file(filename, cancel_callback);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_ascii_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
@@ -1073,6 +1111,7 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
||||
|
||||
// process gcode
|
||||
m_result.filename = filename;
|
||||
m_result.is_binary_file = false;
|
||||
m_result.id = ++s_result_id;
|
||||
initialize_result_moves();
|
||||
size_t parse_line_callback_cntr = 10000;
|
||||
@@ -1090,6 +1129,155 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
||||
this->finalize(false);
|
||||
}
|
||||
|
||||
static void update_lines_ends_and_out_file_pos(const std::string& out_string, std::vector<size_t>& lines_ends, size_t* out_file_pos)
|
||||
{
|
||||
for (size_t i = 0; i < out_string.size(); ++i) {
|
||||
if (out_string[i] == '\n')
|
||||
lines_ends.emplace_back((out_file_pos != nullptr) ? *out_file_pos + i + 1 : i + 1);
|
||||
}
|
||||
if (out_file_pos != nullptr)
|
||||
*out_file_pos += out_string.size();
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_binary_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
m_start_time = std::chrono::high_resolution_clock::now();
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
FilePtr file{ boost::nowide::fopen(filename.c_str(), "rb") };
|
||||
if (file.f == nullptr)
|
||||
throw Slic3r::RuntimeError(format("Error opening file %1%", filename));
|
||||
|
||||
fseek(file.f, 0, SEEK_END);
|
||||
const long file_size = ftell(file.f);
|
||||
rewind(file.f);
|
||||
|
||||
// read file header
|
||||
using namespace bgcode::core;
|
||||
using namespace bgcode::binarize;
|
||||
FileHeader file_header;
|
||||
EResult res = read_header(*file.f, file_header, nullptr);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("File %1% does not contain a valid binary gcode\nError: %2%", filename,
|
||||
std::string(translate_result(res))));
|
||||
|
||||
// read file metadata block, if present
|
||||
BlockHeader block_header;
|
||||
std::vector<std::byte> cs_buffer(65536);
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
if ((EBlockType)block_header.type != EBlockType::FileMetadata &&
|
||||
(EBlockType)block_header.type != EBlockType::PrinterMetadata)
|
||||
throw Slic3r::RuntimeError(format("Unable to find file metadata block in file %1%", filename));
|
||||
if ((EBlockType)block_header.type == EBlockType::FileMetadata) {
|
||||
FileMetadataBlock file_metadata_block;
|
||||
res = file_metadata_block.read_data(*file.f, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
auto producer_it = std::find_if(file_metadata_block.raw_data.begin(), file_metadata_block.raw_data.end(),
|
||||
[](const std::pair<std::string, std::string>& item) { return item.first == "Producer"; });
|
||||
if (producer_it != file_metadata_block.raw_data.end() && boost::starts_with(producer_it->second, std::string(SLIC3R_APP_NAME)))
|
||||
m_producer = EProducer::QIDISlicer;
|
||||
else
|
||||
m_producer = EProducer::Unknown;
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
}
|
||||
else {
|
||||
m_producer = EProducer::Unknown;
|
||||
}
|
||||
|
||||
// read printer metadata block
|
||||
if ((EBlockType)block_header.type != EBlockType::PrinterMetadata)
|
||||
throw Slic3r::RuntimeError(format("Unable to find printer metadata block in file %1%", filename));
|
||||
PrinterMetadataBlock printer_metadata_block;
|
||||
res = printer_metadata_block.read_data(*file.f, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
|
||||
// read thumbnail blocks
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
|
||||
while ((EBlockType)block_header.type == EBlockType::Thumbnail) {
|
||||
ThumbnailBlock thumbnail_block;
|
||||
res = thumbnail_block.read_data(*file.f, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
}
|
||||
|
||||
// read print metadata block
|
||||
if ((EBlockType)block_header.type != EBlockType::PrintMetadata)
|
||||
throw Slic3r::RuntimeError(format("Unable to find print metadata block in file %1%", filename));
|
||||
PrintMetadataBlock print_metadata_block;
|
||||
res = print_metadata_block.read_data(*file.f, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
|
||||
// read slicer metadata block
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
if ((EBlockType)block_header.type != EBlockType::SlicerMetadata)
|
||||
throw Slic3r::RuntimeError(format("Unable to find slicer metadata block in file %1%", filename));
|
||||
SlicerMetadataBlock slicer_metadata_block;
|
||||
res = slicer_metadata_block.read_data(*file.f, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
DynamicPrintConfig config;
|
||||
config.apply(FullPrintConfig::defaults());
|
||||
std::string str;
|
||||
for (const auto& [key, value] : slicer_metadata_block.raw_data) {
|
||||
str += key + " = " + value + "\n";
|
||||
}
|
||||
// Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code.
|
||||
// Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config,
|
||||
// thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways.
|
||||
config.load_from_ini_string(str, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
apply_config(config);
|
||||
|
||||
m_result.filename = filename;
|
||||
m_result.is_binary_file = true;
|
||||
m_result.id = ++s_result_id;
|
||||
initialize_result_moves();
|
||||
|
||||
// read gcodes block
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
if ((EBlockType)block_header.type != EBlockType::GCode)
|
||||
throw Slic3r::RuntimeError(format("Unable to find gcode block in file %1%", filename));
|
||||
while ((EBlockType)block_header.type == EBlockType::GCode) {
|
||||
GCodeBlock block;
|
||||
res = block.read_data(*file.f, file_header, block_header);
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
|
||||
std::vector<size_t>& lines_ends = m_result.lines_ends.emplace_back(std::vector<size_t>());
|
||||
update_lines_ends_and_out_file_pos(block.raw_data, lines_ends, nullptr);
|
||||
|
||||
m_parser.parse_buffer(block.raw_data, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
this->process_gcode_line(line, true);
|
||||
});
|
||||
|
||||
if (ftell(file.f) == file_size)
|
||||
break;
|
||||
|
||||
res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size());
|
||||
if (res != EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res))));
|
||||
}
|
||||
|
||||
// Don't post-process the G-code to update time stamps.
|
||||
this->finalize(false);
|
||||
}
|
||||
void GCodeProcessor::initialize(const std::string& filename)
|
||||
{
|
||||
assert(is_decimal_separator_point());
|
||||
@@ -1113,6 +1301,7 @@ void GCodeProcessor::process_buffer(const std::string &buffer)
|
||||
|
||||
void GCodeProcessor::finalize(bool perform_post_process)
|
||||
{
|
||||
m_result.z_offset = m_z_offset;
|
||||
// update width/height of wipe moves
|
||||
for (GCodeProcessorResult::MoveVertex& move : m_result.moves) {
|
||||
if (move.type == EMoveType::Wipe) {
|
||||
@@ -1132,7 +1321,7 @@ void GCodeProcessor::finalize(bool perform_post_process)
|
||||
|
||||
m_used_filaments.process_caches(this);
|
||||
|
||||
update_estimated_times_stats();
|
||||
update_estimated_statistics();
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
std::cout << "\n";
|
||||
@@ -2363,13 +2552,11 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line)
|
||||
if (line.has_e()) g1_axes[E] = (double)line.e();
|
||||
std::optional<double> g1_feedrate = std::nullopt;
|
||||
if (line.has_f()) g1_feedrate = (double)line.f();
|
||||
std::optional<std::string> g1_cmt = std::nullopt;
|
||||
if (!line.comment().empty()) g1_cmt = line.comment();
|
||||
|
||||
process_G1(g1_axes, g1_feedrate, g1_cmt);
|
||||
process_G1(g1_axes, g1_feedrate);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, std::optional<double> feedrate, std::optional<std::string> cmt)
|
||||
void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes, const std::optional<double>& feedrate,
|
||||
G1DiscretizationOrigin origin, const std::optional<unsigned int>& remaining_internal_g1_lines)
|
||||
{
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
@@ -2449,10 +2636,17 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
if (m_forced_height > 0.0f)
|
||||
// use height coming from the gcode tags
|
||||
m_height = m_forced_height;
|
||||
else if (m_layer_id == 0)
|
||||
else if (m_layer_id == 0) { // first layer
|
||||
if (m_end_position[Z] > 0.0f)
|
||||
// use the current (clamped) z, if greater than zero
|
||||
m_height = std::min<float>(m_end_position[Z], 2.0f);
|
||||
else
|
||||
// use the first layer height
|
||||
m_height = m_first_layer_height + m_z_offset;
|
||||
else if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG) {
|
||||
}
|
||||
else if (origin == G1DiscretizationOrigin::G1) {
|
||||
if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0)
|
||||
m_height = m_end_position[Z] - m_extruded_last_z;
|
||||
}
|
||||
@@ -2460,10 +2654,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
if (m_height == 0.0f)
|
||||
m_height = DEFAULT_TOOLPATH_HEIGHT;
|
||||
|
||||
if (m_end_position[Z] == 0.0f || (m_extrusion_role == GCodeExtrusionRole::Custom && m_layer_id == 0))
|
||||
m_end_position[Z] = m_height;
|
||||
|
||||
if (!cmt.has_value() || *cmt != INTERNAL_G2G3_TAG)
|
||||
if (origin == G1DiscretizationOrigin::G1)
|
||||
m_extruded_last_z = m_end_position[Z];
|
||||
m_options_z_corrector.update(m_height);
|
||||
|
||||
@@ -2472,6 +2663,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
if (m_forced_width > 0.0f)
|
||||
// use width coming from the gcode tags
|
||||
m_width = m_forced_width;
|
||||
else if (m_extrusion_role == GCodeExtrusionRole::ExternalPerimeter)
|
||||
// cross section: rectangle
|
||||
@@ -2525,6 +2717,7 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
block.role = m_extrusion_role;
|
||||
block.distance = distance;
|
||||
block.g1_line_id = m_g1_line_id;
|
||||
block.remaining_internal_g1_lines = remaining_internal_g1_lines.has_value() ? *remaining_internal_g1_lines : 0;
|
||||
block.layer_id = std::max<unsigned int>(1, m_layer_id);
|
||||
|
||||
// calculates block cruise feedrate
|
||||
@@ -2693,18 +2886,60 @@ void GCodeProcessor::process_G1(const std::array<std::optional<double>, 4>& axes
|
||||
}
|
||||
|
||||
// store move
|
||||
store_move_vertex(type, cmt.has_value() && *cmt == INTERNAL_G2G3_TAG);
|
||||
store_move_vertex(type, origin == G1DiscretizationOrigin::G2G3);
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise)
|
||||
{
|
||||
if (!line.has('I') || !line.has('J'))
|
||||
enum class EFitting { None, IJ, R };
|
||||
std::string_view axis_pos_I;
|
||||
std::string_view axis_pos_J;
|
||||
EFitting fitting = EFitting::None;
|
||||
if (line.has('R')) {
|
||||
fitting = EFitting::R;
|
||||
} else {
|
||||
axis_pos_I = line.axis_pos('I');
|
||||
axis_pos_J = line.axis_pos('J');
|
||||
if (! axis_pos_I.empty() || ! axis_pos_J.empty())
|
||||
fitting = EFitting::IJ;
|
||||
}
|
||||
|
||||
if (fitting == EFitting::None)
|
||||
return;
|
||||
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
|
||||
AxisCoords end_position = m_start_position;
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
|
||||
}
|
||||
// relative center
|
||||
Vec3f rel_center = Vec3f::Zero();
|
||||
if (!line.has_value('I', rel_center.x()) || !line.has_value('J', rel_center.y()))
|
||||
#ifndef NDEBUG
|
||||
double radius = 0.0;
|
||||
#endif // NDEBUG
|
||||
if (fitting == EFitting::R) {
|
||||
float r;
|
||||
if (!line.has_value('R', r) || r == 0.0f)
|
||||
return;
|
||||
#ifndef NDEBUG
|
||||
radius = (double)std::abs(r);
|
||||
#endif // NDEBUG
|
||||
const Vec2f start_pos((float)m_start_position[X], (float)m_start_position[Y]);
|
||||
const Vec2f end_pos((float)end_position[X], (float)end_position[Y]);
|
||||
const Vec2f c = Geometry::ArcWelder::arc_center(start_pos, end_pos, r, !clockwise);
|
||||
rel_center.x() = c.x() - m_start_position[X];
|
||||
rel_center.y() = c.y() - m_start_position[Y];
|
||||
}
|
||||
else {
|
||||
assert(fitting == EFitting::IJ);
|
||||
if (! axis_pos_I.empty() && ! line.has_value(axis_pos_I, rel_center.x()))
|
||||
return;
|
||||
if (! axis_pos_J.empty() && ! line.has_value(axis_pos_J, rel_center.y()))
|
||||
return;
|
||||
}
|
||||
|
||||
// scale center, if needed
|
||||
if (m_units == EUnits::Inches)
|
||||
@@ -2740,14 +2975,6 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// arc center
|
||||
arc.center = arc.start + rel_center.cast<double>();
|
||||
|
||||
const float filament_diameter = (static_cast<size_t>(m_extruder_id) < m_result.filament_diameters.size()) ? m_result.filament_diameters[m_extruder_id] : m_result.filament_diameters.back();
|
||||
const float filament_radius = 0.5f * filament_diameter;
|
||||
const float area_filament_cross_section = static_cast<float>(M_PI) * sqr(filament_radius);
|
||||
|
||||
AxisCoords end_position = m_start_position;
|
||||
for (unsigned char a = X; a <= E; ++a) {
|
||||
end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section));
|
||||
}
|
||||
|
||||
// arc end endpoint
|
||||
arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]);
|
||||
@@ -2757,6 +2984,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// what to do ???
|
||||
}
|
||||
|
||||
assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON);
|
||||
// updates feedrate from line
|
||||
std::optional<float> feedrate;
|
||||
if (line.has_f())
|
||||
@@ -2807,18 +3035,17 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
return ret;
|
||||
};
|
||||
|
||||
auto internal_only_g1_line = [this](const AxisCoords& target, bool has_z, const std::optional<float>& feedrate, const std::optional<float>& extrusion) {
|
||||
auto internal_only_g1_line = [this](const AxisCoords& target, bool has_z, const std::optional<float>& feedrate,
|
||||
const std::optional<float>& extrusion, const std::optional<unsigned int>& remaining_internal_g1_lines = std::nullopt) {
|
||||
std::array<std::optional<double>, 4> g1_axes = { target[X], target[Y], std::nullopt, std::nullopt };
|
||||
std::optional<double> g1_feedrate = std::nullopt;
|
||||
if (has_z)
|
||||
g1_axes[Z] = target[Z];
|
||||
if (feedrate.has_value())
|
||||
g1_feedrate = (double)*feedrate;
|
||||
if (extrusion.has_value())
|
||||
g1_axes[E] = target[E];
|
||||
std::optional<std::string> g1_cmt = INTERNAL_G2G3_TAG;
|
||||
|
||||
process_G1(g1_axes, g1_feedrate, g1_cmt);
|
||||
if (feedrate.has_value())
|
||||
g1_feedrate = (double)*feedrate;
|
||||
process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3, remaining_internal_g1_lines);
|
||||
};
|
||||
|
||||
// calculate arc segments
|
||||
@@ -2827,8 +3054,13 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
// https://github.com/qidi3d/QIDI-Firmware/blob/MK3/Firmware/motion_control.cpp
|
||||
|
||||
// segments count
|
||||
#if 0
|
||||
static const double MM_PER_ARC_SEGMENT = 1.0;
|
||||
const size_t segments = std::max<size_t>(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1);
|
||||
#else
|
||||
static const double gcode_arc_tolerance = 0.0125;
|
||||
const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance);
|
||||
#endif
|
||||
|
||||
const double inv_segment = 1.0 / double(segments);
|
||||
const double theta_per_segment = arc.angle * inv_segment;
|
||||
@@ -2876,7 +3108,8 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc
|
||||
arc_target[E] += extruder_per_segment;
|
||||
|
||||
m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line()
|
||||
internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, (i == 1) ? feedrate : std::nullopt, extrusion);
|
||||
internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, (i == 1) ? feedrate : std::nullopt,
|
||||
extrusion, segments - i);
|
||||
prev_target = arc_target;
|
||||
}
|
||||
|
||||
@@ -3435,8 +3668,67 @@ void GCodeProcessor::post_process()
|
||||
// temporary file to contain modified gcode
|
||||
std::string out_path = m_result.filename + ".postprocess";
|
||||
FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") };
|
||||
if (out.f == nullptr) {
|
||||
if (out.f == nullptr)
|
||||
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for writing.\n"));
|
||||
std::vector<double> filament_mm(m_result.extruders_count, 0.0);
|
||||
std::vector<double> filament_cm3(m_result.extruders_count, 0.0);
|
||||
std::vector<double> filament_g(m_result.extruders_count, 0.0);
|
||||
std::vector<double> filament_cost(m_result.extruders_count, 0.0);
|
||||
|
||||
double filament_total_g = 0.0;
|
||||
double filament_total_cost = 0.0;
|
||||
|
||||
for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) {
|
||||
filament_mm[id] = volume / (static_cast<double>(M_PI) * sqr(0.5 * m_result.filament_diameters[id]));
|
||||
filament_cm3[id] = volume * 0.001;
|
||||
filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]);
|
||||
filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001;
|
||||
filament_total_g += filament_g[id];
|
||||
filament_total_cost += filament_cost[id];
|
||||
}
|
||||
|
||||
if (m_binarizer.is_enabled()) {
|
||||
// update print metadata
|
||||
auto stringify = [](const std::vector<double>& values) {
|
||||
std::string ret;
|
||||
char buf[1024];
|
||||
for (size_t i = 0; i < values.size(); ++i) {
|
||||
sprintf(buf, i < values.size() - 1 ? "%.2lf, " : "%.2lf", values[i]);
|
||||
ret += buf;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// update binary data
|
||||
bgcode::binarize::BinaryData& binary_data = m_binarizer.get_binary_data();
|
||||
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm));
|
||||
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3));
|
||||
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g));
|
||||
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost));
|
||||
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentUsedG, stringify({ filament_total_g }));
|
||||
binary_data.print_metadata.raw_data.emplace_back(PrintStatistics::TotalFilamentCost, stringify({ filament_total_cost }));
|
||||
|
||||
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedMm, stringify(filament_mm)); // duplicated into print metadata
|
||||
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedG, stringify(filament_g)); // duplicated into print metadata
|
||||
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentCost, stringify(filament_cost)); // duplicated into print metadata
|
||||
binary_data.printer_metadata.raw_data.emplace_back(PrintStatistics::FilamentUsedCm3, stringify(filament_cm3)); // duplicated into print metadata
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count); ++i) {
|
||||
const TimeMachine& machine = m_time_processor.machines[i];
|
||||
PrintEstimatedStatistics::ETimeMode mode = static_cast<PrintEstimatedStatistics::ETimeMode>(i);
|
||||
if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) {
|
||||
char buf[128];
|
||||
sprintf(buf, "(%s mode)", (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent");
|
||||
binary_data.print_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time));
|
||||
binary_data.print_metadata.raw_data.emplace_back("estimated first layer printing time " + std::string(buf), get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front()));
|
||||
|
||||
binary_data.printer_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time));
|
||||
}
|
||||
}
|
||||
|
||||
const bgcode::core::EResult res = m_binarizer.initialize(*out.f, s_binarizer_config);
|
||||
if (res != bgcode::core::EResult::Success)
|
||||
throw Slic3r::RuntimeError(format("Unable to initialize the gcode binarizer.\nError: %1%", bgcode::core::translate_result(res)));
|
||||
}
|
||||
|
||||
auto time_in_minutes = [](float time_in_seconds) {
|
||||
@@ -3552,32 +3844,55 @@ void GCodeProcessor::post_process()
|
||||
// used to update m_result.moves[].gcode_id
|
||||
std::vector<std::pair<size_t, size_t>> m_gcode_lines_map;
|
||||
|
||||
size_t m_curr_g1_id{ 0 };
|
||||
size_t m_times_cache_id{ 0 };
|
||||
size_t m_out_file_pos{ 0 };
|
||||
|
||||
bgcode::binarize::Binarizer& m_binarizer;
|
||||
public:
|
||||
ExportLines(EWriteType type, TimeMachine& machine)
|
||||
ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, TimeMachine& machine)
|
||||
#ifndef NDEBUG
|
||||
: m_statistics(*this), m_write_type(type), m_machine(machine) {}
|
||||
: m_statistics(*this), m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
#else
|
||||
: m_write_type(type), m_machine(machine) {}
|
||||
: m_binarizer(binarizer), m_write_type(type), m_machine(machine) {}
|
||||
#endif // NDEBUG
|
||||
|
||||
void update(size_t lines_counter, size_t g1_lines_counter) {
|
||||
// return: number of internal G1 lines (from G2/G3 splitting) processed
|
||||
unsigned int update(const std::string& line, size_t lines_counter, size_t g1_lines_counter) {
|
||||
unsigned int ret = 0;
|
||||
m_gcode_lines_map.push_back({ lines_counter, 0 });
|
||||
|
||||
if (g1_lines_counter == 0)
|
||||
return;
|
||||
if (GCodeReader::GCodeLine::cmd_is(line, "G0") ||
|
||||
GCodeReader::GCodeLine::cmd_is(line, "G1") ||
|
||||
GCodeReader::GCodeLine::cmd_is(line, "G2") ||
|
||||
GCodeReader::GCodeLine::cmd_is(line, "G3") ||
|
||||
GCodeReader::GCodeLine::cmd_is(line, "G28"))
|
||||
++g1_lines_counter;
|
||||
else
|
||||
return ret;
|
||||
|
||||
auto init_it = m_machine.g1_times_cache.begin() + m_curr_g1_id;
|
||||
auto init_it = m_machine.g1_times_cache.begin() + m_times_cache_id;
|
||||
auto it = init_it;
|
||||
while (it != m_machine.g1_times_cache.end() && it->id < g1_lines_counter + 1) {
|
||||
while (it != m_machine.g1_times_cache.end() && it->id < g1_lines_counter) {
|
||||
++it;
|
||||
++m_curr_g1_id;
|
||||
++m_times_cache_id;
|
||||
}
|
||||
|
||||
if ((it != m_machine.g1_times_cache.end() && it != init_it) || m_curr_g1_id == 0)
|
||||
if (it->id > g1_lines_counter)
|
||||
return ret;
|
||||
|
||||
// search for internal G1 lines
|
||||
if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) {
|
||||
while (it != m_machine.g1_times_cache.end() && it->remaining_internal_g1_lines > 0) {
|
||||
++it;
|
||||
++m_times_cache_id;
|
||||
++g1_lines_counter;
|
||||
++ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (it != m_machine.g1_times_cache.end() && it->id == g1_lines_counter)
|
||||
m_time = it->elapsed_time;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// add the given gcode line to the cache
|
||||
@@ -3673,7 +3988,14 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
}
|
||||
|
||||
if (m_binarizer.is_enabled()) {
|
||||
if (m_binarizer.append_gcode(out_string) != bgcode::core::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while sending gcode to the binarizer.");
|
||||
}
|
||||
else {
|
||||
write_to_file(out, out_string, result, out_path);
|
||||
update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// flush the current content of the cache to file
|
||||
@@ -3689,7 +4011,14 @@ void GCodeProcessor::post_process()
|
||||
m_statistics.remove_all_lines();
|
||||
#endif // NDEBUG
|
||||
|
||||
if (m_binarizer.is_enabled()) {
|
||||
if (m_binarizer.append_gcode(out_string) != bgcode::core::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while sending gcode to the binarizer.");
|
||||
}
|
||||
else {
|
||||
write_to_file(out, out_string, result, out_path);
|
||||
update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos);
|
||||
}
|
||||
}
|
||||
|
||||
void synchronize_moves(GCodeProcessorResult& result) const {
|
||||
@@ -3708,22 +4037,19 @@ void GCodeProcessor::post_process()
|
||||
private:
|
||||
void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path) {
|
||||
if (!out_string.empty()) {
|
||||
if (!m_binarizer.is_enabled()) {
|
||||
fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f);
|
||||
if (ferror(out.f)) {
|
||||
out.close();
|
||||
boost::nowide::remove(out_path.c_str());
|
||||
throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nIs the disk full?\n"));
|
||||
throw Slic3r::RuntimeError("GCode processor post process export failed.\nIs the disk full?");
|
||||
}
|
||||
for (size_t i = 0; i < out_string.size(); ++i) {
|
||||
if (out_string[i] == '\n')
|
||||
result.lines_ends.emplace_back(m_out_file_pos + i + 1);
|
||||
}
|
||||
m_out_file_pos += out_string.size();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ExportLines export_lines(m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
ExportLines export_lines(m_binarizer, m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, m_time_processor.machines[0]);
|
||||
|
||||
// replace placeholder lines with the proper final value
|
||||
// gcode_line is in/out parameter, to reduce expensive memory allocation
|
||||
@@ -3786,22 +4112,6 @@ void GCodeProcessor::post_process()
|
||||
return processed;
|
||||
};
|
||||
|
||||
std::vector<double> filament_mm(m_result.extruders_count, 0.0);
|
||||
std::vector<double> filament_cm3(m_result.extruders_count, 0.0);
|
||||
std::vector<double> filament_g(m_result.extruders_count, 0.0);
|
||||
std::vector<double> filament_cost(m_result.extruders_count, 0.0);
|
||||
|
||||
double filament_total_g = 0.0;
|
||||
double filament_total_cost = 0.0;
|
||||
|
||||
for (const auto& [id, volume] : m_result.print_statistics.volumes_per_extruder) {
|
||||
filament_mm[id] = volume / (static_cast<double>(M_PI) * sqr(0.5 * m_result.filament_diameters[id]));
|
||||
filament_cm3[id] = volume * 0.001;
|
||||
filament_g[id] = filament_cm3[id] * double(m_result.filament_densities[id]);
|
||||
filament_cost[id] = filament_g[id] * double(m_result.filament_cost[id]) * 0.001;
|
||||
filament_total_g += filament_g[id];
|
||||
filament_total_cost += filament_cost[id];
|
||||
}
|
||||
|
||||
auto process_used_filament = [&](std::string& gcode_line) {
|
||||
// Prefilter for parsing speed.
|
||||
@@ -3823,12 +4133,12 @@ void GCodeProcessor::post_process()
|
||||
};
|
||||
|
||||
bool ret = false;
|
||||
ret |= process_tag(gcode_line, "; filament used [mm] =", filament_mm);
|
||||
ret |= process_tag(gcode_line, "; filament used [g] =", filament_g);
|
||||
ret |= process_tag(gcode_line, "; total filament used [g] =", { filament_total_g });
|
||||
ret |= process_tag(gcode_line, "; filament used [cm3] =", filament_cm3);
|
||||
ret |= process_tag(gcode_line, "; filament cost =", filament_cost);
|
||||
ret |= process_tag(gcode_line, "; total filament cost =", { filament_total_cost });
|
||||
ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedMmMask, filament_mm);
|
||||
ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedGMask, filament_g);
|
||||
ret |= process_tag(gcode_line, PrintStatistics::TotalFilamentUsedGMask, { filament_total_g });
|
||||
ret |= process_tag(gcode_line, PrintStatistics::FilamentUsedCm3Mask, filament_cm3);
|
||||
ret |= process_tag(gcode_line, PrintStatistics::FilamentCostMask, filament_cost);
|
||||
ret |= process_tag(gcode_line, PrintStatistics::TotalFilamentCostMask, { filament_total_cost });
|
||||
return ret;
|
||||
};
|
||||
|
||||
@@ -3920,7 +4230,7 @@ void GCodeProcessor::post_process()
|
||||
}
|
||||
};
|
||||
|
||||
// add lines XXX to exported gcode
|
||||
// add lines M104 to exported gcode
|
||||
auto process_line_T = [this, &export_lines](const std::string& gcode_line, const size_t g1_lines_counter, const ExportLines::Backtrace& backtrace) {
|
||||
const std::string cmd = GCodeReader::GCodeLine::extract_cmd(gcode_line);
|
||||
if (cmd.size() >= 2) {
|
||||
@@ -3967,6 +4277,7 @@ void GCodeProcessor::post_process()
|
||||
};
|
||||
|
||||
m_result.lines_ends.clear();
|
||||
m_result.lines_ends.emplace_back(std::vector<size_t>());
|
||||
|
||||
unsigned int line_id = 0;
|
||||
// Backtrace data for Tx gcode lines
|
||||
@@ -3997,9 +4308,9 @@ void GCodeProcessor::post_process()
|
||||
gcode_line.insert(gcode_line.end(), it, it_end);
|
||||
if (eol) {
|
||||
++line_id;
|
||||
export_lines.update(line_id, g1_lines_counter);
|
||||
|
||||
gcode_line += "\n";
|
||||
const unsigned int internal_g1_lines_counter = export_lines.update(gcode_line, line_id, g1_lines_counter);
|
||||
// replace placeholder lines
|
||||
bool processed = process_placeholders(gcode_line);
|
||||
if (processed)
|
||||
@@ -4007,11 +4318,24 @@ void GCodeProcessor::post_process()
|
||||
if (!processed)
|
||||
processed = process_used_filament(gcode_line);
|
||||
if (!processed && !is_temporary_decoration(gcode_line)) {
|
||||
if (GCodeReader::GCodeLine::cmd_is(gcode_line, "G1"))
|
||||
if (GCodeReader::GCodeLine::cmd_is(gcode_line, "G0") || GCodeReader::GCodeLine::cmd_is(gcode_line, "G1")) {
|
||||
export_lines.append_line(gcode_line);
|
||||
// add lines M73 where needed
|
||||
process_line_G1(g1_lines_counter++);
|
||||
gcode_line.clear();
|
||||
}
|
||||
else if (GCodeReader::GCodeLine::cmd_is(gcode_line, "G2") || GCodeReader::GCodeLine::cmd_is(gcode_line, "G3")) {
|
||||
export_lines.append_line(gcode_line);
|
||||
// add lines M73 where needed
|
||||
process_line_G1(g1_lines_counter + internal_g1_lines_counter);
|
||||
g1_lines_counter += (1 + internal_g1_lines_counter);
|
||||
gcode_line.clear();
|
||||
}
|
||||
else if (GCodeReader::GCodeLine::cmd_is(gcode_line, "G28")) {
|
||||
++g1_lines_counter;
|
||||
}
|
||||
else if (m_result.backtrace_enabled && GCodeReader::GCodeLine::cmd_starts_with(gcode_line, "T")) {
|
||||
// add lines XXX where needed
|
||||
// add lines M104 where needed
|
||||
process_line_T(gcode_line, g1_lines_counter, backtrace_T);
|
||||
max_backtrace_time = std::max(max_backtrace_time, backtrace_T.time);
|
||||
}
|
||||
@@ -4036,13 +4360,29 @@ void GCodeProcessor::post_process()
|
||||
|
||||
export_lines.flush(out, m_result, out_path);
|
||||
|
||||
if (m_binarizer.is_enabled()) {
|
||||
if (m_binarizer.finalize() != bgcode::core::EResult::Success)
|
||||
throw Slic3r::RuntimeError("Error while finalizing the gcode binarizer.");
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
|
||||
const std::string result_filename = m_result.filename;
|
||||
if (m_binarizer.is_enabled()) {
|
||||
// The list of lines in the binary gcode is different from the original one.
|
||||
// This requires to re-process the binarized file to be able to synchronize with it all the data needed by the preview,
|
||||
// as gcode window, tool position and moves slider which relies on indexing the gcode lines.
|
||||
reset();
|
||||
// the following call modifies m_result.filename
|
||||
process_binary_file(out_path);
|
||||
// restore the proper filename
|
||||
m_result.filename = result_filename;
|
||||
}
|
||||
else
|
||||
export_lines.synchronize_moves(m_result);
|
||||
|
||||
if (rename_file(out_path, m_result.filename))
|
||||
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + m_result.filename + '\n' +
|
||||
if (rename_file(out_path, result_filename))
|
||||
throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + result_filename + '\n' +
|
||||
"Is " + out_path + " locked?" + '\n');
|
||||
}
|
||||
|
||||
@@ -4243,7 +4583,7 @@ void GCodeProcessor::simulate_st_synchronize(float additional_time)
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeProcessor::update_estimated_times_stats()
|
||||
void GCodeProcessor::update_estimated_statistics()
|
||||
{
|
||||
auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) {
|
||||
PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast<size_t>(mode)];
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
|
||||
#include <LibBGCode/binarize/binarize.hpp>
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
@@ -56,9 +57,13 @@ namespace Slic3r {
|
||||
time = 0.0f;
|
||||
travel_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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,6 +81,7 @@ namespace Slic3r {
|
||||
m.reset();
|
||||
}
|
||||
volumes_per_color_change.clear();
|
||||
volumes_per_color_change.shrink_to_fit();
|
||||
volumes_per_extruder.clear();
|
||||
used_filaments_per_role.clear();
|
||||
cost_per_extruder.clear();
|
||||
@@ -135,12 +141,16 @@ namespace Slic3r {
|
||||
};
|
||||
|
||||
std::string filename;
|
||||
bool is_binary_file;
|
||||
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;
|
||||
// Binarized gcodes usually have several gcode blocks. Each block has its own list on ends of lines.
|
||||
// Ascii gcodes have only one list on ends of lines
|
||||
std::vector<std::vector<size_t>> lines_ends;
|
||||
Pointfs bed_shape;
|
||||
float max_print_height;
|
||||
float z_offset;
|
||||
SettingsIds settings_ids;
|
||||
size_t extruders_count;
|
||||
bool backtrace_enabled;
|
||||
@@ -261,6 +271,7 @@ namespace Slic3r {
|
||||
EMoveType move_type{ EMoveType::Noop };
|
||||
GCodeExtrusionRole role{ GCodeExtrusionRole::None };
|
||||
unsigned int g1_line_id{ 0 };
|
||||
unsigned int remaining_internal_g1_lines;
|
||||
unsigned int layer_id{ 0 };
|
||||
float distance{ 0.0f }; // mm
|
||||
float acceleration{ 0.0f }; // mm/s^2
|
||||
@@ -301,6 +312,7 @@ namespace Slic3r {
|
||||
struct G1LinesCacheItem
|
||||
{
|
||||
unsigned int id;
|
||||
unsigned int remaining_internal_g1_lines;
|
||||
float elapsed_time;
|
||||
};
|
||||
|
||||
@@ -523,8 +535,11 @@ namespace Slic3r {
|
||||
};
|
||||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
|
||||
static bgcode::binarize::BinarizerConfig& get_binarizer_config() { return s_binarizer_config; }
|
||||
private:
|
||||
GCodeReader m_parser;
|
||||
bgcode::binarize::Binarizer m_binarizer;
|
||||
static bgcode::binarize::BinarizerConfig s_binarizer_config;
|
||||
|
||||
EUnits m_units;
|
||||
EPositioningType m_global_positioning_type;
|
||||
@@ -622,6 +637,8 @@ namespace Slic3r {
|
||||
|
||||
void apply_config(const PrintConfig& config);
|
||||
void set_print(Print* print) { m_print = print; }
|
||||
bgcode::binarize::BinaryData& get_binary_data() { return m_binarizer.get_binary_data(); }
|
||||
const bgcode::binarize::BinaryData& get_binary_data() const { return m_binarizer.get_binary_data(); }
|
||||
|
||||
void enable_stealth_time_estimator(bool enabled);
|
||||
bool is_stealth_time_estimator_enabled() const {
|
||||
@@ -664,6 +681,8 @@ namespace Slic3r {
|
||||
void apply_config_kissslicer(const std::string& filename);
|
||||
void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled);
|
||||
|
||||
void process_ascii_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
void process_binary_file(const std::string& filename, std::function<void()> cancel_callback = nullptr);
|
||||
// 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);
|
||||
@@ -680,8 +699,13 @@ namespace Slic3r {
|
||||
// Move
|
||||
void process_G0(const GCodeReader::GCodeLine& line);
|
||||
void process_G1(const GCodeReader::GCodeLine& line);
|
||||
enum class G1DiscretizationOrigin {
|
||||
G1,
|
||||
G2G3,
|
||||
};
|
||||
void process_G1(const std::array<std::optional<double>, 4>& axes = { std::nullopt, std::nullopt, std::nullopt, std::nullopt },
|
||||
std::optional<double> feedrate = std::nullopt, std::optional<std::string> cmt = std::nullopt);
|
||||
const std::optional<double>& feedrate = std::nullopt, G1DiscretizationOrigin origin = G1DiscretizationOrigin::G1,
|
||||
const std::optional<unsigned int>& remaining_internal_g1_lines = std::nullopt);
|
||||
|
||||
// Arc Move
|
||||
void process_G2_G3(const GCodeReader::GCodeLine& line, bool clockwise);
|
||||
@@ -815,7 +839,7 @@ namespace Slic3r {
|
||||
// Simulates firmware st_synchronize() call
|
||||
void simulate_st_synchronize(float additional_time = 0.0f);
|
||||
|
||||
void update_estimated_times_stats();
|
||||
void update_estimated_statistics();
|
||||
|
||||
double extract_absolute_position_on_axis(Axis axis, const GCodeReader::GCodeLine& line, double area_filament_cross_section);
|
||||
};
|
||||
|
||||
651
src/libslic3r/GCode/GCodeWriter.cpp
Normal file
651
src/libslic3r/GCode/GCodeWriter.cpp
Normal file
@@ -0,0 +1,651 @@
|
||||
#include "GCodeWriter.hpp"
|
||||
#include "../CustomGCode.hpp"
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <assert.h>
|
||||
#include <string_view>
|
||||
#include <boost/math/special_functions/pow.hpp>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <boost/spirit/include/karma.hpp>
|
||||
#endif
|
||||
|
||||
#define FLAVOR_IS(val) this->config.gcode_flavor == val
|
||||
#define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
namespace Slic3r {
|
||||
|
||||
// static
|
||||
bool GCodeWriter::supports_separate_travel_acceleration(GCodeFlavor flavor)
|
||||
{
|
||||
return (flavor == gcfRepetier || flavor == gcfMarlinFirmware || flavor == gcfRepRapFirmware);
|
||||
}
|
||||
|
||||
void GCodeWriter::apply_print_config(const PrintConfig &print_config)
|
||||
{
|
||||
this->config.apply(print_config, true);
|
||||
m_extrusion_axis = get_extrusion_axis(this->config);
|
||||
m_single_extruder_multi_material = print_config.single_extruder_multi_material.value;
|
||||
bool use_mach_limits = print_config.gcode_flavor.value == gcfMarlinLegacy
|
||||
|| print_config.gcode_flavor.value == gcfMarlinFirmware
|
||||
|| print_config.gcode_flavor.value == gcfRepRapFirmware;
|
||||
m_max_acceleration = static_cast<unsigned int>(std::round((use_mach_limits && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode) ?
|
||||
print_config.machine_max_acceleration_extruding.values.front() : 0));
|
||||
m_max_travel_acceleration = static_cast<unsigned int>(std::round((use_mach_limits && print_config.machine_limits_usage.value == MachineLimitsUsage::EmitToGCode && supports_separate_travel_acceleration(print_config.gcode_flavor.value)) ?
|
||||
print_config.machine_max_acceleration_travel.values.front() : 0));
|
||||
}
|
||||
|
||||
void GCodeWriter::set_extruders(std::vector<unsigned int> extruder_ids)
|
||||
{
|
||||
std::sort(extruder_ids.begin(), extruder_ids.end());
|
||||
m_extruders.clear();
|
||||
m_extruders.reserve(extruder_ids.size());
|
||||
for (unsigned int extruder_id : extruder_ids)
|
||||
m_extruders.emplace_back(Extruder(extruder_id, &this->config));
|
||||
|
||||
/* we enable support for multiple extruder if any extruder greater than 0 is used
|
||||
(even if prints only uses that one) since we need to output Tx commands
|
||||
first extruder has index 0 */
|
||||
this->multiple_extruders = (*std::max_element(extruder_ids.begin(), extruder_ids.end())) > 0;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::preamble()
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
|
||||
if (FLAVOR_IS_NOT(gcfMakerWare)) {
|
||||
gcode << "G21 ; set units to millimeters\n";
|
||||
gcode << "G90 ; use absolute coordinates\n";
|
||||
}
|
||||
if (FLAVOR_IS(gcfRepRapSprinter) ||
|
||||
FLAVOR_IS(gcfRepRapFirmware) ||
|
||||
FLAVOR_IS(gcfMarlinLegacy) ||
|
||||
FLAVOR_IS(gcfMarlinFirmware) ||
|
||||
FLAVOR_IS(gcfKlipper) ||
|
||||
FLAVOR_IS(gcfTeacup) ||
|
||||
FLAVOR_IS(gcfRepetier) ||
|
||||
FLAVOR_IS(gcfSmoothie))
|
||||
{
|
||||
if (this->config.use_relative_e_distances) {
|
||||
gcode << "M83 ; use relative distances for extrusion\n";
|
||||
} else {
|
||||
gcode << "M82 ; use absolute distances for extrusion\n";
|
||||
}
|
||||
gcode << this->reset_e(true);
|
||||
}
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::postamble() const
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
if (FLAVOR_IS(gcfMachinekit))
|
||||
gcode << "M2 ; end of program\n";
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const
|
||||
{
|
||||
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
|
||||
return {};
|
||||
|
||||
std::string_view code, comment;
|
||||
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
|
||||
code = "M109"sv;
|
||||
comment = "set temperature and wait for it to be reached"sv;
|
||||
} else {
|
||||
if (FLAVOR_IS(gcfRepRapFirmware)) { // M104 is deprecated on RepRapFirmware
|
||||
code = "G10"sv;
|
||||
} else {
|
||||
code = "M104"sv;
|
||||
}
|
||||
comment = "set temperature"sv;
|
||||
}
|
||||
|
||||
std::ostringstream gcode;
|
||||
gcode << code << " ";
|
||||
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
|
||||
gcode << "P";
|
||||
} else {
|
||||
gcode << "S";
|
||||
}
|
||||
gcode << temperature;
|
||||
bool multiple_tools = this->multiple_extruders && ! m_single_extruder_multi_material;
|
||||
if (tool != -1 && (multiple_tools || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish) || FLAVOR_IS(gcfRepRapFirmware)) ) {
|
||||
if (FLAVOR_IS(gcfRepRapFirmware)) {
|
||||
gcode << " P" << tool;
|
||||
} else {
|
||||
gcode << " T" << tool;
|
||||
}
|
||||
}
|
||||
gcode << " ; " << comment << "\n";
|
||||
|
||||
if ((FLAVOR_IS(gcfTeacup) || FLAVOR_IS(gcfRepRapFirmware)) && wait)
|
||||
gcode << "M116 ; wait for temperature to be reached\n";
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_bed_temperature(unsigned int temperature, bool wait)
|
||||
{
|
||||
if (temperature == m_last_bed_temperature && (! wait || m_last_bed_temperature_reached))
|
||||
return {};
|
||||
|
||||
m_last_bed_temperature = temperature;
|
||||
m_last_bed_temperature_reached = wait;
|
||||
|
||||
std::string_view code, comment;
|
||||
if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
|
||||
if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
|
||||
code = "M109"sv;
|
||||
} else {
|
||||
code = "M190"sv;
|
||||
}
|
||||
comment = "set bed temperature and wait for it to be reached"sv;
|
||||
} else {
|
||||
code = "M140"sv;
|
||||
comment = "set bed temperature"sv;
|
||||
}
|
||||
|
||||
std::ostringstream gcode;
|
||||
gcode << code << " ";
|
||||
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
|
||||
gcode << "P";
|
||||
} else {
|
||||
gcode << "S";
|
||||
}
|
||||
gcode << temperature << " ; " << comment << "\n";
|
||||
|
||||
if (FLAVOR_IS(gcfTeacup) && wait)
|
||||
gcode << "M116 ; wait for bed temperature to be reached\n";
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
|
||||
//B34
|
||||
std::string GCodeWriter::set_pressure_advance(double pa) const
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
if (pa < 0)
|
||||
return gcode.str();
|
||||
else{
|
||||
if (FLAVOR_IS(gcfKlipper))
|
||||
gcode << "SET_PRESSURE_ADVANCE ADVANCE=" << std::setprecision(4) << pa << "; Override pressure advance value\n";
|
||||
else if(FLAVOR_IS(gcfRepRapFirmware))
|
||||
gcode << ("M572 D0 S") << std::setprecision(4) << pa << "; Override pressure advance value\n";
|
||||
else
|
||||
gcode << "M900 K" <<std::setprecision(4)<< pa << "; Override pressure advance value\n";
|
||||
}
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
//B24
|
||||
std::string GCodeWriter::set_volume_temperature(unsigned int temperature, bool wait)
|
||||
{
|
||||
if (temperature == m_last_volume_temperature && (! wait || m_last_volume_temperature_reached))
|
||||
return std::string();
|
||||
|
||||
m_last_volume_temperature = temperature;
|
||||
m_last_volume_temperature_reached = wait;
|
||||
|
||||
std::string code, comment;
|
||||
code = "M141";
|
||||
comment = "set Volume temperature";
|
||||
// if (wait && FLAVOR_IS_NOT(gcfTeacup)) {
|
||||
// if (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)) {
|
||||
// code = "M109";
|
||||
// } else {
|
||||
// code = "M190";
|
||||
// }
|
||||
// comment = "set bed temperature and wait for it to be reached";
|
||||
// } else {
|
||||
// code = "M140";
|
||||
// comment = "set bed temperature";
|
||||
// }
|
||||
|
||||
std::ostringstream gcode;
|
||||
gcode << code << " ";
|
||||
if (FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMachinekit)) {
|
||||
gcode << "P";
|
||||
} else {
|
||||
gcode << "S";
|
||||
}
|
||||
gcode << temperature << " ; " << comment << "\n";
|
||||
|
||||
// if (FLAVOR_IS(gcfTeacup) && wait)
|
||||
// gcode << "M116 ; wait for bed temperature to be reached\n";
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_acceleration_internal(Acceleration type, unsigned int acceleration)
|
||||
{
|
||||
// Clamp the acceleration to the allowed maximum.
|
||||
if (type == Acceleration::Print && m_max_acceleration > 0 && acceleration > m_max_acceleration)
|
||||
acceleration = m_max_acceleration;
|
||||
if (type == Acceleration::Travel && m_max_travel_acceleration > 0 && acceleration > m_max_travel_acceleration)
|
||||
acceleration = m_max_travel_acceleration;
|
||||
|
||||
// Are we setting travel acceleration for a flavour that supports separate travel and print acc?
|
||||
bool separate_travel = (type == Acceleration::Travel && supports_separate_travel_acceleration(this->config.gcode_flavor));
|
||||
|
||||
auto& last_value = separate_travel ? m_last_travel_acceleration : m_last_acceleration ;
|
||||
if (acceleration == 0 || acceleration == last_value)
|
||||
return {};
|
||||
|
||||
last_value = acceleration;
|
||||
|
||||
std::ostringstream gcode;
|
||||
if (FLAVOR_IS(gcfRepetier))
|
||||
gcode << (separate_travel ? "M202 X" : "M201 X") << acceleration << " Y" << acceleration;
|
||||
else if (FLAVOR_IS(gcfRepRapFirmware) || FLAVOR_IS(gcfMarlinFirmware))
|
||||
gcode << (separate_travel ? "M204 T" : "M204 P") << acceleration;
|
||||
else
|
||||
gcode << "M204 S" << acceleration;
|
||||
|
||||
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
|
||||
gcode << "\n";
|
||||
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::reset_e(bool force)
|
||||
{
|
||||
return
|
||||
FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish) || this->config.use_relative_e_distances ||
|
||||
(m_extruder != nullptr && ! m_extruder->reset_E() && ! force) ||
|
||||
m_extrusion_axis.empty() ?
|
||||
std::string{} :
|
||||
std::string("G92 ") + m_extrusion_axis + (this->config.gcode_comments ? "0 ; reset extrusion distance\n" : "0\n");
|
||||
}
|
||||
|
||||
std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
|
||||
{
|
||||
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
|
||||
return {};
|
||||
unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5);
|
||||
if (!allow_100) percent = std::min(percent, (unsigned int)99);
|
||||
|
||||
std::ostringstream gcode;
|
||||
gcode << "M73 P" << percent;
|
||||
if (this->config.gcode_comments) gcode << " ; update progress";
|
||||
gcode << "\n";
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::toolchange_prefix() const
|
||||
{
|
||||
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
|
||||
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
|
||||
}
|
||||
|
||||
std::string GCodeWriter::toolchange(unsigned int extruder_id)
|
||||
{
|
||||
// set the new extruder
|
||||
auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; });
|
||||
assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id);
|
||||
m_extruder = &*it_extruder;
|
||||
|
||||
// return the toolchange command
|
||||
// if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
std::ostringstream gcode;
|
||||
if (this->multiple_extruders) {
|
||||
gcode << this->toolchange_prefix() << extruder_id;
|
||||
if (this->config.gcode_comments)
|
||||
gcode << " ; change extruder";
|
||||
gcode << "\n";
|
||||
gcode << this->reset_e(true);
|
||||
}
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_speed(double F, const std::string_view comment, const std::string_view cooling_marker) const
|
||||
{
|
||||
assert(F > 0.);
|
||||
assert(F < 100000.);
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_f(F);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
w.emit_string(cooling_marker);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment)
|
||||
{
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point);
|
||||
//B36
|
||||
auto speed = m_is_first_layer ? this->config.get_abs_value("first_layer_travel_speed") :
|
||||
this->config.travel_speed.value;
|
||||
w.emit_f(this->config.travel_speed.value * 60.0);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) < 1200.);
|
||||
assert(std::abs(ij.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_ij(ij);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment)
|
||||
{
|
||||
if (std::abs(point.x() - m_pos.x()) < EPSILON && std::abs(point.y() - m_pos.y()) < EPSILON) {
|
||||
return this->travel_to_z(point.z(), comment);
|
||||
} else if (std::abs(point.z() - m_pos.z()) < EPSILON) {
|
||||
return this->travel_to_xy(point.head<2>(), comment);
|
||||
} else {
|
||||
m_pos = point;
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value};
|
||||
w.emit_f(speed.norm() * 60.0);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string GCodeWriter::travel_to_z(double z, const std::string_view comment)
|
||||
{
|
||||
return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment)
|
||||
{
|
||||
m_pos.z() = z;
|
||||
|
||||
double speed = this->config.travel_speed_z.value;
|
||||
if (speed == 0.)
|
||||
speed = this->config.travel_speed.value;
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_z(z);
|
||||
w.emit_f(speed * 60.0);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(dE != 0);
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xy(point);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(dE) < 1000.0);
|
||||
assert(dE != 0);
|
||||
assert(std::abs(point.x()) < 1200.);
|
||||
assert(std::abs(point.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) < 1200.);
|
||||
assert(std::abs(ij.y()) < 1200.);
|
||||
assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001);
|
||||
|
||||
m_pos.head<2>() = point.head<2>();
|
||||
|
||||
GCodeG2G3Formatter w(ccw);
|
||||
w.emit_xy(point);
|
||||
w.emit_ij(ij);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
|
||||
#if 0
|
||||
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment)
|
||||
{
|
||||
m_pos = point;
|
||||
m_lifted = 0;
|
||||
m_extruder->extrude(dE);
|
||||
|
||||
GCodeG1Formatter w;
|
||||
w.emit_xyz(point);
|
||||
w.emit_e(m_extrusion_axis, m_extruder->E());
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
return w.string();
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string GCodeWriter::retract(bool before_wipe)
|
||||
{
|
||||
double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.;
|
||||
assert(factor >= 0. && factor <= 1. + EPSILON);
|
||||
return this->_retract(
|
||||
factor * m_extruder->retract_length(),
|
||||
m_extruder->retract_restart_extra(),
|
||||
"retract"
|
||||
);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::retract_for_toolchange(bool before_wipe)
|
||||
{
|
||||
double factor = before_wipe ? m_extruder->retract_before_wipe() : 1.;
|
||||
assert(factor >= 0. && factor <= 1. + EPSILON);
|
||||
return this->_retract(
|
||||
factor * m_extruder->retract_length_toolchange(),
|
||||
m_extruder->retract_restart_extra_toolchange(),
|
||||
"retract for toolchange"
|
||||
);
|
||||
}
|
||||
|
||||
std::string GCodeWriter::_retract(double length, double restart_extra, const std::string_view comment)
|
||||
{
|
||||
assert(std::abs(length) < 1000.0);
|
||||
assert(std::abs(restart_extra) < 1000.0);
|
||||
/* If firmware retraction is enabled, we use a fake value of 1
|
||||
since we ignore the actual configured retract_length which
|
||||
might be 0, in which case the retraction logic gets skipped. */
|
||||
if (this->config.use_firmware_retraction)
|
||||
length = 1;
|
||||
|
||||
// If we use volumetric E values we turn lengths into volumes */
|
||||
if (this->config.use_volumetric_e) {
|
||||
double d = m_extruder->filament_diameter();
|
||||
double area = d * d * PI/4;
|
||||
length = length * area;
|
||||
restart_extra = restart_extra * area;
|
||||
}
|
||||
|
||||
std::string gcode;
|
||||
if (auto [dE, emitE] = m_extruder->retract(length, restart_extra); dE != 0) {
|
||||
if (this->config.use_firmware_retraction) {
|
||||
gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n";
|
||||
} else if (! m_extrusion_axis.empty()) {
|
||||
GCodeG1Formatter w;
|
||||
w.emit_e(m_extrusion_axis, emitE);
|
||||
w.emit_f(m_extruder->retract_speed() * 60.);
|
||||
w.emit_comment(this->config.gcode_comments, comment);
|
||||
gcode = w.string();
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAVOR_IS(gcfMakerWare))
|
||||
gcode += "M103 ; extruder off\n";
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::unretract()
|
||||
{
|
||||
std::string gcode;
|
||||
|
||||
if (FLAVOR_IS(gcfMakerWare))
|
||||
gcode = "M101 ; extruder on\n";
|
||||
|
||||
if (auto [dE, emitE] = m_extruder->unretract(); dE != 0) {
|
||||
if (this->config.use_firmware_retraction) {
|
||||
gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n";
|
||||
gcode += this->reset_e();
|
||||
} else if (! m_extrusion_axis.empty()) {
|
||||
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
|
||||
GCodeG1Formatter w;
|
||||
w.emit_e(m_extrusion_axis, emitE);
|
||||
w.emit_f(m_extruder->deretract_speed() * 60.);
|
||||
w.emit_comment(this->config.gcode_comments, " ; unretract");
|
||||
gcode += w.string();
|
||||
}
|
||||
}
|
||||
|
||||
return gcode;
|
||||
}
|
||||
|
||||
|
||||
void GCodeWriter::update_position(const Vec3d &new_pos)
|
||||
{
|
||||
m_pos = new_pos;
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed)
|
||||
{
|
||||
std::ostringstream gcode;
|
||||
if (speed == 0) {
|
||||
switch (gcode_flavor) {
|
||||
case gcfTeacup:
|
||||
gcode << "M106 S0"; break;
|
||||
case gcfMakerWare:
|
||||
case gcfSailfish:
|
||||
gcode << "M127"; break;
|
||||
default:
|
||||
//B15 //B25 //B39
|
||||
gcode << "M107\nM106 P2 S0"; break;
|
||||
}
|
||||
if (gcode_comments)
|
||||
gcode << " ; disable fan";
|
||||
gcode << "\n";
|
||||
} else {
|
||||
switch (gcode_flavor) {
|
||||
case gcfMakerWare:
|
||||
case gcfSailfish:
|
||||
gcode << "M126"; break;
|
||||
case gcfMach3:
|
||||
case gcfMachinekit:
|
||||
gcode << "M106 P" << 255.0 * speed / 100.0; break;
|
||||
default:
|
||||
gcode << "M106 S" << 255.0 * speed / 100.0; break;
|
||||
}
|
||||
if (gcode_comments)
|
||||
gcode << " ; enable fan";
|
||||
gcode << "\n";
|
||||
}
|
||||
return gcode.str();
|
||||
}
|
||||
|
||||
std::string GCodeWriter::set_fan(unsigned int speed) const
|
||||
{
|
||||
return GCodeWriter::set_fan(this->config.gcode_flavor, this->config.gcode_comments, speed);
|
||||
}
|
||||
|
||||
|
||||
//B38
|
||||
void GCodeWriter::add_object_start_labels(std::string &gcode)
|
||||
{
|
||||
if (!m_gcode_label_objects_start.empty()) {
|
||||
gcode += m_gcode_label_objects_start;
|
||||
m_gcode_label_objects_start = "";
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeWriter::add_object_end_labels(std::string &gcode)
|
||||
{
|
||||
if (!m_gcode_label_objects_end.empty()) {
|
||||
gcode += m_gcode_label_objects_end;
|
||||
m_gcode_label_objects_end = "";
|
||||
}
|
||||
}
|
||||
|
||||
void GCodeWriter::add_object_change_labels(std::string &gcode)
|
||||
{
|
||||
add_object_end_labels(gcode);
|
||||
add_object_start_labels(gcode);
|
||||
}
|
||||
|
||||
|
||||
void GCodeFormatter::emit_axis(const char axis, const double v, size_t digits) {
|
||||
assert(digits <= 9);
|
||||
static constexpr const std::array<int, 10> pow_10{1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
|
||||
*ptr_err.ptr++ = ' '; *ptr_err.ptr++ = axis;
|
||||
|
||||
char *base_ptr = this->ptr_err.ptr;
|
||||
auto v_int = int64_t(std::round(v * pow_10[digits]));
|
||||
// Older stdlib on macOS doesn't support std::from_chars at all, so it is used boost::spirit::karma::generate instead of it.
|
||||
// That is a little bit slower than std::to_chars but not much.
|
||||
#ifdef __APPLE__
|
||||
boost::spirit::karma::generate(this->ptr_err.ptr, boost::spirit::karma::int_generator<int64_t>(), v_int);
|
||||
#else
|
||||
// this->buf_end minus 1 because we need space for adding the extra decimal point.
|
||||
this->ptr_err = std::to_chars(this->ptr_err.ptr, this->buf_end - 1, v_int);
|
||||
#endif
|
||||
size_t writen_digits = (this->ptr_err.ptr - base_ptr) - (v_int < 0 ? 1 : 0);
|
||||
if (writen_digits < digits) {
|
||||
// Number is smaller than 10^digits, so that we will pad it with zeros.
|
||||
size_t remaining_digits = digits - writen_digits;
|
||||
// Move all newly inserted chars by remaining_digits to allocate space for padding with zeros.
|
||||
for (char *from_ptr = this->ptr_err.ptr - 1, *to_ptr = from_ptr + remaining_digits; from_ptr >= this->ptr_err.ptr - writen_digits; --to_ptr, --from_ptr)
|
||||
*to_ptr = *from_ptr;
|
||||
|
||||
memset(this->ptr_err.ptr - writen_digits, '0', remaining_digits);
|
||||
this->ptr_err.ptr += remaining_digits;
|
||||
}
|
||||
|
||||
// Move all newly inserted chars by one to allocate space for a decimal point.
|
||||
for (char *to_ptr = this->ptr_err.ptr, *from_ptr = to_ptr - 1; from_ptr >= this->ptr_err.ptr - digits; --to_ptr, --from_ptr)
|
||||
*to_ptr = *from_ptr;
|
||||
|
||||
*(this->ptr_err.ptr - digits) = '.';
|
||||
for (size_t i = 0; i < digits; ++i) {
|
||||
if (*this->ptr_err.ptr != '0')
|
||||
break;
|
||||
this->ptr_err.ptr--;
|
||||
}
|
||||
if (*this->ptr_err.ptr == '.')
|
||||
this->ptr_err.ptr--;
|
||||
if ((this->ptr_err.ptr + 1) == base_ptr || *this->ptr_err.ptr == '-')
|
||||
*(++this->ptr_err.ptr) = '0';
|
||||
this->ptr_err.ptr++;
|
||||
|
||||
#if 0 // #ifndef NDEBUG
|
||||
{
|
||||
// Verify that the optimized formatter produces the same result as the standard sprintf().
|
||||
double v1 = atof(std::string(base_ptr, this->ptr_err.ptr).c_str());
|
||||
char buf[2048];
|
||||
sprintf(buf, "%.*lf", int(digits), v);
|
||||
double v2 = atof(buf);
|
||||
// Numbers may differ when rounding at exactly or very close to 0.5 due to numerical issues when scaling the double to an integer.
|
||||
// Thus the complex assert.
|
||||
// assert(v1 == v2);
|
||||
assert(std::abs(v1 - v) * pow_10[digits] < 0.50001);
|
||||
assert(std::abs(v2 - v) * pow_10[digits] < 0.50001);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
275
src/libslic3r/GCode/GCodeWriter.hpp
Normal file
275
src/libslic3r/GCode/GCodeWriter.hpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#ifndef slic3r_GCodeWriter_hpp_
|
||||
#define slic3r_GCodeWriter_hpp_
|
||||
|
||||
#include "../libslic3r.h"
|
||||
#include "../Extruder.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "CoolingBuffer.hpp"
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <charconv>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeWriter {
|
||||
public:
|
||||
GCodeConfig config;
|
||||
bool multiple_extruders;
|
||||
|
||||
GCodeWriter() :
|
||||
multiple_extruders(false), m_extrusion_axis("E"), m_extruder(nullptr),
|
||||
m_single_extruder_multi_material(false),
|
||||
m_last_acceleration(0), m_max_acceleration(0),
|
||||
m_last_bed_temperature(0), m_last_bed_temperature_reached(true),
|
||||
//B24
|
||||
m_last_volume_temperature(0), m_last_volume_temperature_reached(true),
|
||||
m_lifted(0)
|
||||
//B36
|
||||
, m_is_first_layer(true)
|
||||
{}
|
||||
Extruder* extruder() { return m_extruder; }
|
||||
const Extruder* extruder() const { return m_extruder; }
|
||||
|
||||
// Returns empty string for gcfNoExtrusion.
|
||||
std::string extrusion_axis() const { return m_extrusion_axis; }
|
||||
void apply_print_config(const PrintConfig &print_config);
|
||||
// Extruders are expected to be sorted in an increasing order.
|
||||
void set_extruders(std::vector<unsigned int> extruder_ids);
|
||||
const std::vector<Extruder>& extruders() const { return m_extruders; }
|
||||
std::vector<unsigned int> extruder_ids() const {
|
||||
std::vector<unsigned int> out;
|
||||
out.reserve(m_extruders.size());
|
||||
for (const Extruder &e : m_extruders)
|
||||
out.push_back(e.id());
|
||||
return out;
|
||||
}
|
||||
std::string preamble();
|
||||
std::string postamble() const;
|
||||
std::string set_temperature(unsigned int temperature, bool wait = false, int tool = -1) const;
|
||||
std::string set_bed_temperature(unsigned int temperature, bool wait = false);
|
||||
//B34
|
||||
std::string set_pressure_advance(double pa) const;
|
||||
//B24
|
||||
std::string set_volume_temperature(unsigned int temperature, bool wait = false);
|
||||
std::string set_print_acceleration(unsigned int acceleration) { return set_acceleration_internal(Acceleration::Print, acceleration); }
|
||||
std::string set_travel_acceleration(unsigned int acceleration) { return set_acceleration_internal(Acceleration::Travel, acceleration); }
|
||||
std::string reset_e(bool force = false);
|
||||
std::string update_progress(unsigned int num, unsigned int tot, bool allow_100 = false) const;
|
||||
// return false if this extruder was already selected
|
||||
bool need_toolchange(unsigned int extruder_id) const
|
||||
{ return m_extruder == nullptr || m_extruder->id() != extruder_id; }
|
||||
std::string set_extruder(unsigned int extruder_id)
|
||||
{ return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; }
|
||||
// Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
|
||||
// printed with the same extruder.
|
||||
std::string toolchange_prefix() const;
|
||||
std::string toolchange(unsigned int extruder_id);
|
||||
std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const;
|
||||
std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {});
|
||||
std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {});
|
||||
std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {});
|
||||
std::string get_travel_to_z_gcode(double z, const std::string_view comment);
|
||||
std::string travel_to_z(double z, const std::string_view comment = {});
|
||||
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {});
|
||||
std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment);
|
||||
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string_view comment = {});
|
||||
std::string retract(bool before_wipe = false);
|
||||
std::string retract_for_toolchange(bool before_wipe = false);
|
||||
std::string unretract();
|
||||
|
||||
// Current position of the printer, in G-code coordinates.
|
||||
// Z coordinate of current position contains zhop. If zhop is applied (this->zhop() > 0),
|
||||
// then the print_z = this->get_position().z() - this->zhop().
|
||||
Vec3d get_position() const { return m_pos; }
|
||||
// Zhop value is obsolete. This is for backwards compability.
|
||||
double get_zhop() const { return 0; }
|
||||
// Update position of the print head based on the final position returned by a custom G-code block.
|
||||
// The new position Z coordinate contains the Z-hop.
|
||||
// GCodeWriter expects the custom script to NOT change print_z, only Z-hop, thus the print_z is maintained
|
||||
// by this function while the current Z-hop accumulator is updated.
|
||||
void update_position(const Vec3d &new_pos);
|
||||
|
||||
// Returns whether this flavor supports separate print and travel acceleration.
|
||||
static bool supports_separate_travel_acceleration(GCodeFlavor flavor);
|
||||
|
||||
// To be called by the CoolingBuffer from another thread.
|
||||
static std::string set_fan(const GCodeFlavor gcode_flavor, bool gcode_comments, unsigned int speed);
|
||||
// To be called by the main thread. It always emits the G-code, it does not remember the previous state.
|
||||
// Keeping the state is left to the CoolingBuffer, which runs asynchronously on another thread.
|
||||
std::string set_fan(unsigned int speed) const;
|
||||
|
||||
//B36
|
||||
void set_is_first_layer(bool bval) { m_is_first_layer = bval; }
|
||||
|
||||
//B38
|
||||
void set_object_start_str(std::string start_string) { m_gcode_label_objects_start = start_string; }
|
||||
bool is_object_start_str_empty() { return m_gcode_label_objects_start.empty(); }
|
||||
void set_object_end_str(std::string end_string) { m_gcode_label_objects_end = end_string; }
|
||||
bool is_object_end_str_empty() { return m_gcode_label_objects_end.empty(); }
|
||||
void add_object_start_labels(std::string &gcode);
|
||||
void add_object_end_labels(std::string &gcode);
|
||||
void add_object_change_labels(std::string &gcode);
|
||||
|
||||
|
||||
private:
|
||||
// Extruders are sorted by their ID, so that binary search is possible.
|
||||
std::vector<Extruder> m_extruders;
|
||||
std::string m_extrusion_axis;
|
||||
bool m_single_extruder_multi_material;
|
||||
Extruder* m_extruder;
|
||||
unsigned int m_last_acceleration = (unsigned int)(-1);
|
||||
unsigned int m_last_travel_acceleration = (unsigned int)(-1); // only used for flavors supporting separate print/travel acc
|
||||
// Limit for setting the acceleration, to respect the machine limits set for the Marlin firmware.
|
||||
// If set to zero, the limit is not in action.
|
||||
unsigned int m_max_acceleration;
|
||||
unsigned int m_max_travel_acceleration;
|
||||
|
||||
unsigned int m_last_bed_temperature;
|
||||
bool m_last_bed_temperature_reached;
|
||||
//B24
|
||||
unsigned int m_last_volume_temperature;
|
||||
bool m_last_volume_temperature_reached;
|
||||
double m_lifted;
|
||||
Vec3d m_pos = Vec3d::Zero();
|
||||
|
||||
//B36
|
||||
bool m_is_first_layer = true;
|
||||
|
||||
//B38
|
||||
std::string m_gcode_label_objects_start;
|
||||
std::string m_gcode_label_objects_end;
|
||||
|
||||
enum class Acceleration {
|
||||
Travel,
|
||||
Print
|
||||
};
|
||||
|
||||
std::string _retract(double length, double restart_extra, const std::string_view comment);
|
||||
std::string set_acceleration_internal(Acceleration type, unsigned int acceleration);
|
||||
};
|
||||
|
||||
class GCodeFormatter {
|
||||
public:
|
||||
GCodeFormatter() {
|
||||
this->buf_end = buf + buflen;
|
||||
this->ptr_err.ptr = this->buf;
|
||||
}
|
||||
|
||||
GCodeFormatter(const GCodeFormatter&) = delete;
|
||||
GCodeFormatter& operator=(const GCodeFormatter&) = delete;
|
||||
|
||||
// At layer height 0.15mm, extrusion width 0.2mm and filament diameter 1.75mm,
|
||||
// the crossection of extrusion is 0.4 * 0.15 = 0.06mm2
|
||||
// and the filament crossection is 1.75^2 = 3.063mm2
|
||||
// thus the filament moves 3.063 / 0.6 = 51x slower than the XY axes
|
||||
// and we need roughly two decimal digits more on extruder than on XY.
|
||||
#if 1
|
||||
static constexpr const int XYZF_EXPORT_DIGITS = 3;
|
||||
static constexpr const int E_EXPORT_DIGITS = 5;
|
||||
#else
|
||||
// order of magnitude smaller extrusion rate erros
|
||||
static constexpr const int XYZF_EXPORT_DIGITS = 4;
|
||||
static constexpr const int E_EXPORT_DIGITS = 6;
|
||||
// excessive accuracy
|
||||
// static constexpr const int XYZF_EXPORT_DIGITS = 6;
|
||||
// static constexpr const int E_EXPORT_DIGITS = 9;
|
||||
#endif
|
||||
|
||||
static constexpr const std::array<double, 10> pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000.};
|
||||
static constexpr const std::array<double, 10> pow_10_inv{1./1., 1./10., 1./100., 1./1000., 1./10000., 1./100000., 1./1000000., 1./10000000., 1./100000000., 1./1000000000.};
|
||||
|
||||
// Quantize doubles to a resolution of the G-code.
|
||||
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
|
||||
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
|
||||
static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
|
||||
static Vec2d quantize(const Vec2d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS) }; }
|
||||
static Vec3d quantize(const Vec3d &pt)
|
||||
{ return { quantize(pt.x(), XYZF_EXPORT_DIGITS), quantize(pt.y(), XYZF_EXPORT_DIGITS), quantize(pt.z(), XYZF_EXPORT_DIGITS) }; }
|
||||
|
||||
void emit_axis(const char axis, const double v, size_t digits);
|
||||
|
||||
void emit_xy(const Vec2d &point) {
|
||||
this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS);
|
||||
this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_xyz(const Vec3d &point) {
|
||||
this->emit_axis('X', point.x(), XYZF_EXPORT_DIGITS);
|
||||
this->emit_axis('Y', point.y(), XYZF_EXPORT_DIGITS);
|
||||
this->emit_z(point.z());
|
||||
}
|
||||
|
||||
void emit_z(const double z) {
|
||||
this->emit_axis('Z', z, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_ij(const Vec2d &point) {
|
||||
if (point.x() != 0)
|
||||
this->emit_axis('I', point.x(), XYZF_EXPORT_DIGITS);
|
||||
if (point.y() != 0)
|
||||
this->emit_axis('J', point.y(), XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_e(const std::string_view axis, double v) {
|
||||
if (! axis.empty()) {
|
||||
// not gcfNoExtrusion
|
||||
this->emit_axis(axis[0], v, E_EXPORT_DIGITS);
|
||||
}
|
||||
}
|
||||
|
||||
void emit_f(double speed) {
|
||||
this->emit_axis('F', speed, XYZF_EXPORT_DIGITS);
|
||||
}
|
||||
|
||||
void emit_string(const std::string_view s) {
|
||||
strncpy(ptr_err.ptr, s.data(), s.size());
|
||||
ptr_err.ptr += s.size();
|
||||
}
|
||||
|
||||
void emit_comment(bool allow_comments, const std::string_view comment) {
|
||||
if (allow_comments && ! comment.empty()) {
|
||||
*ptr_err.ptr ++ = ' '; *ptr_err.ptr ++ = ';'; *ptr_err.ptr ++ = ' ';
|
||||
this->emit_string(comment);
|
||||
}
|
||||
}
|
||||
|
||||
std::string string() {
|
||||
*ptr_err.ptr ++ = '\n';
|
||||
return std::string(this->buf, ptr_err.ptr - buf);
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr const size_t buflen = 256;
|
||||
char buf[buflen];
|
||||
char* buf_end;
|
||||
std::to_chars_result ptr_err;
|
||||
};
|
||||
|
||||
class GCodeG1Formatter : public GCodeFormatter {
|
||||
public:
|
||||
GCodeG1Formatter() {
|
||||
this->buf[0] = 'G';
|
||||
this->buf[1] = '1';
|
||||
this->ptr_err.ptr += 2;
|
||||
}
|
||||
|
||||
GCodeG1Formatter(const GCodeG1Formatter&) = delete;
|
||||
GCodeG1Formatter& operator=(const GCodeG1Formatter&) = delete;
|
||||
};
|
||||
|
||||
class GCodeG2G3Formatter : public GCodeFormatter {
|
||||
public:
|
||||
GCodeG2G3Formatter(bool ccw) {
|
||||
this->buf[0] = 'G';
|
||||
this->buf[1] = ccw ? '3' : '2';
|
||||
this->ptr_err.ptr += 2;
|
||||
}
|
||||
|
||||
GCodeG2G3Formatter(const GCodeG2G3Formatter&) = delete;
|
||||
GCodeG2G3Formatter& operator=(const GCodeG2G3Formatter&) = delete;
|
||||
};
|
||||
} /* namespace Slic3r */
|
||||
|
||||
#endif /* slic3r_GCodeWriter_hpp_ */
|
||||
191
src/libslic3r/GCode/LabelObjects.cpp
Normal file
191
src/libslic3r/GCode/LabelObjects.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
#include "LabelObjects.hpp"
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
Polygon instance_outline(const PrintInstance* pi)
|
||||
{
|
||||
ExPolygons outline;
|
||||
const ModelObject* mo = pi->model_instance->get_object();
|
||||
const ModelInstance* mi = pi->model_instance;
|
||||
for (const ModelVolume *v : mo->volumes) {
|
||||
Polygons vol_outline;
|
||||
vol_outline = project_mesh(v->mesh().its,
|
||||
mi->get_matrix() * v->get_matrix(),
|
||||
[] {});
|
||||
switch (v->type()) {
|
||||
case ModelVolumeType::MODEL_PART: outline = union_ex(outline, vol_outline); break;
|
||||
case ModelVolumeType::NEGATIVE_VOLUME: outline = diff_ex(outline, vol_outline); break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
// The projection may contain multiple polygons, which is not supported by Klipper.
|
||||
// When that happens, calculate and use a 2d convex hull instead.
|
||||
if (outline.size() == 1u)
|
||||
return outline.front().contour;
|
||||
else
|
||||
return pi->model_instance->get_object()->convex_hull_2d(pi->model_instance->get_matrix());
|
||||
}
|
||||
|
||||
}; // anonymous namespace
|
||||
|
||||
|
||||
void LabelObjects::init(const Print& print)
|
||||
{
|
||||
m_label_objects_style = print.config().gcode_label_objects;
|
||||
m_flavor = print.config().gcode_flavor;
|
||||
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return;
|
||||
|
||||
std::map<const ModelObject*, std::vector<const PrintInstance*>> model_object_to_print_instances;
|
||||
|
||||
// Iterate over all PrintObjects and their PrintInstances, collect PrintInstances which
|
||||
// belong to the same ModelObject.
|
||||
for (const PrintObject* po : print.objects())
|
||||
for (const PrintInstance& pi : po->instances())
|
||||
model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi);
|
||||
|
||||
// Now go through the map, assign a unique_id to each of the PrintInstances and get the indices of the
|
||||
// respective ModelObject and ModelInstance so we can use them in the tags. This will maintain
|
||||
// indices even in case that some instances are rotated (those end up in different PrintObjects)
|
||||
// or when some are out of bed (these ModelInstances have no corresponding PrintInstances).
|
||||
int unique_id = 0;
|
||||
for (const auto& [model_object, print_instances] : model_object_to_print_instances) {
|
||||
const ModelObjectPtrs& model_objects = model_object->get_model()->objects;
|
||||
int object_id = int(std::find(model_objects.begin(), model_objects.end(), model_object) - model_objects.begin());
|
||||
for (const PrintInstance* const pi : print_instances) {
|
||||
bool object_has_more_instances = print_instances.size() > 1u;
|
||||
int instance_id = int(std::find(model_object->instances.begin(), model_object->instances.end(), pi->model_instance) - model_object->instances.begin());
|
||||
|
||||
// Now compose the name of the object and define whether indexing is 0 or 1-based.
|
||||
std::string name = model_object->name;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint) {
|
||||
// use zero-based indexing for objects and instances, as we always have done
|
||||
name += " id:" + std::to_string(object_id) + " copy " + std::to_string(instance_id);
|
||||
}
|
||||
else if (m_label_objects_style == LabelObjectsStyle::Firmware) {
|
||||
// use one-based indexing for objects and instances so indices match what we see in QIDISlicer.
|
||||
++object_id;
|
||||
++instance_id;
|
||||
|
||||
if (object_has_more_instances)
|
||||
name += " (Instance " + std::to_string(instance_id) + ")";
|
||||
if (m_flavor == gcfKlipper) {
|
||||
const std::string banned = "-. \r\n\v\t\f";
|
||||
std::replace_if(name.begin(), name.end(), [&banned](char c) { return banned.find(c) != std::string::npos; }, '_');
|
||||
}
|
||||
}
|
||||
|
||||
m_label_data.emplace(pi, LabelData{name, unique_id});
|
||||
++unique_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string LabelObjects::all_objects_header() const
|
||||
{
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
std::string out;
|
||||
|
||||
// Let's sort the values according to unique_id so they are in the same order in which they were added.
|
||||
std::vector<std::pair<const PrintInstance*, LabelData>> label_data_sorted;
|
||||
for (const auto& pi_and_label : m_label_data)
|
||||
label_data_sorted.emplace_back(pi_and_label);
|
||||
std::sort(label_data_sorted.begin(), label_data_sorted.end(), [](const auto& ld1, const auto& ld2) { return ld1.second.unique_id < ld2.second.unique_id; });
|
||||
|
||||
out += "\n";
|
||||
for (const auto& [print_instance, label] : label_data_sorted) {
|
||||
if (m_label_objects_style == LabelObjectsStyle::Firmware && m_flavor == gcfKlipper) {
|
||||
char buffer[64];
|
||||
out += "EXCLUDE_OBJECT_DEFINE NAME=" + label.name;
|
||||
Polygon outline = instance_outline(print_instance);
|
||||
assert(! outline.empty());
|
||||
outline.douglas_peucker(50000.f);
|
||||
Point center = outline.centroid();
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, " CENTER=%.3f,%.3f", unscale<float>(center[0]), unscale<float>(center[1]));
|
||||
out += buffer + std::string(" POLYGON=[");
|
||||
for (const Point& point : outline) {
|
||||
std::snprintf(buffer, sizeof(buffer) - 1, "[%.3f,%.3f],", unscale<float>(point[0]), unscale<float>(point[1]));
|
||||
out += buffer;
|
||||
}
|
||||
out.pop_back();
|
||||
out += "]\n";
|
||||
} else {
|
||||
out += start_object(*print_instance, IncludeName::Yes);
|
||||
out += stop_object(*print_instance);
|
||||
}
|
||||
}
|
||||
out += "\n";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const
|
||||
{
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
out += std::string("; printing object ") + label.name + "\n";
|
||||
else if (m_label_objects_style == LabelObjectsStyle::Firmware) {
|
||||
if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware) {
|
||||
out += std::string("M486 S") + std::to_string(label.unique_id);
|
||||
if (include_name == IncludeName::Yes) {
|
||||
out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? " A" : "\nM486 A");
|
||||
out += (m_flavor == GCodeFlavor::gcfRepRapFirmware ? (std::string("\"") + label.name + "\"") : label.name);
|
||||
}
|
||||
out += "\n";
|
||||
} else if (m_flavor == gcfKlipper)
|
||||
out += "EXCLUDE_OBJECT_START NAME=" + label.name + "\n";
|
||||
else {
|
||||
// Not supported by / implemented for the other firmware flavors.
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string LabelObjects::stop_object(const PrintInstance& print_instance) const
|
||||
{
|
||||
if (m_label_objects_style == LabelObjectsStyle::Disabled)
|
||||
return std::string();
|
||||
|
||||
const LabelData& label = m_label_data.at(&print_instance);
|
||||
|
||||
std::string out;
|
||||
if (m_label_objects_style == LabelObjectsStyle::Octoprint)
|
||||
out += std::string("; stop printing object ") + label.name + "\n";
|
||||
else if (m_label_objects_style == LabelObjectsStyle::Firmware) {
|
||||
if (m_flavor == GCodeFlavor::gcfMarlinFirmware || m_flavor == GCodeFlavor::gcfMarlinLegacy || m_flavor == GCodeFlavor::gcfRepRapFirmware)
|
||||
out += std::string("M486 S-1\n");
|
||||
else if (m_flavor ==gcfKlipper)
|
||||
out += "EXCLUDE_OBJECT_END NAME=" + label.name + "\n";
|
||||
else {
|
||||
// Not supported by / implemented for the other firmware flavors.
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r::GCode
|
||||
45
src/libslic3r/GCode/LabelObjects.hpp
Normal file
45
src/libslic3r/GCode/LabelObjects.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef slic3r_GCode_LabelObjects_hpp_
|
||||
#define slic3r_GCode_LabelObjects_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum GCodeFlavor : unsigned char;
|
||||
enum class LabelObjectsStyle;
|
||||
struct PrintInstance;
|
||||
class Print;
|
||||
|
||||
|
||||
namespace GCode {
|
||||
|
||||
|
||||
class LabelObjects {
|
||||
public:
|
||||
enum class IncludeName {
|
||||
No,
|
||||
Yes
|
||||
};
|
||||
void init(const Print& print);
|
||||
std::string all_objects_header() const;
|
||||
std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const;
|
||||
std::string stop_object(const PrintInstance& print_instance) const;
|
||||
|
||||
private:
|
||||
struct LabelData {
|
||||
std::string name;
|
||||
int unique_id;
|
||||
};
|
||||
|
||||
LabelObjectsStyle m_label_objects_style;
|
||||
GCodeFlavor m_flavor;
|
||||
std::unordered_map<const PrintInstance*, LabelData> m_label_data;
|
||||
|
||||
};
|
||||
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_LabelObjects_hpp_
|
||||
@@ -30,7 +30,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c
|
||||
|
||||
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)));
|
||||
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);
|
||||
@@ -44,7 +44,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio
|
||||
{
|
||||
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))));
|
||||
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);
|
||||
@@ -58,7 +58,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext
|
||||
{
|
||||
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))));
|
||||
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);
|
||||
|
||||
@@ -1484,7 +1484,7 @@ void SeamPlacer::init(const Print &print, std::function<void(void)> throw_if_can
|
||||
}
|
||||
}
|
||||
|
||||
void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool external_first,
|
||||
Point SeamPlacer::place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first,
|
||||
const Point &last_pos) const {
|
||||
using namespace SeamPlacerImpl;
|
||||
const PrintObject *po = layer->object();
|
||||
@@ -1587,7 +1587,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
|
||||
//lastly, for internal perimeters, do the staggering if requested
|
||||
if (po->config().staggered_inner_seams && loop.length() > 0.0) {
|
||||
//fix depth, it is sometimes strongly underestimated
|
||||
depth = std::max(loop.paths[projected_point.path_idx].width, depth);
|
||||
depth = std::max(loop.paths[projected_point.path_idx].width(), depth);
|
||||
|
||||
while (depth > 0.0f) {
|
||||
auto next_point = get_next_loop_point(projected_point);
|
||||
@@ -1605,13 +1605,7 @@ void SeamPlacer::place_seam(const Layer *layer, ExtrusionLoop &loop, bool extern
|
||||
}
|
||||
}
|
||||
|
||||
// Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns,
|
||||
// thus empty path segments will not be produced by G-code export.
|
||||
if (!loop.split_at_vertex(seam_point, scaled<double>(0.0015))) {
|
||||
// The point is not in the original loop.
|
||||
// Insert it.
|
||||
loop.split_at(seam_point, true);
|
||||
}
|
||||
return seam_point;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ public:
|
||||
|
||||
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;
|
||||
Point place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const;
|
||||
|
||||
private:
|
||||
void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info);
|
||||
|
||||
269
src/libslic3r/GCode/SmoothPath.cpp
Normal file
269
src/libslic3r/GCode/SmoothPath.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
#include "SmoothPath.hpp"
|
||||
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../ExtrusionEntityCollection.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
// Length of a smooth path.
|
||||
double length(const SmoothPath &path)
|
||||
{
|
||||
double l = 0;
|
||||
for (const SmoothPathElement &el : path)
|
||||
l += Geometry::ArcWelder::path_length<double>(el.path);
|
||||
return l;
|
||||
}
|
||||
|
||||
// Returns true if the smooth path is longer than a threshold.
|
||||
bool longer_than(const SmoothPath &path, double length)
|
||||
{
|
||||
for (const SmoothPathElement &el : path) {
|
||||
for (auto it = std::next(el.path.begin()); it != el.path.end(); ++ it) {
|
||||
length -= Geometry::ArcWelder::segment_length<double>(*std::prev(it), *it);
|
||||
if (length < 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return length < 0;
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement &el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++ it; it != end; ++ it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
} else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(- angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance)
|
||||
{
|
||||
if (distance >= 0) {
|
||||
for (const SmoothPathElement& el : path) {
|
||||
auto it = el.path.begin();
|
||||
auto end = el.path.end();
|
||||
Point prev_point = it->point;
|
||||
for (++it; it != end; ++it) {
|
||||
Point point = it->point;
|
||||
if (it->linear()) {
|
||||
// Linear segment
|
||||
Vec2d v = (point - prev_point).cast<double>();
|
||||
double lsqr = v.squaredNorm();
|
||||
if (lsqr > sqr(distance))
|
||||
return std::make_optional<Point>(prev_point + (v * (distance / sqrt(lsqr))).cast<coord_t>());
|
||||
distance -= sqrt(lsqr);
|
||||
}
|
||||
else {
|
||||
// Circular segment
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_point.cast<float>(), point.cast<float>(), it->radius);
|
||||
double len = std::abs(it->radius) * angle;
|
||||
if (len > distance) {
|
||||
// Rotate the segment end point in reverse towards the start point.
|
||||
return std::make_optional<Point>(prev_point.rotated(-angle * (distance / len),
|
||||
Geometry::ArcWelder::arc_center(prev_point.cast<float>(), point.cast<float>(), it->radius, it->ccw()).cast<coord_t>()));
|
||||
}
|
||||
distance -= len;
|
||||
}
|
||||
if (distance < 0)
|
||||
return std::make_optional<Point>(point);
|
||||
prev_point = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Failed.
|
||||
return {};
|
||||
}
|
||||
|
||||
// Clip length of a smooth path, for seam hiding.
|
||||
// When clipping the end of a path, don't create segments shorter than min_point_distance_threshold,
|
||||
// rather discard such a degenerate segment.
|
||||
double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold)
|
||||
{
|
||||
while (! path.empty() && distance > 0) {
|
||||
Geometry::ArcWelder::Path &p = path.back().path;
|
||||
distance = clip_end(p, distance);
|
||||
if (p.empty()) {
|
||||
path.pop_back();
|
||||
} else {
|
||||
// Trailing path was trimmed and it is valid.
|
||||
Geometry::ArcWelder::Path &last_path = path.back().path;
|
||||
assert(last_path.size() > 1);
|
||||
assert(distance == 0);
|
||||
// Distance to go is zero.
|
||||
// Remove the last segment if its length is shorter than min_point_distance_threshold.
|
||||
const Geometry::ArcWelder::Segment &prev_segment = last_path[last_path.size() - 2];
|
||||
const Geometry::ArcWelder::Segment &last_segment = last_path.back();
|
||||
if (Geometry::ArcWelder::segment_length<double>(prev_segment, last_segment) < min_point_distance_threshold) {
|
||||
last_path.pop_back();
|
||||
if (last_path.size() < 2)
|
||||
path.pop_back();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Return distance to go after the whole smooth path was trimmed to zero.
|
||||
return distance;
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
double tolerance = params.tolerance;
|
||||
if (path.role().is_sparse_infill())
|
||||
// Use 3x lower resolution than the object fine detail for sparse infill.
|
||||
tolerance *= 3.;
|
||||
else if (path.role().is_support())
|
||||
// Use 4x lower resolution than the object fine detail for support.
|
||||
tolerance *= 4.;
|
||||
else if (path.role().is_skirt())
|
||||
// Brim is currently marked as skirt.
|
||||
// Use 4x lower resolution than the object fine detail for skirt & brim.
|
||||
tolerance *= 4.;
|
||||
m_cache[&path.polyline] = Slic3r::Geometry::ArcWelder::fit_path(path.polyline.points, tolerance, params.fit_circle_tolerance);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionMultiPath &multi_path, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : multi_path.paths)
|
||||
this->interpolate_add(path, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionPath &path : loop.paths)
|
||||
this->interpolate_add(path, params);
|
||||
}
|
||||
|
||||
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms)
|
||||
{
|
||||
for (const ExtrusionEntity *ee : eec) {
|
||||
if (ee->is_collection())
|
||||
this->interpolate_add(*static_cast<const ExtrusionEntityCollection*>(ee), params);
|
||||
else if (const ExtrusionPath *path = dynamic_cast<const ExtrusionPath*>(ee); path)
|
||||
this->interpolate_add(*path, params);
|
||||
else if (const ExtrusionMultiPath *multi_path = dynamic_cast<const ExtrusionMultiPath*>(ee); multi_path)
|
||||
this->interpolate_add(*multi_path, params);
|
||||
else if (const ExtrusionLoop *loop = dynamic_cast<const ExtrusionLoop*>(ee); loop)
|
||||
this->interpolate_add(*loop, params);
|
||||
else
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const Polyline *pl) const
|
||||
{
|
||||
auto it = m_cache.find(pl);
|
||||
return it == m_cache.end() ? nullptr : &it->second;
|
||||
}
|
||||
|
||||
const Geometry::ArcWelder::Path* SmoothPathCache::resolve(const ExtrusionPath &path) const
|
||||
{
|
||||
return this->resolve(&path.polyline);
|
||||
}
|
||||
|
||||
Geometry::ArcWelder::Path SmoothPathCache::resolve_or_fit(const ExtrusionPath &path, bool reverse, double tolerance) const
|
||||
{
|
||||
Geometry::ArcWelder::Path out;
|
||||
if (const Geometry::ArcWelder::Path *cached = this->resolve(path); cached)
|
||||
out = *cached;
|
||||
else
|
||||
out = Geometry::ArcWelder::fit_polyline(path.polyline.points, tolerance);
|
||||
if (reverse)
|
||||
Geometry::ArcWelder::reverse(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const
|
||||
{
|
||||
SmoothPath out;
|
||||
out.reserve(paths.size());
|
||||
if (reverse) {
|
||||
for (auto it = paths.crbegin(); it != paths.crend(); ++ it)
|
||||
out.push_back({ it->attributes(), this->resolve_or_fit(*it, true, resolution) });
|
||||
} else {
|
||||
for (auto it = paths.cbegin(); it != paths.cend(); ++ it)
|
||||
out.push_back({ it->attributes(), this->resolve_or_fit(*it, false, resolution) });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit(const ExtrusionMultiPath &multipath, bool reverse, double resolution) const
|
||||
{
|
||||
return this->resolve_or_fit(multipath.paths, reverse, resolution);
|
||||
}
|
||||
|
||||
SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam(
|
||||
const ExtrusionLoop &loop, const bool reverse, const double resolution,
|
||||
const Point &seam_point, const double seam_point_merge_distance_threshold) const
|
||||
{
|
||||
SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution);
|
||||
assert(! out.empty());
|
||||
if (! out.empty()) {
|
||||
// Find a closest point on a vector of smooth paths.
|
||||
Geometry::ArcWelder::PathSegmentProjection proj;
|
||||
int proj_path = -1;
|
||||
for (const SmoothPathElement &el : out)
|
||||
if (Geometry::ArcWelder::PathSegmentProjection this_proj = Geometry::ArcWelder::point_to_path_projection(el.path, seam_point, proj.distance2);
|
||||
this_proj.valid()) {
|
||||
// Found a better (closer) projection.
|
||||
assert(this_proj.distance2 < proj.distance2);
|
||||
assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size());
|
||||
proj = this_proj;
|
||||
proj_path = &el - out.data();
|
||||
if (proj.distance2 == 0)
|
||||
// There will be no better split point found than one with zero distance.
|
||||
break;
|
||||
}
|
||||
assert(proj_path >= 0);
|
||||
// Split the path at the closest point.
|
||||
Geometry::ArcWelder::Path &path = out[proj_path].path;
|
||||
std::pair<Geometry::ArcWelder::Path, Geometry::ArcWelder::Path> split = Geometry::ArcWelder::split_at(
|
||||
path, proj, seam_point_merge_distance_threshold);
|
||||
if (split.second.empty()) {
|
||||
std::rotate(out.begin(), out.begin() + proj_path + 1, out.end());
|
||||
assert(out.back().path == split.first);
|
||||
} else {
|
||||
ExtrusionAttributes attr = out[proj_path].path_attributes;
|
||||
std::rotate(out.begin(), out.begin() + proj_path, out.end());
|
||||
out.front().path = std::move(split.second);
|
||||
if (! split.first.empty()) {
|
||||
if (out.back().path_attributes == attr) {
|
||||
// Merge with the last segment.
|
||||
out.back().path.insert(out.back().path.end(), std::next(split.first.begin()), split.first.end());
|
||||
} else
|
||||
out.push_back({ attr, std::move(split.first) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
||||
89
src/libslic3r/GCode/SmoothPath.hpp
Normal file
89
src/libslic3r/GCode/SmoothPath.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef slic3r_GCode_SmoothPath_hpp_
|
||||
#define slic3r_GCode_SmoothPath_hpp_
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "../ExtrusionEntity.hpp"
|
||||
#include "../Geometry/ArcWelder.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ExtrusionEntityCollection;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
struct SmoothPathElement
|
||||
{
|
||||
ExtrusionAttributes path_attributes;
|
||||
Geometry::ArcWelder::Path path;
|
||||
};
|
||||
|
||||
using SmoothPath = std::vector<SmoothPathElement>;
|
||||
|
||||
// Length of a smooth path.
|
||||
double length(const SmoothPath &path);
|
||||
// Returns true if the smooth path is longer than a threshold.
|
||||
bool longer_than(const SmoothPath &path, const double length);
|
||||
|
||||
std::optional<Point> sample_path_point_at_distance_from_start(const SmoothPath &path, double distance);
|
||||
std::optional<Point> sample_path_point_at_distance_from_end(const SmoothPath &path, double distance);
|
||||
|
||||
// Clip end of a smooth path, for seam hiding.
|
||||
// When clipping the end of a path, don't create segments shorter than min_point_distance_threshold,
|
||||
// rather discard such a degenerate segment.
|
||||
double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold);
|
||||
|
||||
class SmoothPathCache
|
||||
{
|
||||
public:
|
||||
struct InterpolationParameters {
|
||||
double tolerance;
|
||||
double fit_circle_tolerance;
|
||||
};
|
||||
|
||||
void interpolate_add(const ExtrusionPath &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters ¶ms);
|
||||
void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters ¶ms);
|
||||
|
||||
const Geometry::ArcWelder::Path* resolve(const Polyline *pl) const;
|
||||
const Geometry::ArcWelder::Path* resolve(const ExtrusionPath &path) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
Geometry::ArcWelder::Path resolve_or_fit(const ExtrusionPath &path, bool reverse, double resolution) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
SmoothPath resolve_or_fit(const ExtrusionPaths &paths, bool reverse, double resolution) const;
|
||||
SmoothPath resolve_or_fit(const ExtrusionMultiPath &path, bool reverse, double resolution) const;
|
||||
|
||||
// Look-up a smooth representation of path in the cache. If it does not exist, produce a simplified polyline.
|
||||
SmoothPath resolve_or_fit_split_with_seam(
|
||||
const ExtrusionLoop &path, const bool reverse, const double resolution,
|
||||
const Point &seam_point, const double seam_point_merge_distance_threshold) const;
|
||||
|
||||
private:
|
||||
ankerl::unordered_dense::map<const Polyline*, Geometry::ArcWelder::Path> m_cache;
|
||||
};
|
||||
|
||||
// Encapsulates references to global and layer local caches of smooth extrusion paths.
|
||||
class SmoothPathCaches final
|
||||
{
|
||||
public:
|
||||
SmoothPathCaches() = delete;
|
||||
SmoothPathCaches(const SmoothPathCache &global, const SmoothPathCache &layer_local) :
|
||||
m_global(&global), m_layer_local(&layer_local) {}
|
||||
SmoothPathCaches operator=(const SmoothPathCaches &rhs)
|
||||
{ m_global = rhs.m_global; m_layer_local = rhs.m_layer_local; return *this; }
|
||||
|
||||
const SmoothPathCache& global() const { return *m_global; }
|
||||
const SmoothPathCache& layer_local() const { return *m_layer_local; }
|
||||
|
||||
private:
|
||||
const SmoothPathCache *m_global;
|
||||
const SmoothPathCache *m_layer_local;
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_SmoothPath_hpp_
|
||||
@@ -19,6 +19,9 @@ public:
|
||||
m_enabled = en;
|
||||
}
|
||||
|
||||
bool is_enabled() const {
|
||||
return m_enabled;
|
||||
}
|
||||
std::string process_layer(const std::string &gcode);
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "Thumbnails.hpp"
|
||||
#include "../miniz_extension.hpp"
|
||||
#include "../format.hpp"
|
||||
|
||||
#include <qoi/qoi.h>
|
||||
#include <jpeglib.h>
|
||||
#include <jerror.h>
|
||||
#include<stdio.h>
|
||||
#include<string.h>
|
||||
// #include "colpic3.h"
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <string>
|
||||
namespace Slic3r::GCodeThumbnails {
|
||||
|
||||
using namespace std::literals;
|
||||
@@ -30,76 +30,14 @@ struct CompressedQOI : CompressedImageBuffer
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
//B3
|
||||
std::string compress_qidi_thumbnail_png(const ThumbnailData &data)
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_png(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 = ColPic_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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_jpg(const ThumbnailData& data)
|
||||
{
|
||||
// Take vector of RGBA pixels and flip the image vertically
|
||||
@@ -169,14 +107,6 @@ std::unique_ptr<CompressedImageBuffer> compress_thumbnail_qoi(const ThumbnailDat
|
||||
return out;
|
||||
}
|
||||
|
||||
//B3
|
||||
std::string compress_qidi_thumbnail(const ThumbnailData& data,
|
||||
GCodeThumbnailsFormat format)
|
||||
{
|
||||
return compress_qidi_thumbnail_png(data);
|
||||
}
|
||||
|
||||
//B3
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
@@ -189,249 +119,85 @@ std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &d
|
||||
return compress_thumbnail_qoi(data);
|
||||
}
|
||||
}
|
||||
static void colmemmove(U8 *dec, U8 *src, int lenth)
|
||||
|
||||
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext /*= "PNG"sv*/)
|
||||
{
|
||||
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(U8 *dec, U8 *src, int lenth)
|
||||
{
|
||||
while (lenth > 0) {
|
||||
*(dec++) = *(src++);
|
||||
lenth--;
|
||||
}
|
||||
}
|
||||
static void colmemset(U8 *dec, U8 val, int lenth)
|
||||
{
|
||||
while (lenth > 0) {
|
||||
*(dec++) = val;
|
||||
lenth--;
|
||||
}
|
||||
}
|
||||
static void ADList0(U16 val, U16HEAD *listu16, int *listqty, int maxqty)
|
||||
{
|
||||
U8 A0;
|
||||
U8 A1;
|
||||
U8 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 = (U8) (val >> 11);
|
||||
A1 = (U8) ((val << 5) >> 10);
|
||||
A2 = (U8) ((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(U16 *fromcolor16,
|
||||
U16 *listu16,
|
||||
int listqty,
|
||||
int dotsqty,
|
||||
U8 * outputdata,
|
||||
int decMaxBytesize)
|
||||
{
|
||||
U8 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;
|
||||
if (thumbnails_string.empty())
|
||||
return {};
|
||||
|
||||
std::istringstream is(thumbnails_string);
|
||||
std::string point_str;
|
||||
|
||||
ThumbnailErrors errors;
|
||||
|
||||
// generate thumbnails data to process it
|
||||
|
||||
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;
|
||||
|
||||
// check validity of extention
|
||||
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;
|
||||
}
|
||||
}
|
||||
tid = (U8) (temp % 32);
|
||||
sid = (U8) (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] = (U8) 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] = (U8) dots;
|
||||
decindex++;
|
||||
}
|
||||
srcindex += dots;
|
||||
dotsqty -= dots;
|
||||
errors = enum_bitmask(errors | ThumbnailError::InvalidVal);
|
||||
}
|
||||
IL_END:
|
||||
return decindex;
|
||||
|
||||
return std::make_pair(std::move(thumbnails_list), errors);
|
||||
}
|
||||
static int ColPicEncode(U16 *fromcolor16,
|
||||
int picw,
|
||||
int pich,
|
||||
U8 * outputdata,
|
||||
int outputmaxtsize,
|
||||
int colorsmax)
|
||||
|
||||
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase& config)
|
||||
{
|
||||
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);
|
||||
}
|
||||
// ??? Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
|
||||
// ??? If "thumbnails_format" is not defined, export to PNG.
|
||||
|
||||
// generate thumbnails data to process it
|
||||
|
||||
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;
|
||||
if (const auto thumbnails_value = config.option<ConfigOptionString>("thumbnails"))
|
||||
return make_and_check_thumbnail_list(thumbnails_value->value);
|
||||
|
||||
return {};
|
||||
}
|
||||
int ColPic_EncodeStr(U16 *fromcolor16,
|
||||
int picw,
|
||||
int pich,
|
||||
U8 * outputdata,
|
||||
int outputmaxtsize,
|
||||
int colorsmax)
|
||||
|
||||
std::string get_error_string(const ThumbnailErrors& errors)
|
||||
{
|
||||
int qty = 0;
|
||||
int temp = 0;
|
||||
int strindex = 0;
|
||||
int hexindex = 0;
|
||||
U8 TempBytes[4];
|
||||
qty = ColPicEncode(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;
|
||||
std::string error_str;
|
||||
|
||||
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);
|
||||
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";
|
||||
|
||||
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;
|
||||
return error_str;
|
||||
}
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r::GCodeThumbnails
|
||||
|
||||
@@ -9,33 +9,18 @@
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include <LibBGCode/binarize/binarize.hpp>
|
||||
#include <boost/beast/core/detail/base64.hpp>
|
||||
|
||||
#include "DataType.h"
|
||||
#include "../libslic3r/enum_bitmask.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt };
|
||||
using ThumbnailErrors = enum_bitmask<ThumbnailError>;
|
||||
ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError);
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
|
||||
@@ -48,81 +33,43 @@ struct CompressedImageBuffer
|
||||
};
|
||||
|
||||
std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format);
|
||||
//B3
|
||||
std::string compress_qidi_thumbnail(
|
||||
const ThumbnailData &data, GCodeThumbnailsFormat format);
|
||||
int ColPic_EncodeStr(U16* fromcolor16, int picw, int pich, U8* outputdata, int outputmaxtsize, int colorsmax);
|
||||
|
||||
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);
|
||||
|
||||
std::string get_error_string(const ThumbnailErrors& errors);
|
||||
|
||||
|
||||
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
|
||||
inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<Vec2d> &sizes, GCodeThumbnailsFormat format, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
|
||||
inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>>& thumbnails_list, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
|
||||
{
|
||||
// Write thumbnails using base64 encoding
|
||||
if (thumbnail_cb != nullptr) {
|
||||
for (const auto& [format, size] : thumbnails_list) {
|
||||
static constexpr const size_t max_row_length = 78;
|
||||
// ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true });
|
||||
//B3
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, false, false, true });
|
||||
int count = 0;
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true });
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
if (data.is_valid()) {
|
||||
//B3
|
||||
switch (format) {
|
||||
case GCodeThumbnailsFormat::QIDI:
|
||||
{
|
||||
auto compressed = compress_qidi_thumbnail(data,
|
||||
format);
|
||||
if (count == 0) {
|
||||
output(
|
||||
(boost::format("\n\n;gimage:%s\n\n") % compressed)
|
||||
.str()
|
||||
.c_str());
|
||||
count++;
|
||||
break;
|
||||
} else {
|
||||
output(
|
||||
(boost::format("\n\n;simage:%s\n\n") % compressed)
|
||||
.str()
|
||||
.c_str());
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
case GCodeThumbnailsFormat::JPG:
|
||||
default: {
|
||||
auto compressed = compress_thumbnail(data, format);
|
||||
if (compressed->data && compressed->size) {
|
||||
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());
|
||||
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());
|
||||
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\n") % encoded).str().c_str());
|
||||
|
||||
output((boost::format("; %s end\n;\n") %
|
||||
compressed->tag())
|
||||
.str()
|
||||
.c_str());
|
||||
}
|
||||
}
|
||||
output((boost::format("; %s end\n;\n") % compressed->tag()).str().c_str());
|
||||
|
||||
}
|
||||
|
||||
@@ -130,6 +77,41 @@ inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ThrowIfCanceledCallback>
|
||||
inline void generate_binary_thumbnails(ThumbnailsGeneratorCallback& thumbnail_cb, std::vector<bgcode::binarize::ThumbnailBlock>& out_thumbnails,
|
||||
const std::vector<std::pair<GCodeThumbnailsFormat, Vec2d>> &thumbnails_list, ThrowIfCanceledCallback throw_if_canceled)
|
||||
{
|
||||
using namespace bgcode::core;
|
||||
using namespace bgcode::binarize;
|
||||
out_thumbnails.clear();
|
||||
assert(thumbnail_cb != nullptr);
|
||||
if (thumbnail_cb != nullptr) {
|
||||
for (const auto& [format, size] : thumbnails_list) {
|
||||
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ {size}, true, true, true, true });
|
||||
for (const ThumbnailData &data : thumbnails)
|
||||
if (data.is_valid()) {
|
||||
auto compressed = compress_thumbnail(data, format);
|
||||
if (compressed->data != nullptr && compressed->size > 0) {
|
||||
ThumbnailBlock& block = out_thumbnails.emplace_back(ThumbnailBlock());
|
||||
block.params.width = (uint16_t)data.width;
|
||||
block.params.height = (uint16_t)data.height;
|
||||
switch (format) {
|
||||
case GCodeThumbnailsFormat::PNG: { block.params.format = (uint16_t)EThumbnailFormat::PNG; break; }
|
||||
case GCodeThumbnailsFormat::JPG: { block.params.format = (uint16_t)EThumbnailFormat::JPG; break; }
|
||||
case GCodeThumbnailsFormat::QOI: { block.params.format = (uint16_t)EThumbnailFormat::QOI; break; }
|
||||
}
|
||||
block.data.resize(compressed->size);
|
||||
memcpy(block.data.data(), compressed->data, compressed->size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r::GCodeThumbnails
|
||||
|
||||
|
||||
@@ -474,6 +474,8 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
|
||||
|
||||
bool ToolOrdering::insert_wipe_tower_extruder()
|
||||
{
|
||||
if (!m_print_config_ptr->wipe_tower)
|
||||
return false;
|
||||
// In case that wipe_tower_extruder is set to non-zero, we must make sure that the extruder will be in the list.
|
||||
bool changed = false;
|
||||
if (m_print_config_ptr->wipe_tower_extruder != 0) {
|
||||
@@ -831,4 +833,18 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const
|
||||
}
|
||||
}
|
||||
|
||||
int ToolOrdering::toolchanges_count() const
|
||||
{
|
||||
std::vector<unsigned int> tools_in_order;
|
||||
for (const LayerTools& lt : m_layer_tools)
|
||||
tools_in_order.insert(tools_in_order.end(), lt.extruders.begin(), lt.extruders.end());
|
||||
assert(std::find(tools_in_order.begin(), tools_in_order.end(), (unsigned int)(-1)) == tools_in_order.end());
|
||||
for (size_t i=1; i<tools_in_order.size(); ++i)
|
||||
if (tools_in_order[i] == tools_in_order[i-1])
|
||||
tools_in_order[i-1] = (unsigned int)(-1);
|
||||
tools_in_order.erase(std::remove(tools_in_order.begin(), tools_in_order.end(), (unsigned int)(-1)), tools_in_order.end());
|
||||
if (tools_in_order.size() > 1 && tools_in_order.back() == tools_in_order[tools_in_order.size()-2])
|
||||
tools_in_order.pop_back();
|
||||
return std::max(0, int(tools_in_order.size())-1); // 5 tools = 4 toolchanges
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -161,6 +161,7 @@ public:
|
||||
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().wipe_tower_partitions > 0; }
|
||||
int toolchanges_count() const;
|
||||
|
||||
private:
|
||||
void initialize_layers(std::vector<coordf_t> &zs);
|
||||
|
||||
257
src/libslic3r/GCode/Wipe.cpp
Normal file
257
src/libslic3r/GCode/Wipe.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "Wipe.hpp"
|
||||
#include "../GCode.hpp"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
void Wipe::init(const PrintConfig &config, const std::vector<unsigned int> &extruders)
|
||||
{
|
||||
this->reset_path();
|
||||
|
||||
// Calculate maximum wipe length to accumulate by the wipe cache.
|
||||
// Paths longer than wipe_xy should never be needed for the wipe move.
|
||||
double wipe_xy = 0;
|
||||
const bool multimaterial = extruders.size() > 1;
|
||||
for (auto id : extruders)
|
||||
if (config.wipe.get_at(id)) {
|
||||
// Wipe length to extrusion ratio.
|
||||
const double xy_to_e = this->calc_xy_to_e_ratio(config, id);
|
||||
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length.get_at(id));
|
||||
if (multimaterial)
|
||||
wipe_xy = std::max(wipe_xy, xy_to_e * config.retract_length_toolchange.get_at(id));
|
||||
}
|
||||
|
||||
if (wipe_xy == 0)
|
||||
this->disable();
|
||||
else
|
||||
this->enable(wipe_xy);
|
||||
}
|
||||
|
||||
void Wipe::set_path(SmoothPath &&path, bool reversed)
|
||||
{
|
||||
this->reset_path();
|
||||
|
||||
if (this->enabled() && ! path.empty()) {
|
||||
if (reversed) {
|
||||
m_path = std::move(path.back().path);
|
||||
Geometry::ArcWelder::reverse(m_path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.rbegin()); len < m_wipe_len_max && it != path.rend(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.back().point);
|
||||
if (m_path.back().point != it->path.back().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.rbegin() + 1, it->path.rend());
|
||||
}
|
||||
} else {
|
||||
m_path = std::move(path.front().path);
|
||||
int64_t len = Geometry::ArcWelder::estimate_path_length(m_path);
|
||||
for (auto it = std::next(path.begin()); len < m_wipe_len_max && it != path.end(); ++ it) {
|
||||
if (it->path_attributes.role.is_bridge())
|
||||
break; // Do not perform a wipe on bridges.
|
||||
assert(it->path.size() >= 2);
|
||||
assert(m_path.back().point == it->path.front().point);
|
||||
if (m_path.back().point != it->path.front().point)
|
||||
// ExtrusionMultiPath is interrupted in some place. This should not really happen.
|
||||
break;
|
||||
len += Geometry::ArcWelder::estimate_path_length(it->path);
|
||||
m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(m_path.empty() || m_path.size() > 1);
|
||||
}
|
||||
|
||||
std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange)
|
||||
{
|
||||
std::string gcode;
|
||||
const Extruder &extruder = *gcodegen.writer().extruder();
|
||||
static constexpr const std::string_view wipe_retract_comment = "wipe and retract"sv;
|
||||
|
||||
// Remaining quantized retraction length.
|
||||
if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length());
|
||||
retract_length > 0 && this->has_path()) {
|
||||
// Delayed emitting of a wipe start tag.
|
||||
bool wiped = false;
|
||||
const double wipe_speed = this->calc_wipe_speed(gcodegen.writer().config);
|
||||
auto start_wipe = [&wiped, &gcode, &gcodegen, wipe_speed](){
|
||||
if (! wiped) {
|
||||
wiped = true;
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n";
|
||||
gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE"sv : ""sv);
|
||||
}
|
||||
};
|
||||
const double xy_to_e = this->calc_xy_to_e_ratio(gcodegen.writer().config, extruder.id());
|
||||
auto wipe_linear = [&gcode, &gcodegen, &retract_length, xy_to_e](const Vec2d &prev_quantized, Vec2d &p) {
|
||||
Vec2d p_quantized = GCodeFormatter::quantize(p);
|
||||
if (p_quantized == prev_quantized) {
|
||||
p = p_quantized;
|
||||
return false;
|
||||
}
|
||||
double segment_length = (p_quantized - prev_quantized).norm();
|
||||
// Quantize E axis as it is to be extruded as a whole segment.
|
||||
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
|
||||
bool done = false;
|
||||
if (dE > retract_length - EPSILON) {
|
||||
if (dE > retract_length + EPSILON)
|
||||
// Shorten the segment.
|
||||
p = GCodeFormatter::quantize(Vec2d(prev_quantized + (p - prev_quantized) * (retract_length / dE)));
|
||||
else
|
||||
p = p_quantized;
|
||||
dE = retract_length;
|
||||
done = true;
|
||||
} else
|
||||
p = p_quantized;
|
||||
gcode += gcodegen.writer().extrude_to_xy(p, -dE, wipe_retract_comment);
|
||||
retract_length -= dE;
|
||||
return done;
|
||||
};
|
||||
auto wipe_arc = [&gcode, &gcodegen, &retract_length, xy_to_e, &wipe_linear](
|
||||
const Vec2d &prev_quantized, Vec2d &p, double radius_in, const bool ccw) {
|
||||
Vec2d p_quantized = GCodeFormatter::quantize(p);
|
||||
if (p_quantized == prev_quantized) {
|
||||
p = p_quantized;
|
||||
return false;
|
||||
}
|
||||
// Use the exact radius for calculating the IJ values, no quantization.
|
||||
double radius = radius_in;
|
||||
if (radius == 0)
|
||||
// Degenerated arc after quantization. Process it as if it was a line segment.
|
||||
return wipe_linear(prev_quantized, p);
|
||||
Vec2d center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius), ccw);
|
||||
float angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p_quantized.cast<double>(), double(radius));
|
||||
assert(angle > 0);
|
||||
double segment_length = angle * std::abs(radius);
|
||||
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
|
||||
bool done = false;
|
||||
if (dE > retract_length - EPSILON) {
|
||||
if (dE > retract_length + EPSILON) {
|
||||
// Shorten the segment. Recalculate the arc from the unquantized end coordinate.
|
||||
center = Geometry::ArcWelder::arc_center(prev_quantized.cast<double>(), p.cast<double>(), double(radius), ccw);
|
||||
angle = Geometry::ArcWelder::arc_angle(prev_quantized.cast<double>(), p.cast<double>(), double(radius));
|
||||
segment_length = angle * std::abs(radius);
|
||||
dE = xy_to_e * segment_length;
|
||||
p = GCodeFormatter::quantize(
|
||||
Vec2d(center + Eigen::Rotation2D((ccw ? angle : -angle) * (retract_length / dE)) * (prev_quantized - center)));
|
||||
} else
|
||||
p = p_quantized;
|
||||
dE = retract_length;
|
||||
done = true;
|
||||
} else
|
||||
p = p_quantized;
|
||||
assert(dE > 0);
|
||||
{
|
||||
// Calculate quantized IJ circle center offset.
|
||||
Vec2d ij = GCodeFormatter::quantize(Vec2d(center - prev_quantized));
|
||||
if (ij == Vec2d::Zero())
|
||||
// Degenerated arc after quantization. Process it as if it was a line segment.
|
||||
return wipe_linear(prev_quantized, p);
|
||||
// The arc is valid.
|
||||
gcode += gcodegen.writer().extrude_to_xy_G2G3IJ(
|
||||
p, ij, ccw, -dE, wipe_retract_comment);
|
||||
}
|
||||
retract_length -= dE;
|
||||
return done;
|
||||
};
|
||||
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
|
||||
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
|
||||
auto it = this->path().begin();
|
||||
Vec2d p = gcodegen.point_to_gcode(it->point + m_offset);
|
||||
++ it;
|
||||
bool done = false;
|
||||
if (p != prev) {
|
||||
start_wipe();
|
||||
done = wipe_linear(prev, p);
|
||||
}
|
||||
if (! done) {
|
||||
prev = p;
|
||||
auto end = this->path().end();
|
||||
for (; it != end && ! done; ++ it) {
|
||||
p = gcodegen.point_to_gcode(it->point + m_offset);
|
||||
if (p != prev) {
|
||||
start_wipe();
|
||||
if (it->linear() ?
|
||||
wipe_linear(prev, p) :
|
||||
wipe_arc(prev, p, unscaled<double>(it->radius), it->ccw()))
|
||||
break;
|
||||
prev = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (wiped) {
|
||||
// add tag for processor
|
||||
assert(p == GCodeFormatter::quantize(p));
|
||||
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
|
||||
gcodegen.set_last_pos(gcodegen.gcode_to_point(p));
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent wiping again on the same path.
|
||||
this->reset_path();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Make a little move inwards before leaving loop after path was extruded,
|
||||
// thus the current extruder position is at the end of a path and the path
|
||||
// may not be closed in case the loop was clipped to hide a seam.
|
||||
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length)
|
||||
{
|
||||
assert(! path.empty());
|
||||
assert(path.front().path.size() >= 2);
|
||||
assert(path.back().path.size() >= 2);
|
||||
|
||||
// Heuristics for estimating whether there is a chance that the wipe move will fit inside a small perimeter
|
||||
// or that the wipe move direction could be calculated with reasonable accuracy.
|
||||
if (longer_than(path, 2.5 * wipe_length)) {
|
||||
// The print head will be moved away from path end inside the island.
|
||||
Point p_current = path.back().path.back().point;
|
||||
Point p_next = path.front().path.front().point;
|
||||
Point p_prev;
|
||||
{
|
||||
// Is the seam hiding gap large enough already?
|
||||
double l = wipe_length - (p_next - p_current).cast<double>().norm();
|
||||
if (l > 0) {
|
||||
// Not yet.
|
||||
std::optional<Point> n = sample_path_point_at_distance_from_start(path, l);
|
||||
assert(n);
|
||||
if (! n)
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
if (std::optional<Point> p = sample_path_point_at_distance_from_end(path, wipe_length); p)
|
||||
p_prev = *p;
|
||||
else
|
||||
// Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above.
|
||||
return {};
|
||||
}
|
||||
// Detect angle between last and first segment.
|
||||
// The side depends on the original winding order of the polygon (left for contours, right for holes).
|
||||
double angle_inside = angle(p_next - p_current, p_prev - p_current);
|
||||
assert(angle_inside >= -M_PI && angle_inside <= M_PI);
|
||||
// 3rd of this angle will be taken, thus make the angle monotonic before interpolation.
|
||||
if (is_hole) {
|
||||
if (angle_inside > 0)
|
||||
angle_inside -= 2.0 * M_PI;
|
||||
} else {
|
||||
if (angle_inside < 0)
|
||||
angle_inside += 2.0 * M_PI;
|
||||
}
|
||||
// Rotate the forward segment inside by 1/3 of the wedge angle.
|
||||
auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast<double>().normalized();
|
||||
return std::make_optional<Point>(p_current + (v_rotated * wipe_length).cast<coord_t>());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
||||
73
src/libslic3r/GCode/Wipe.hpp
Normal file
73
src/libslic3r/GCode/Wipe.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef slic3r_GCode_Wipe_hpp_
|
||||
#define slic3r_GCode_Wipe_hpp_
|
||||
|
||||
#include "SmoothPath.hpp"
|
||||
|
||||
#include "../Geometry/ArcWelder.hpp"
|
||||
#include "../Point.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeGenerator;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
class Wipe {
|
||||
public:
|
||||
using Path = Slic3r::Geometry::ArcWelder::Path;
|
||||
|
||||
Wipe() = default;
|
||||
|
||||
void init(const PrintConfig &config, const std::vector<unsigned int> &extruders);
|
||||
void enable(double wipe_len_max) { m_enabled = true; m_wipe_len_max = wipe_len_max; }
|
||||
void disable() { m_enabled = false; }
|
||||
bool enabled() const { return m_enabled; }
|
||||
|
||||
const Path& path() const { return m_path; }
|
||||
bool has_path() const { assert(m_path.empty() || m_path.size() > 1); return ! m_path.empty(); }
|
||||
void reset_path() { m_path.clear(); m_offset = Point::Zero(); }
|
||||
void set_path(const Path &path) {
|
||||
assert(path.empty() || path.size() > 1);
|
||||
this->reset_path();
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = path;
|
||||
}
|
||||
void set_path(Path &&path) {
|
||||
assert(path.empty() || path.size() > 1);
|
||||
this->reset_path();
|
||||
if (this->enabled() && path.size() > 1)
|
||||
m_path = std::move(path);
|
||||
}
|
||||
void set_path(SmoothPath &&path, bool reversed);
|
||||
void offset_path(const Point &v) { m_offset += v; }
|
||||
|
||||
std::string wipe(GCodeGenerator &gcodegen, bool toolchange);
|
||||
|
||||
// Reduce feedrate a bit; travel speed is often too high to move on existing material.
|
||||
// Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
|
||||
static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; }
|
||||
// Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
|
||||
// due to rounding (TODO: test and/or better math for this).
|
||||
static double calc_xy_to_e_ratio(const GCodeConfig &config, unsigned int extruder_id)
|
||||
{ return 0.95 * floor(config.retract_speed.get_at(extruder_id) + 0.5) / calc_wipe_speed(config); }
|
||||
|
||||
private:
|
||||
bool m_enabled{ false };
|
||||
// Maximum length of a path to accumulate. Only wipes shorter than this threshold will be requested.
|
||||
double m_wipe_len_max{ 0. };
|
||||
Path m_path;
|
||||
// Offset from m_path to the current PrintObject active.
|
||||
Point m_offset{ Point::Zero() };
|
||||
};
|
||||
|
||||
// Make a little move inwards before leaving loop.
|
||||
std::optional<Point> wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length);
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_Wipe_hpp_
|
||||
@@ -530,6 +530,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default
|
||||
m_no_sparse_layers(config.wipe_tower_no_sparse_layers),
|
||||
m_gcode_flavor(config.gcode_flavor),
|
||||
m_travel_speed(config.travel_speed),
|
||||
m_travel_speed_z(config.travel_speed_z),
|
||||
m_infill_speed(default_region_config.infill_speed),
|
||||
m_perimeter_speed(default_region_config.perimeter_speed),
|
||||
m_current_tool(initial_tool),
|
||||
@@ -786,12 +787,13 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool)
|
||||
.set_initial_tool(m_current_tool)
|
||||
.set_y_shift(m_y_shift + (tool!=(unsigned int)(-1) && (m_current_shape == SHAPE_REVERSED) ? m_layer_info->depth - m_layer_info->toolchanges_depth(): 0.f))
|
||||
.append(";--------------------\n"
|
||||
"; CP TOOLCHANGE START\n")
|
||||
.comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based
|
||||
"; CP TOOLCHANGE START\n");
|
||||
|
||||
if (tool != (unsigned)(-1))
|
||||
if (tool != (unsigned)(-1)) {
|
||||
writer.comment_with_value(" toolchange #", m_num_tool_changes + 1); // the number is zero-based
|
||||
writer.append(std::string("; material : " + (m_current_tool < m_filpar.size() ? m_filpar[m_current_tool].material : "(NONE)") + " -> " + m_filpar[tool].material + "\n").c_str())
|
||||
.append(";--------------------\n");
|
||||
}
|
||||
|
||||
writer.speed_override_backup();
|
||||
writer.speed_override(100);
|
||||
@@ -1001,7 +1003,7 @@ void WipeTower::toolchange_Change(
|
||||
// This is where we want to place the custom gcodes. We will use placeholders for this.
|
||||
// These will be substituted by the actual gcodes when the gcode is generated.
|
||||
//writer.append("[end_filament_gcode]\n");
|
||||
writer.append("[toolchange_gcode]\n");
|
||||
writer.append("[toolchange_gcode_from_wipe_tower_generator]\n");
|
||||
|
||||
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
|
||||
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
|
||||
@@ -1010,7 +1012,8 @@ void WipeTower::toolchange_Change(
|
||||
writer.feedrate(m_travel_speed * 60.f) // see https://github.com/qidi3d/QIDISlicer/issues/5483
|
||||
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(current_pos.x())
|
||||
+ " Y" + Slic3r::float_to_string_decimal_point(current_pos.y())
|
||||
+ never_skip_tag() + "\n");
|
||||
+ never_skip_tag() + "\n"
|
||||
);
|
||||
writer.append("[deretraction_from_wipe_tower_generator]");
|
||||
|
||||
// The toolchange Tn command will be inserted later, only in case that the user does
|
||||
@@ -1479,6 +1482,10 @@ void WipeTower::save_on_last_wipe()
|
||||
// Which toolchange will finish_layer extrusions be subtracted from?
|
||||
int idx = first_toolchange_to_nonsoluble(m_layer_info->tool_changes);
|
||||
|
||||
if (idx == -1) {
|
||||
// In this case, finish_layer will be called at the very beginning.
|
||||
finish_layer().total_extrusion_length_in_plane();
|
||||
}
|
||||
for (int i=0; i<int(m_layer_info->tool_changes.size()); ++i) {
|
||||
auto& toolchange = m_layer_info->tool_changes[i];
|
||||
tool_change(toolchange.new_tool);
|
||||
@@ -1490,9 +1497,9 @@ void WipeTower::save_on_last_wipe()
|
||||
m_perimeter_width, m_layer_info->height) - toolchange.first_wipe_line - length_to_save;
|
||||
|
||||
length_to_wipe = std::max(length_to_wipe,0.f);
|
||||
float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) ) * m_extra_spacing;
|
||||
float depth_to_wipe = m_perimeter_width * (std::floor(length_to_wipe/width) + ( length_to_wipe > 0.f ? 1.f : 0.f ) );
|
||||
|
||||
toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe;
|
||||
toolchange.required_depth = (toolchange.ramming_depth + depth_to_wipe) * m_extra_spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1558,9 +1565,9 @@ void WipeTower::generate(std::vector<std::vector<WipeTower::ToolChangeResult>> &
|
||||
|
||||
m_old_temperature = -1; // reset last temperature written in the gcode
|
||||
|
||||
std::vector<WipeTower::ToolChangeResult> layer_result;
|
||||
for (const WipeTower::WipeTowerInfo& layer : m_plan)
|
||||
{
|
||||
std::vector<WipeTower::ToolChangeResult> layer_result;
|
||||
set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z);
|
||||
m_internal_rotation += 180.f;
|
||||
|
||||
|
||||
@@ -275,6 +275,7 @@ private:
|
||||
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_travel_speed_z = 0.f;
|
||||
float m_infill_speed = 0.f;
|
||||
float m_perimeter_speed = 0.f;
|
||||
float m_first_layer_speed = 0.f;
|
||||
|
||||
257
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
257
src/libslic3r/GCode/WipeTowerIntegration.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "WipeTowerIntegration.hpp"
|
||||
|
||||
#include "../GCode.hpp"
|
||||
#include "../libslic3r.h"
|
||||
|
||||
#include "boost/algorithm/string/replace.hpp"
|
||||
|
||||
namespace Slic3r::GCode {
|
||||
|
||||
static inline Point wipe_tower_point_to_object_point(GCodeGenerator &gcodegen, const Vec2f& wipe_tower_pt)
|
||||
{
|
||||
return Point(scale_(wipe_tower_pt.x() - gcodegen.origin()(0)), scale_(wipe_tower_pt.y() - gcodegen.origin()(1)));
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const
|
||||
{
|
||||
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
|
||||
throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
|
||||
std::string gcode;
|
||||
|
||||
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines)
|
||||
// We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position
|
||||
float alpha = m_wipe_tower_rotation / 180.f * float(M_PI);
|
||||
|
||||
auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f {
|
||||
Vec2f out = Eigen::Rotation2Df(alpha) * pt;
|
||||
out += m_wipe_tower_pos;
|
||||
return out;
|
||||
};
|
||||
|
||||
Vec2f start_pos = tcr.start_pos;
|
||||
Vec2f end_pos = tcr.end_pos;
|
||||
if (! tcr.priming) {
|
||||
start_pos = transform_wt_pt(start_pos);
|
||||
end_pos = transform_wt_pt(end_pos);
|
||||
}
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
|
||||
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
||||
|
||||
double current_z = gcodegen.writer().get_position().z();
|
||||
gcode += gcodegen.writer().travel_to_z(current_z);
|
||||
|
||||
if (z == -1.) // in case no specific z was provided, print at current_z pos
|
||||
z = current_z;
|
||||
|
||||
const bool needs_toolchange = gcodegen.writer().need_toolchange(new_extruder_id);
|
||||
const bool will_go_down = ! is_approx(z, current_z);
|
||||
const bool is_ramming = (gcodegen.config().single_extruder_multi_material)
|
||||
|| (! gcodegen.config().single_extruder_multi_material && gcodegen.config().filament_multitool_ramming.get_at(tcr.initial_tool));
|
||||
const bool should_travel_to_tower = ! tcr.priming
|
||||
&& (tcr.force_travel // wipe tower says so
|
||||
|| ! needs_toolchange // this is just finishing the tower with no toolchange
|
||||
|| is_ramming
|
||||
|| will_go_down); // don't dig into the print
|
||||
if (should_travel_to_tower) {
|
||||
gcode += gcodegen.retract_and_wipe();
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
gcode += gcodegen.travel_to(
|
||||
wipe_tower_point_to_object_point(gcodegen, start_pos),
|
||||
ExtrusionRole::Mixed,
|
||||
"Travel to a Wipe Tower");
|
||||
gcode += gcodegen.unretract();
|
||||
} else {
|
||||
// When this is multiextruder printer without any ramming, we can just change
|
||||
// the tool without travelling to the tower.
|
||||
}
|
||||
|
||||
if (will_go_down) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
std::string toolchange_gcode_str;
|
||||
std::string deretraction_str;
|
||||
if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) {
|
||||
if (is_ramming)
|
||||
gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines.
|
||||
toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z
|
||||
if (gcodegen.config().wipe_tower)
|
||||
deretraction_str += gcodegen.writer().get_travel_to_z_gcode(z, "restore layer Z");
|
||||
deretraction_str += gcodegen.unretract();
|
||||
|
||||
}
|
||||
assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n');
|
||||
assert(deretraction_str.empty() || deretraction_str.back() == '\n');
|
||||
|
||||
// Insert the toolchange and deretraction gcode into the generated gcode.
|
||||
boost::replace_first(tcr_rotated_gcode, "[toolchange_gcode_from_wipe_tower_generator]", toolchange_gcode_str);
|
||||
boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str);
|
||||
std::string tcr_gcode;
|
||||
unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode);
|
||||
gcode += tcr_gcode;
|
||||
|
||||
// A phony move to the end position at the wipe tower.
|
||||
gcodegen.writer().travel_to_xy(end_pos.cast<double>());
|
||||
gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos));
|
||||
if (!is_approx(z, current_z)) {
|
||||
gcode += gcodegen.writer().retract();
|
||||
gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer.");
|
||||
gcode += gcodegen.writer().unretract();
|
||||
}
|
||||
|
||||
else {
|
||||
// Prepare a future wipe.
|
||||
// Convert to a smooth path.
|
||||
Geometry::ArcWelder::Path path;
|
||||
path.reserve(tcr.wipe_path.size());
|
||||
std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path),
|
||||
[&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) {
|
||||
return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) };
|
||||
});
|
||||
// Pass to the wipe cache.
|
||||
gcodegen.m_wipe.set_path(std::move(path));
|
||||
}
|
||||
|
||||
// Let the planner know we are traveling between objects.
|
||||
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
|
||||
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
|
||||
std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
|
||||
{
|
||||
Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
|
||||
|
||||
std::istringstream gcode_str(tcr.gcode);
|
||||
std::string gcode_out;
|
||||
std::string line;
|
||||
Vec2f pos = tcr.start_pos;
|
||||
Vec2f transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
|
||||
Vec2f old_pos(-1000.1f, -1000.1f);
|
||||
|
||||
while (gcode_str) {
|
||||
std::getline(gcode_str, line); // we read the gcode line by line
|
||||
|
||||
// All G1 commands should be translated and rotated. X and Y coords are
|
||||
// only pushed to the output when they differ from last time.
|
||||
// WT generator can override this by appending the never_skip_tag
|
||||
if (boost::starts_with(line, "G1 ")) {
|
||||
bool never_skip = false;
|
||||
auto it = line.find(WipeTower::never_skip_tag());
|
||||
if (it != std::string::npos) {
|
||||
// remove the tag and remember we saw it
|
||||
never_skip = true;
|
||||
line.erase(it, it + WipeTower::never_skip_tag().size());
|
||||
}
|
||||
std::ostringstream line_out;
|
||||
std::istringstream line_str(line);
|
||||
line_str >> std::noskipws; // don't skip whitespace
|
||||
char ch = 0;
|
||||
line_str >> ch >> ch; // read the "G1"
|
||||
while (line_str >> ch) {
|
||||
if (ch == 'X' || ch == 'Y')
|
||||
line_str >> (ch == 'X' ? pos.x() : pos.y());
|
||||
else
|
||||
line_out << ch;
|
||||
}
|
||||
|
||||
line = line_out.str();
|
||||
boost::trim(line); // Remove leading and trailing spaces.
|
||||
|
||||
transformed_pos = Eigen::Rotation2Df(angle) * pos + translation;
|
||||
|
||||
if (transformed_pos != old_pos || never_skip || ! line.empty()) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3) << "G1";
|
||||
if (transformed_pos.x() != old_pos.x() || never_skip)
|
||||
oss << " X" << transformed_pos.x() - extruder_offset.x();
|
||||
if (transformed_pos.y() != old_pos.y() || never_skip)
|
||||
oss << " Y" << transformed_pos.y() - extruder_offset.y();
|
||||
if (! line.empty())
|
||||
oss << " ";
|
||||
line = oss.str() + line;
|
||||
old_pos = transformed_pos;
|
||||
}
|
||||
}
|
||||
|
||||
gcode_out += line + "\n";
|
||||
|
||||
// If this was a toolchange command, we should change current extruder offset
|
||||
if (line == "[toolchange_gcode_from_wipe_tower_generator]") {
|
||||
extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
|
||||
|
||||
// If the extruder offset changed, add an extra move so everything is continuous
|
||||
if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3)
|
||||
<< "G1 X" << transformed_pos.x() - extruder_offset.x()
|
||||
<< " Y" << transformed_pos.y() - extruder_offset.y()
|
||||
<< "\n";
|
||||
gcode_out += oss.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode_out;
|
||||
}
|
||||
|
||||
|
||||
std::string WipeTowerIntegration::prime(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
for (const WipeTower::ToolChangeResult& tcr : m_priming) {
|
||||
if (! tcr.extrusions.empty())
|
||||
gcode += append_tcr(gcodegen, tcr, tcr.new_tool);
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
std::string WipeTowerIntegration::tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer)
|
||||
{
|
||||
std::string gcode;
|
||||
assert(m_layer_idx >= 0);
|
||||
if (gcodegen.writer().need_toolchange(extruder_id) || finish_layer) {
|
||||
if (m_layer_idx < (int)m_tool_changes.size()) {
|
||||
if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size()))
|
||||
throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer.");
|
||||
|
||||
// Calculate where the wipe tower layer will be printed. -1 means that print z will not change,
|
||||
// resulting in a wipe tower with sparse layers.
|
||||
double wipe_tower_z = -1;
|
||||
bool ignore_sparse = false;
|
||||
if (gcodegen.config().wipe_tower_no_sparse_layers.value) {
|
||||
wipe_tower_z = m_last_wipe_tower_print_z;
|
||||
ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && m_layer_idx != 0);
|
||||
if (m_tool_change_idx == 0 && !ignore_sparse)
|
||||
wipe_tower_z = m_last_wipe_tower_print_z + m_tool_changes[m_layer_idx].front().layer_height;
|
||||
}
|
||||
|
||||
if (!ignore_sparse) {
|
||||
gcode += append_tcr(gcodegen, m_tool_changes[m_layer_idx][m_tool_change_idx++], extruder_id, wipe_tower_z);
|
||||
m_last_wipe_tower_print_z = wipe_tower_z;
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// Print is finished. Now it remains to unload the filament safely with ramming over the wipe tower.
|
||||
std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen)
|
||||
{
|
||||
std::string gcode;
|
||||
if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON)
|
||||
gcode += gcodegen.generate_travel_gcode(
|
||||
{{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}},
|
||||
"move to safe place for purging"
|
||||
);
|
||||
gcode += append_tcr(gcodegen, m_final_purge, -1);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GCode
|
||||
65
src/libslic3r/GCode/WipeTowerIntegration.hpp
Normal file
65
src/libslic3r/GCode/WipeTowerIntegration.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#ifndef slic3r_GCode_WipeTowerIntegration_hpp_
|
||||
#define slic3r_GCode_WipeTowerIntegration_hpp_
|
||||
|
||||
#include "WipeTower.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GCodeGenerator;
|
||||
|
||||
namespace GCode {
|
||||
|
||||
class WipeTowerIntegration {
|
||||
public:
|
||||
WipeTowerIntegration(
|
||||
const PrintConfig &print_config,
|
||||
const std::vector<WipeTower::ToolChangeResult> &priming,
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &tool_changes,
|
||||
const WipeTower::ToolChangeResult &final_purge) :
|
||||
m_left(/*float(print_config.wipe_tower_x.value)*/ 0.f),
|
||||
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
|
||||
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
|
||||
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
|
||||
m_extruder_offsets(print_config.extruder_offset.values),
|
||||
m_priming(priming),
|
||||
m_tool_changes(tool_changes),
|
||||
m_final_purge(final_purge),
|
||||
m_layer_idx(-1),
|
||||
m_tool_change_idx(0)
|
||||
{}
|
||||
|
||||
std::string prime(GCodeGenerator &gcodegen);
|
||||
void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; }
|
||||
std::string tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer);
|
||||
std::string finalize(GCodeGenerator &gcodegen);
|
||||
std::vector<float> used_filament_length() const;
|
||||
|
||||
private:
|
||||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
|
||||
// Left / right edges of the wipe tower, for the planning of wipe moves.
|
||||
const float m_left;
|
||||
const float m_right;
|
||||
const Vec2f m_wipe_tower_pos;
|
||||
const float m_wipe_tower_rotation;
|
||||
const std::vector<Vec2d> m_extruder_offsets;
|
||||
|
||||
// Reference to cached values at the Printer class.
|
||||
const std::vector<WipeTower::ToolChangeResult> &m_priming;
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
|
||||
const WipeTower::ToolChangeResult &m_final_purge;
|
||||
// Current layer index.
|
||||
int m_layer_idx;
|
||||
int m_tool_change_idx;
|
||||
double m_last_wipe_tower_print_z = 0.f;
|
||||
};
|
||||
|
||||
} // namespace GCode
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCode_WipeTowerIntegration_hpp_
|
||||
Reference in New Issue
Block a user