From 03b1312f2da591e35527221a76d8f5be6dd0f3bc Mon Sep 17 00:00:00 2001 From: bubnikv Date: Mon, 26 Sep 2016 12:37:54 +0200 Subject: [PATCH] G-code analyser, first draft. The G-code analyser will be used for advanced visualization of the printing paths, including the extrusion types. --- xs/src/libslic3r/GCode/Analyzer.cpp | 326 ++++++++++++++++++++++++++++ xs/src/libslic3r/GCode/Analyzer.hpp | 152 +++++++++++++ 2 files changed, 478 insertions(+) create mode 100644 xs/src/libslic3r/GCode/Analyzer.cpp create mode 100644 xs/src/libslic3r/GCode/Analyzer.hpp diff --git a/xs/src/libslic3r/GCode/Analyzer.cpp b/xs/src/libslic3r/GCode/Analyzer.cpp new file mode 100644 index 000000000..63f9639b6 --- /dev/null +++ b/xs/src/libslic3r/GCode/Analyzer.cpp @@ -0,0 +1,326 @@ +#include +#include +#include + +#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 diff --git a/xs/src/libslic3r/GCode/Analyzer.hpp b/xs/src/libslic3r/GCode/Analyzer.hpp new file mode 100644 index 000000000..9e84e33c4 --- /dev/null +++ b/xs/src/libslic3r/GCode/Analyzer.hpp @@ -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 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 tool_changes; +}; + +typedef std::vector 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 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_ */