Fixed visualization of G-code lines in G-code viewer (3D view).
Improved speed of parsing external G-code.
This commit is contained in:
parent
116fd0526b
commit
ac7674b85a
7 changed files with 163 additions and 73 deletions
|
@ -343,18 +343,6 @@ void GCodeProcessor::TimeProcessor::reset()
|
|||
machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
|
||||
}
|
||||
|
||||
struct FilePtr {
|
||||
FilePtr(FILE *f) : f(f) {}
|
||||
~FilePtr() { this->close(); }
|
||||
void close() {
|
||||
if (this->f) {
|
||||
::fclose(this->f);
|
||||
this->f = nullptr;
|
||||
}
|
||||
}
|
||||
FILE* f = nullptr;
|
||||
};
|
||||
|
||||
void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<MoveVertex>& moves, std::vector<size_t>& lines_ends)
|
||||
{
|
||||
FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
|
||||
|
@ -755,11 +743,11 @@ void GCodeProcessor::Result::reset() {
|
|||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = {
|
||||
{ EProducer::PrusaSlicer, "PrusaSlicer" },
|
||||
{ EProducer::Slic3rPE, "Slic3r Prusa Edition" },
|
||||
{ EProducer::Slic3r, "Slic3r" },
|
||||
{ EProducer::PrusaSlicer, "generated by PrusaSlicer" },
|
||||
{ EProducer::Slic3rPE, "generated by Slic3r Prusa Edition" },
|
||||
{ EProducer::Slic3r, "generated by Slic3r" },
|
||||
{ EProducer::Cura, "Cura_SteamEngine" },
|
||||
{ EProducer::Simplify3D, "Simplify3D" },
|
||||
{ EProducer::Simplify3D, "G-Code generated by Simplify3D(R)" },
|
||||
{ EProducer::CraftWare, "CraftWare" },
|
||||
{ EProducer::ideaMaker, "ideaMaker" },
|
||||
{ EProducer::KissSlicer, "KISSlicer" }
|
||||
|
@ -1191,6 +1179,16 @@ void GCodeProcessor::reset()
|
|||
#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
|
||||
}
|
||||
|
||||
static inline const char* skip_whitespaces(const char *begin, const char *end) {
|
||||
for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin);
|
||||
return begin;
|
||||
}
|
||||
|
||||
static inline const char* remove_eols(const char *begin, const char *end) {
|
||||
for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end);
|
||||
return end;
|
||||
}
|
||||
|
||||
void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
|
||||
{
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
@ -1202,14 +1200,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
|
|||
// pre-processing
|
||||
// parse the gcode file to detect its producer
|
||||
{
|
||||
m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
const std::string_view cmd = line.cmd();
|
||||
if (cmd.empty()) {
|
||||
const std::string_view comment = line.comment();
|
||||
if (comment.length() > 1 && detect_producer(comment))
|
||||
m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) {
|
||||
begin = skip_whitespaces(begin, end);
|
||||
if (begin != end && *begin == ';') {
|
||||
// Comment.
|
||||
begin = skip_whitespaces(++ begin, end);
|
||||
end = remove_eols(begin, end);
|
||||
if (begin != end && detect_producer(std::string_view(begin, end - begin)))
|
||||
m_parser.quit_parsing();
|
||||
}
|
||||
});
|
||||
});
|
||||
m_parser.reset();
|
||||
|
||||
// if the gcode was produced by PrusaSlicer,
|
||||
|
@ -1374,9 +1374,11 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
|
|||
};
|
||||
|
||||
BedSize bed_size;
|
||||
bool producer_detected = false;
|
||||
|
||||
m_parser.parse_file(filename, [this, &bed_size](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
|
||||
auto extract_double = [](const std::string& cmt, const std::string& key, double& out) {
|
||||
m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) {
|
||||
|
||||
auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) {
|
||||
size_t pos = cmt.find(key);
|
||||
if (pos != cmt.npos) {
|
||||
pos = cmt.find(',', pos);
|
||||
|
@ -1388,12 +1390,12 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
|
|||
return false;
|
||||
};
|
||||
|
||||
auto extract_floats = [](const std::string& cmt, const std::string& key, std::vector<float>& out) {
|
||||
auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector<float>& out) {
|
||||
size_t pos = cmt.find(key);
|
||||
if (pos != cmt.npos) {
|
||||
pos = cmt.find(',', pos);
|
||||
if (pos != cmt.npos) {
|
||||
std::string data_str = cmt.substr(pos + 1);
|
||||
const std::string_view data_str = cmt.substr(pos + 1);
|
||||
std::vector<std::string> values_str;
|
||||
boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on);
|
||||
for (const std::string& s : values_str) {
|
||||
|
@ -1404,28 +1406,39 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
|
|||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const std::string& comment = line.raw();
|
||||
if (comment.length() > 2 && comment.front() == ';') {
|
||||
if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos)
|
||||
extract_double(comment, "strokeXoverride", bed_size.x);
|
||||
else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos)
|
||||
extract_double(comment, "strokeYoverride", bed_size.y);
|
||||
else if (comment.find("filamentDiameters") != comment.npos) {
|
||||
m_result.filament_diameters.clear();
|
||||
extract_floats(comment, "filamentDiameters", m_result.filament_diameters);
|
||||
|
||||
begin = skip_whitespaces(begin, end);
|
||||
end = remove_eols(begin, end);
|
||||
if (begin != end)
|
||||
if (*begin == ';') {
|
||||
// Comment.
|
||||
begin = skip_whitespaces(++ begin, end);
|
||||
if (begin != end) {
|
||||
std::string_view comment(begin, end - begin);
|
||||
if (producer_detected) {
|
||||
if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos)
|
||||
extract_double(comment, "strokeXoverride", bed_size.x);
|
||||
else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos)
|
||||
extract_double(comment, "strokeYoverride", bed_size.y);
|
||||
else if (comment.find("filamentDiameters") != comment.npos) {
|
||||
m_result.filament_diameters.clear();
|
||||
extract_floats(comment, "filamentDiameters", m_result.filament_diameters);
|
||||
} else if (comment.find("filamentDensities") != comment.npos) {
|
||||
m_result.filament_densities.clear();
|
||||
extract_floats(comment, "filamentDensities", m_result.filament_densities);
|
||||
} else if (comment.find("extruderDiameter") != comment.npos) {
|
||||
std::vector<float> extruder_diameters;
|
||||
extract_floats(comment, "extruderDiameter", extruder_diameters);
|
||||
m_result.extruders_count = extruder_diameters.size();
|
||||
}
|
||||
} else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)"))
|
||||
producer_detected = true;
|
||||
}
|
||||
} else {
|
||||
// Some non-empty G-code line detected, stop parsing config comments.
|
||||
reader.quit_parsing();
|
||||
}
|
||||
else if (comment.find("filamentDensities") != comment.npos) {
|
||||
m_result.filament_densities.clear();
|
||||
extract_floats(comment, "filamentDensities", m_result.filament_densities);
|
||||
}
|
||||
else if (comment.find("extruderDiameter") != comment.npos) {
|
||||
std::vector<float> extruder_diameters;
|
||||
extract_floats(comment, "extruderDiameter", extruder_diameters);
|
||||
m_result.extruders_count = extruder_diameters.size();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (m_result.extruders_count == 0)
|
||||
m_result.extruders_count = std::max<size_t>(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size()));
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include "LocalesUtils.hpp"
|
||||
|
||||
|
@ -126,44 +127,92 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
|
|||
}
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file(const std::string &file, callback_t callback)
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback)
|
||||
{
|
||||
boost::nowide::ifstream f(file);
|
||||
f.sync_with_stdio(false);
|
||||
FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
|
||||
|
||||
// Read the input stream 64kB at a time, extract lines and process them.
|
||||
std::vector<char> buffer(65536 * 10, 0);
|
||||
std::string line;
|
||||
// Line buffer.
|
||||
std::string gcode_line;
|
||||
size_t file_pos = 0;
|
||||
m_parsing = true;
|
||||
GCodeLine gline;
|
||||
while (m_parsing && ! f.eof()) {
|
||||
f.read(buffer.data(), buffer.size());
|
||||
if (! f.eof() && ! f.good())
|
||||
// Reading the input file failed.
|
||||
for (;;) {
|
||||
size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f);
|
||||
if (::ferror(in.f))
|
||||
return false;
|
||||
bool eof = cnt_read == 0;
|
||||
auto it = buffer.begin();
|
||||
auto it_bufend = buffer.begin() + f.gcount();
|
||||
while (it != it_bufend) {
|
||||
bool eol = false;
|
||||
auto it_bufend = buffer.begin() + cnt_read;
|
||||
while (it != it_bufend || (eof && ! gcode_line.empty())) {
|
||||
// Find end of line.
|
||||
bool eol = false;
|
||||
auto it_end = it;
|
||||
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ;
|
||||
eol |= f.eof() && it_end == it_bufend;
|
||||
for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end)
|
||||
if (*it_end == '\n')
|
||||
line_end_callback((it_end - buffer.begin()) + 1);
|
||||
// End of line is indicated also if end of file was reached.
|
||||
eol |= eof && it_end == it_bufend;
|
||||
if (eol) {
|
||||
gline.reset();
|
||||
if (line.empty())
|
||||
this->parse_line(&(*it), &(*it_end), gline, callback);
|
||||
if (gcode_line.empty())
|
||||
parse_line_callback(&(*it), &(*it_end));
|
||||
else {
|
||||
line.insert(line.end(), it, it_end);
|
||||
this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback);
|
||||
line.clear();
|
||||
gcode_line.insert(gcode_line.end(), it, it_end);
|
||||
parse_line_callback(gcode_line.c_str(), gcode_line.c_str() + gcode_line.size());
|
||||
gcode_line.clear();
|
||||
}
|
||||
if (! m_parsing)
|
||||
// The callback wishes to exit.
|
||||
return true;
|
||||
} else
|
||||
line.insert(line.end(), it, it_end);
|
||||
// Skip all the empty lines.
|
||||
for (it = it_end; it != it_bufend && (*it == '\r' || *it == '\n'); ++ it) ;
|
||||
gcode_line.insert(gcode_line.end(), it, it_end);
|
||||
// Skip EOL.
|
||||
it = it_end;
|
||||
if (it != it_bufend && *it == '\r')
|
||||
++ it;
|
||||
if (it != it_bufend && *it == '\n') {
|
||||
line_end_callback((it - buffer.begin()) + 1);
|
||||
++ it;
|
||||
}
|
||||
}
|
||||
if (eof)
|
||||
break;
|
||||
file_pos += cnt_read;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool GCodeReader::parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback)
|
||||
{
|
||||
GCodeLine gline;
|
||||
return this->parse_file_raw_internal(filename,
|
||||
[this, &gline, parse_line_callback](const char *begin, const char *end) {
|
||||
gline.reset();
|
||||
this->parse_line(begin, end, gline, parse_line_callback);
|
||||
},
|
||||
line_end_callback);
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file(const std::string &file, callback_t callback)
|
||||
{
|
||||
return this->parse_file_internal(file, callback, [](size_t){});
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends)
|
||||
{
|
||||
lines_ends.clear();
|
||||
return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos){ lines_ends.emplace_back(file_pos); });
|
||||
}
|
||||
|
||||
bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_t line_callback)
|
||||
{
|
||||
return this->parse_file_raw_internal(filename,
|
||||
[this, line_callback](const char *begin, const char *end) { line_callback(*this, begin, end); },
|
||||
[](size_t){});
|
||||
}
|
||||
|
||||
bool GCodeReader::GCodeLine::has(char axis) const
|
||||
{
|
||||
const char *c = m_raw.c_str();
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
};
|
||||
|
||||
typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;
|
||||
typedef std::function<void(GCodeReader&, const char*, const char*)> raw_line_callback_t;
|
||||
|
||||
GCodeReader() : m_verbose(false), m_extrusion_axis('E') { this->reset(); }
|
||||
void reset() { memset(m_position, 0, sizeof(m_position)); }
|
||||
|
@ -114,6 +115,13 @@ public:
|
|||
|
||||
// Returns false if reading the file failed.
|
||||
bool parse_file(const std::string &file, callback_t callback);
|
||||
// Collect positions of line ends in the binary G-code to be used by the G-code viewer when memory mapping and displaying section of G-code
|
||||
// as an overlay in the 3D scene.
|
||||
bool parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends);
|
||||
// Just read the G-code file line by line, calls callback (const char *begin, const char *end). Returns false if reading the file failed.
|
||||
bool parse_file_raw(const std::string &file, raw_line_callback_t callback);
|
||||
|
||||
// To be called by the callback to stop parsing.
|
||||
void quit_parsing() { m_parsing = false; }
|
||||
|
||||
float& x() { return m_position[X]; }
|
||||
|
@ -132,6 +140,11 @@ public:
|
|||
// void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
|
||||
|
||||
private:
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback);
|
||||
template<typename ParseLineCallback, typename LineEndCallback>
|
||||
bool parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback);
|
||||
|
||||
const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command);
|
||||
void update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command);
|
||||
|
||||
|
@ -154,6 +167,7 @@ private:
|
|||
char m_extrusion_axis;
|
||||
float m_position[NUM_AXES];
|
||||
bool m_verbose;
|
||||
// To be set by the callback to stop parsing.
|
||||
bool m_parsing{ false };
|
||||
};
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ bool is_decimal_separator_point()
|
|||
}
|
||||
|
||||
|
||||
double string_to_double_decimal_point(const std::string& str, size_t* pos /* = nullptr*/)
|
||||
double string_to_double_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/)
|
||||
{
|
||||
double out;
|
||||
size_t p = fast_float::from_chars(str.data(), str.data() + str.size(), out).ptr - str.data();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <clocale>
|
||||
#include <iomanip>
|
||||
#include <cassert>
|
||||
#include <string_view>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <xlocale.h>
|
||||
|
@ -40,7 +41,7 @@ bool is_decimal_separator_point();
|
|||
// (We use user C locales and "C" C++ locales in most of the code.)
|
||||
std::string float_to_string_decimal_point(double value, int precision = -1);
|
||||
//std::string float_to_string_decimal_point(float value, int precision = -1);
|
||||
double string_to_double_decimal_point(const std::string& str, size_t* pos = nullptr);
|
||||
double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
|
@ -255,6 +255,19 @@ template<typename T> struct IsTriviallyCopyable { static constexpr bool value =
|
|||
template<typename T> struct IsTriviallyCopyable : public std::is_trivially_copyable<T> {};
|
||||
#endif
|
||||
|
||||
// A very lightweight ROII wrapper around C FILE.
|
||||
// The old C file API is much faster than C++ streams, thus they are recommended for processing large / huge files.
|
||||
struct FilePtr {
|
||||
FilePtr(FILE *f) : f(f) {}
|
||||
~FilePtr() { this->close(); }
|
||||
void close() {
|
||||
if (this->f) {
|
||||
::fclose(this->f);
|
||||
this->f = nullptr;
|
||||
}
|
||||
}
|
||||
FILE* f = nullptr;
|
||||
};
|
||||
|
||||
class ScopeGuard
|
||||
{
|
||||
|
|
|
@ -889,7 +889,7 @@ std::string string_printf(const char *format, ...)
|
|||
|
||||
std::string header_slic3r_generated()
|
||||
{
|
||||
return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
return std::string("generated by PrusaSlicer " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
|
||||
}
|
||||
|
||||
std::string header_gcodeviewer_generated()
|
||||
|
|
Loading…
Reference in a new issue