PRUSA 2.7.0

This commit is contained in:
sunsets
2023-12-27 18:02:35 +08:00
parent b33112327f
commit 0a3c63dcb1
488 changed files with 92371 additions and 29443 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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> &current_pos) const
std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::array<float, 5> &current_pos) const
{
std::vector<PerExtruderAdjustments> per_extruder_adjustments(m_extruder_ids.size());
std::vector<size_t> map_extruder_to_per_extruder_adjustment(m_num_extruders, 0);
@@ -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)

View File

@@ -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> &current_pos) const;
std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::array<float, 5> &current_pos) const;
float calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
// Returns the adjusted G-code.
@@ -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;

View File

@@ -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

View 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

View File

@@ -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 &current = 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_

View File

@@ -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)];

View File

@@ -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);
};

View 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

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

View 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

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

View File

@@ -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);

View File

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

View File

@@ -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);

View 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 &params)
{
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 &params)
{
for (const ExtrusionPath &path : multi_path.paths)
this->interpolate_add(path, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionLoop &loop, const InterpolationParameters &params)
{
for (const ExtrusionPath &path : loop.paths)
this->interpolate_add(path, params);
}
void SmoothPathCache::interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params)
{
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

View 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 &params);
void interpolate_add(const ExtrusionMultiPath &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionLoop &ee, const InterpolationParameters &params);
void interpolate_add(const ExtrusionEntityCollection &eec, const InterpolationParameters &params);
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_

View File

@@ -19,6 +19,9 @@ public:
m_enabled = en;
}
bool is_enabled() const {
return m_enabled;
}
std::string process_layer(const std::string &gcode);
private:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View 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

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

View File

@@ -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;

View File

@@ -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;

View 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

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