G-code analyser, first draft. The G-code analyser will be used

for advanced visualization of the printing paths, including
the extrusion types.
This commit is contained in:
bubnikv 2016-09-26 12:37:54 +02:00
parent 565146d9d1
commit 03b1312f2d
2 changed files with 478 additions and 0 deletions

View file

@ -0,0 +1,326 @@
#include <memory.h>
#include <string.h>
#include <float.h>
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "Analyzer.hpp"
namespace Slic3r {
void GCodeMovesDB::reset()
{
for (size_t i = 0; i < m_layers.size(); ++ i)
delete m_layers[i];
m_layers.clear();
}
GCodeAnalyzer::GCodeAnalyzer(const Slic3r::GCodeConfig *config) :
m_config(config)
{
reset();
m_moves = new GCodeMovesDB();
}
GCodeAnalyzer::~GCodeAnalyzer()
{
delete m_moves;
}
void GCodeAnalyzer::reset()
{
output_buffer.clear();
output_buffer_length = 0;
m_current_extruder = 0;
// Zero the position of the XYZE axes + the current feed
memset(m_current_pos, 0, sizeof(float) * 5);
m_current_extrusion_role = erNone;
m_current_extrusion_width = 0;
m_current_extrusion_height = 0;
// Expect the first command to fill the nozzle (deretract).
m_retracted = true;
m_moves->reset();
}
const char* GCodeAnalyzer::process(const char *szGCode, bool flush)
{
// Reset length of the output_buffer.
output_buffer_length = 0;
if (szGCode != 0) {
const char *p = szGCode;
while (*p != 0) {
// Find end of the line.
const char *endl = p;
// Slic3r always generates end of lines in a Unix style.
for (; *endl != 0 && *endl != '\n'; ++ endl) ;
// Process a G-code line, store it into the provided GCodeLine object.
bool should_output = process_line(p, endl - p);
if (*endl == '\n')
++ endl;
if (should_output)
push_to_output(p, endl - p);
p = endl;
}
}
return output_buffer.data();
}
// Is a white space?
static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; }
// Is it an end of line? Consider a comment to be an end of line as well.
static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; };
// Is it a white space or end of line?
static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); };
// Eat whitespaces.
static void eatws(const char *&line)
{
while (is_ws(*line))
++ line;
}
// Parse an int starting at the current position of a line.
// If succeeded, the line pointer is advanced.
static inline int parse_int(const char *&line)
{
char *endptr = NULL;
long result = strtol(line, &endptr, 10);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("GCodePressureEqualizer: Error parsing an int");
line = endptr;
return int(result);
};
// Parse an int starting at the current position of a line.
// If succeeded, the line pointer is advanced.
static inline float parse_float(const char *&line)
{
char *endptr = NULL;
float result = strtof(line, &endptr);
if (endptr == NULL || !is_ws_or_eol(*endptr))
throw std::runtime_error("GCodePressureEqualizer: Error parsing a float");
line = endptr;
return result;
};
#define EXTRUSION_ROLE_TAG ";_EXTRUSION_ROLE:"
bool GCodeAnalyzer::process_line(const char *line, const size_t len)
{
if (strncmp(line, EXTRUSION_ROLE_TAG, strlen(EXTRUSION_ROLE_TAG)) == 0) {
line += strlen(EXTRUSION_ROLE_TAG);
int role = atoi(line);
this->m_current_extrusion_role = ExtrusionRole(role);
return false;
}
/*
// Set the type, copy the line to the buffer.
buf.type = GCODE_MOVE_TYPE_OTHER;
buf.modified = false;
if (buf.raw.size() < len + 1)
buf.raw.assign(line, line + len + 1);
else
memcpy(buf.raw.data(), line, len);
buf.raw[len] = 0;
buf.raw_length = len;
memcpy(buf.pos_start, m_current_pos, sizeof(float)*5);
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
memset(buf.pos_provided, 0, 5);
buf.volumetric_extrusion_rate = 0.f;
buf.volumetric_extrusion_rate_start = 0.f;
buf.volumetric_extrusion_rate_end = 0.f;
buf.max_volumetric_extrusion_rate_slope_positive = 0.f;
buf.max_volumetric_extrusion_rate_slope_negative = 0.f;
buf.extrusion_role = m_current_extrusion_role;
// Parse the G-code line, store the result into the buf.
switch (toupper(*line ++)) {
case 'G': {
int gcode = parse_int(line);
eatws(line);
switch (gcode) {
case 0:
case 1:
{
// G0, G1: A FFF 3D printer does not make a difference between the two.
float new_pos[5];
memcpy(new_pos, m_current_pos, sizeof(float)*5);
bool changed[5] = { false, false, false, false, false };
while (!is_eol(*line)) {
char axis = toupper(*line++);
int i = -1;
switch (axis) {
case 'X':
case 'Y':
case 'Z':
i = axis - 'X';
break;
case 'E':
i = 3;
break;
case 'F':
i = 4;
break;
default:
assert(false);
}
if (i == -1)
throw std::runtime_error(std::string("GCodePressureEqualizer: Invalid axis for G0/G1: ") + axis);
buf.pos_provided[i] = true;
new_pos[i] = parse_float(line);
if (i == 3 && m_config->use_relative_e_distances.value)
new_pos[i] += m_current_pos[i];
changed[i] = new_pos[i] != m_current_pos[i];
eatws(line);
}
if (changed[3]) {
// Extrusion, retract or unretract.
float diff = new_pos[3] - m_current_pos[3];
if (diff < 0) {
buf.type = GCODE_MOVE_TYPE_RETRACT;
m_retracted = true;
} else if (! changed[0] && ! changed[1] && ! changed[2]) {
// assert(m_retracted);
buf.type = GCODE_MOVE_TYPE_UNRETRACT;
m_retracted = false;
} else {
assert(changed[0] || changed[1]);
// Moving in XY plane.
buf.type = GCODE_MOVE_TYPE_EXTRUDE;
// Calculate the volumetric extrusion rate.
float diff[4];
for (size_t i = 0; i < 4; ++ i)
diff[i] = new_pos[i] - m_current_pos[i];
// volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min]
float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2];
float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2);
buf.volumetric_extrusion_rate = rate;
buf.volumetric_extrusion_rate_start = rate;
buf.volumetric_extrusion_rate_end = rate;
m_stat.update(rate, sqrt(len2));
if (rate < 10.f) {
printf("Extremely low flow rate: %f\n", rate);
}
}
} else if (changed[0] || changed[1] || changed[2]) {
// Moving without extrusion.
buf.type = GCODE_MOVE_TYPE_MOVE;
}
memcpy(m_current_pos, new_pos, sizeof(float) * 5);
break;
}
case 92:
{
// G92 : Set Position
// Set a logical coordinate position to a new value without actually moving the machine motors.
// Which axes to set?
bool set = false;
while (!is_eol(*line)) {
char axis = toupper(*line++);
switch (axis) {
case 'X':
case 'Y':
case 'Z':
m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
set = true;
break;
case 'E':
m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line) : 0.f;
set = true;
break;
default:
throw std::runtime_error(std::string("GCodePressureEqualizer: Incorrect axis in a G92 G-code: ") + axis);
}
eatws(line);
}
assert(set);
break;
}
case 10:
case 22:
// Firmware retract.
buf.type = GCODE_MOVE_TYPE_RETRACT;
m_retracted = true;
break;
case 11:
case 23:
// Firmware unretract.
buf.type = GCODE_MOVE_TYPE_UNRETRACT;
m_retracted = false;
break;
default:
// Ignore the rest.
break;
}
break;
}
case 'M': {
int mcode = parse_int(line);
eatws(line);
switch (mcode) {
default:
// Ignore the rest of the M-codes.
break;
}
break;
}
case 'T':
{
// Activate an extruder head.
int new_extruder = parse_int(line);
if (new_extruder != m_current_extruder) {
m_current_extruder = new_extruder;
m_retracted = true;
buf.type = GCODE_MOVE_TYPE_TOOL_CHANGE;
} else {
buf.type = GCODE_MOVE_TYPE_NOOP;
}
break;
}
}
buf.extruder_id = m_current_extruder;
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
*/
return true;
}
void GCodeAnalyzer::push_to_output(const char *text, const size_t len, bool add_eol)
{
// New length of the output buffer content.
size_t len_new = output_buffer_length + len + 1;
if (add_eol)
++ len_new;
// Resize the output buffer to a power of 2 higher than the required memory.
if (output_buffer.size() < len_new) {
size_t v = len_new;
// Compute the next highest power of 2 of 32-bit v
// http://graphics.stanford.edu/~seander/bithacks.html
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
output_buffer.resize(v);
}
// Copy the text to the output.
if (len != 0) {
memcpy(output_buffer.data() + output_buffer_length, text, len);
output_buffer_length += len;
}
if (add_eol)
output_buffer[output_buffer_length ++] = '\n';
output_buffer[output_buffer_length] = 0;
}
} // namespace Slic3r

View file

@ -0,0 +1,152 @@
#ifndef slic3r_GCode_PressureEqualizer_hpp_
#define slic3r_GCode_PressureEqualizer_hpp_
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
namespace Slic3r {
enum GCodeMoveType
{
GCODE_MOVE_TYPE_NOOP,
GCODE_MOVE_TYPE_RETRACT,
GCODE_MOVE_TYPE_UNRETRACT,
GCODE_MOVE_TYPE_TOOL_CHANGE,
GCODE_MOVE_TYPE_MOVE,
GCODE_MOVE_TYPE_EXTRUDE,
};
// For visualization purposes, for the purposes of the G-code analysis and timing.
// The size of this structure is 56B.
// Keep the size of this structure as small as possible, because all moves of a complete print
// may be held in RAM.
struct GCodeMove
{
bool moving_xy(const float* pos_start) const { return fabs(pos_end[0] - pos_start[0]) > 0.f || fabs(pos_end[1] - pos_start[1]) > 0.f; }
bool moving_xy() const { return moving_xy(get_pos_start()); }
bool moving_z (const float* pos_start) const { return fabs(pos_end[2] - pos_start[2]) > 0.f; }
bool moving_z () const { return moving_z(get_pos_start()); }
bool extruding(const float* pos_start) const { return moving_xy() && pos_end[3] > pos_start[3]; }
bool extruding() const { return extruding(get_pos_start()); }
bool retracting(const float* pos_start) const { return pos_end[3] < pos_start[3]; }
bool retracting() const { return retracting(get_pos_start()); }
bool deretracting(const float* pos_start) const { return ! moving_xy() && pos_end[3] > pos_start[3]; }
bool deretracting() const { return deretracting(get_pos_start()); }
float dist_xy2(const float* pos_start) const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]); }
float dist_xy2() const { return dist_xy2(get_pos_start()); }
float dist_xyz2(const float* pos_start) const { return (pos_end[0] - pos_start[0]) * (pos_end[0] - pos_start[0]) + (pos_end[1] - pos_start[1]) * (pos_end[1] - pos_start[1]) + (pos_end[2] - pos_start[2]) * (pos_end[2] - pos_start[2]); }
float dist_xyz2() const { return dist_xyz2(get_pos_start()); }
float dist_xy(const float* pos_start) const { return sqrt(dist_xy2(pos_start)); }
float dist_xy() const { return dist_xy(get_pos_start()); }
float dist_xyz(const float* pos_start) const { return sqrt(dist_xyz2(pos_start)); }
float dist_xyz() const { return dist_xyz(get_pos_start()); }
float dist_e(const float* pos_start) const { return fabs(pos_end[3] - pos_start[3]); }
float dist_e() const { return dist_e(get_pos_start()); }
float feedrate() const { return pos_end[4]; }
float time(const float* pos_start) const { return dist_xyz(pos_start) / feedrate(); }
float time() const { return time(get_pos_start()); }
float time_inv(const float* pos_start) const { return feedrate() / dist_xyz(pos_start); }
float time_inv() const { return time_inv(get_pos_start()); }
const float* get_pos_start() const { assert(type != GCODE_MOVE_TYPE_NOOP); return this[-1].pos_end; }
// Pack the enums to conserve space. With C++x11 the allocation size could be declared for enums, but for old C++ this is the only portable way.
// GCodeLineType
uint8_t type;
// Index of the active extruder.
uint8_t extruder_id;
// ExtrusionRole
uint8_t extrusion_role;
// For example, is it a bridge flow? Is the fan on?
uint8_t flags;
// X,Y,Z,E,F. Storing the state of the currently active extruder only.
float pos_end[5];
// Extrusion width, height for this segment in um.
uint16_t extrusion_width;
uint16_t extrusion_height;
};
typedef std::vector<GCodeMove> GCodeMoves;
struct GCodeLayer
{
// Index of an object printed.
size_t object_idx;
// Index of an object instance printed.
size_t object_instance_idx;
// Index of the layer printed.
size_t layer_idx;
// Top z coordinate of the layer printed.
float layer_z_top;
// Moves over this layer. The 0th move is always of type GCODELINETYPE_NOOP and
// it sets the initial position and tool for the layer.
GCodeMoves moves;
// Indices into m_moves, where the tool changes happen.
// This is useful, if one wants to display just only a piece of the path quickly.
std::vector<size_t> tool_changes;
};
typedef std::vector<GCodeLayer*> GCodeLayerPtrs;
class GCodeMovesDB
{
public:
GCodeMovesDB() {};
~GCodeMovesDB() { reset(); }
void reset();
GCodeLayerPtrs m_layers;
};
// Processes a G-code to extract moves and their types.
// This information is then used to render the print simulation colored by the extrusion type
// or various speeds.
// The GCodeAnalyzer is employed as a G-Code filter. It reads the G-code as it is generated,
// parses the comments generated by Slic3r just for the analyzer, and removes these comments.
class GCodeAnalyzer
{
public:
GCodeAnalyzer(const Slic3r::GCodeConfig *config);
~GCodeAnalyzer();
void reset();
// Process a next batch of G-code lines. Flush the internal buffers if asked for.
const char* process(const char *szGCode, bool flush);
// Length of the buffer returned by process().
size_t get_output_buffer_length() const { return output_buffer_length; }
private:
// Keeps the reference, does not own the config.
const Slic3r::GCodeConfig *m_config;
// Internal data.
// X,Y,Z,E,F
float m_current_pos[5];
size_t m_current_extruder;
ExtrusionRole m_current_extrusion_role;
uint16_t m_current_extrusion_width;
uint16_t m_current_extrusion_height;
bool m_retracted;
GCodeMovesDB *m_moves;
// Output buffer will only grow. It will not be reallocated over and over.
std::vector<char> output_buffer;
size_t output_buffer_length;
bool process_line(const char *line, const size_t len);
// Push the text to the end of the output_buffer.
void push_to_output(const char *text, const size_t len, bool add_eol = true);
};
} // namespace Slic3r
#endif /* slic3r_GCode_PressureEqualizer_hpp_ */