Files
grbl-sender/gcode_parser.cpp
T
2023-05-16 09:18:06 +03:00

530 lines
17 KiB
C++

#include "gcode_parser.h"
grbl::parse_exception::parse_exception(const std::string &reason, size_t lineNumber)
: reason(reason), line_number(lineNumber) {}
const char *grbl::parse_exception::what() const noexcept {
return reason.c_str();
}
struct word {
char command;
double parameter;
std::string to_string() const {
std::string result;
result += command;
result += std::to_string(parameter);
return result;
}
};
static std::array<double, 4> motion_commands = {0, 1, 2, 3};
static std::string valid_words = "GMXYZIJKFRSP";
static std::string ignore_axes = "ABC";
std::string grbl::grbl_parser::cleanup_line(std::string line, int line_number) {
auto comment_index = line.rfind(';');
if (comment_index != std::string::npos) {
line = line.substr(0, comment_index);
}
size_t start;
while ((start = line.find('(')) != std::string::npos) {
auto end = line.find(')');
if (end == std::string::npos || end < start)
throw parse_exception("mismatched parentheses", line_number);
line.erase(start, end - start + 1);
}
return line;
}
void grbl::grbl_parser::parse(std::istream &in) {
int i = 0;
for (std::string line; std::getline(in, line);) {
i++;
line = trim(cleanup_line(line, i));
if (line.empty()) continue;
parse(to_upper(line), i);
}
}
bool grbl::grbl_parser::is_motion_command(double param) {
for (auto &p: motion_commands) {
if (p == param)
return true;
}
return false;
}
void grbl::grbl_parser::parse(std::string line, int line_number) {
static auto gcode_re = std::regex(R"([a-zA-Z]\s*\-?\d*\.?\d*)");
std::vector<word> words;
std::smatch matches;
std::string::const_iterator start(line.cbegin());
while (regex_search(start, line.cend(), matches, gcode_re)) {
auto token = remove_spaces(matches[0]);
words.push_back(
word{
.command= token[0],
.parameter = std::stod(token.substr(1))
}
);
start = matches.suffix().first;
}
auto it = words.begin();
while (it != words.end()) {
if ((*it).command == 'N') {
it = words.erase(it);
continue;
}
bool ignore_additional_axes = true;
if (ignore_axes.find((*it).command) != std::string::npos && ignore_additional_axes) {
it = words.erase(it);
continue;
}
if (valid_words.find((*it).command) == std::string::npos) {
warnings.push_back("ignoring unknown word (letter): \"" + (*it).to_string() + "\". (line " +
std::to_string(line_number) + ")");
it = words.erase(it);
continue;
}
if ((*it).command != 'F') {
it++;
continue;
}
state.feed = (*it).parameter;
if (state.unit == parse_unit::imperial)
state.feed *= 25.4;
it = words.erase(it);
}
it = words.begin();
while (it != words.end()) {
if ((*it).command == 'M') {
int param = (int) (*it).parameter;
if (param != (*it).parameter || param < 0) {
throw parse_exception("M code can only have positive integer parameters", line_number);
}
commands.push_back(std::make_shared<mcode_cmd>(param, line_number));
it = words.erase(it);
continue;
}
if ((*it).command == 'S') {
double param = (*it).parameter;
if (param < 0) {
warnings.push_back("spindle speed must be positive. (line " + std::to_string(line_number) + ")");
}
commands.push_back(std::make_shared<spindle_cmd>(std::abs(param), line_number));
it = words.erase(it);
continue;
}
if ((*it).command == 'G' && !is_motion_command((*it).parameter)) {
auto param = (*it).parameter;
if (param == 90) {
state.distance_mode = parse_distance_mode::absolute;
it = words.erase(it);
continue;
}
if (param == 91) {
state.distance_mode = parse_distance_mode::incremental;
it = words.erase(it);
continue;
}
if (param == 90.1) {
state.arc_distance_mode = parse_distance_mode::absolute;
it = words.erase(it);
continue;
}
if (param == 91.1) {
state.arc_distance_mode = parse_distance_mode::incremental;
it = words.erase(it);
continue;
}
if (param == 21) {
state.unit = parse_unit::metric;
it = words.erase(it);
continue;
}
if (param == 20) {
state.unit = parse_unit::imperial;
it = words.erase(it);
continue;
}
if (param == 17) {
state.plane = arc_plane::xy;
it = words.erase(it);
continue;
}
if (param == 18) {
state.plane = arc_plane::zx;
it = words.erase(it);
continue;
}
if (param == 19) {
state.plane = arc_plane::yz;
it = words.erase(it);
continue;
}
if (param == 4) {
auto next = it;
next++;
if (next != words.end() && (*next).command == 'P') {
if ((*next).parameter < 0) {
warnings.push_back("dwell time must be positive. (line " + std::to_string(line_number) + ")");
}
commands.push_back(std::make_shared<dwell_cmd>(std::abs((*next).parameter), line_number));
it = words.erase(it);
it = words.erase(it);
continue;
}
}
// we do not support G64, only G61
if (param == 64) {
warnings.push_back("G64 not supported. Ignoring. (line " + std::to_string(line_number) + ")");
auto next = it;
next++;
if (next != words.end() && (*next).command == 'P') {
it = words.erase(it);
}
it = words.erase(it);
continue;
}
if (param == 94 || param == 93 || param == 95) {
// G93 sets feed rate mode to inverse time
// G94 sets feed rate mode to units per minute (mm/min or inch/min)
// G95 sets feed rate mode to units per revolution
it = words.erase(it);
continue;
}
// coordinate system offset
if (param == 54 || param == 55 || param == 56 || param == 57) {
it = words.erase(it);
continue;
}
warnings.push_back(
"ignoring unknown command " + (*it).to_string() + ". (line " + std::to_string(line_number) + ")");
it = words.erase(it);
}
if (words.empty())
return;
int MotionMode = state.last_motion_mode;
if (words.begin()->command == 'G') {
MotionMode = (int) words.begin()->parameter;
state.last_motion_mode = MotionMode;
words.erase(words.begin());
}
if (MotionMode < 0) {
throw parse_exception("no motion mode active", line_number);
}
double unit_multiplier = (state.unit == parse_unit::metric) ? 1 : 25.4;
glm::vec<3, double> end_pos = state.position;
auto StartValid = state.position_valid[0] && state.position_valid[1] && state.position_valid[2];
if (state.distance_mode == parse_distance_mode::incremental && !StartValid) {
throw parse_exception(
"incremental motion is only allowed after an absolute position has been established (eg. with \"G90 G0 X0 Y0 Z5\")",
line_number);
}
if ((MotionMode == 2 || MotionMode == 3) && !StartValid) {
throw parse_exception(
"arcs (G2/G3) are only allowed after an absolute position has been established (eg. with \"G90 G0 X0 Y0 Z5\")",
line_number);
}
{
float incremental = (state.distance_mode == parse_distance_mode::incremental) ? 1 : 0;
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'X') {
it++;
continue;
}
end_pos.x = (*it).parameter * unit_multiplier + incremental * end_pos.x;
it = words.erase(it);
state.position_valid[0] = true;
break;
}
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'Y') {
it++;
continue;
}
end_pos.y = (*it).parameter * unit_multiplier + incremental * end_pos.y;
it = words.erase(it);
state.position_valid[1] = true;
break;
}
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'Z') {
it++;
continue;
}
end_pos.z = (*it).parameter * unit_multiplier + incremental * end_pos.z;
it = words.erase(it);
state.position_valid[2] = true;
break;
}
}
if (MotionMode != 0 && state.feed <= 0) {
throw parse_exception("feed rate undefined", line_number);
}
if (MotionMode == 1 && !StartValid) {
warnings.push_back(
"a feed move is used before an absolute position is established, height maps will not be applied to this motion. (line " +
std::to_string(line_number) + ")");
}
if (MotionMode <= 1) {
if (!words.empty()) {
warnings.push_back(
"motion command must be last in line (ignoring unused words (TOOD: show serialized words)in block). (line " +
std::to_string(line_number) + ")");
}
auto l = new line_motion_cmd;
l->start = state.position;
l->end = end_pos;
l->feed = state.feed;
l->rapid = MotionMode == 0;
l->line_number = line_number;
l->start_valid = StartValid;
std::memcpy(l->position_valid, state.position_valid, sizeof(bool) * 3);
commands.push_back(std::shared_ptr<line_motion_cmd>(l));
state.position = end_pos;
return;
}
double U, V;
bool ijk_used = false;
switch (state.plane) {
case arc_plane::yz:
U = state.position.y;
V = state.position.z;
break;
case arc_plane::zx:
U = state.position.z;
V = state.position.x;
break;
default:
U = state.position.x;
V = state.position.y;
break;
}
// find IJK
{
float arc_incremental = (state.arc_distance_mode == parse_distance_mode::incremental) ? 1 : 0;
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'I') {
it++;
continue;
}
switch (state.plane) {
case arc_plane::xy:
U = (*it).parameter * unit_multiplier + arc_incremental * state.position.x;
break;
case arc_plane::yz:
throw parse_exception("current plane is YZ, I word is invalid", line_number);
case arc_plane::zx:
V = (*it).parameter * unit_multiplier + arc_incremental * state.position.x;
break;
}
ijk_used = true;
it = words.erase(it);
break;
}
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'J') {
it++;
continue;
}
switch (state.plane) {
case arc_plane::xy:
V = (*it).parameter * unit_multiplier + arc_incremental * state.position.y;
break;
case arc_plane::yz:
U = (*it).parameter * unit_multiplier + arc_incremental * state.position.y;
break;
case arc_plane::zx:
throw parse_exception("current plane is ZX, J word is invalid", line_number);
}
ijk_used = true;
it = words.erase(it);
break;
}
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'K') {
it++;
continue;
}
switch (state.plane) {
case arc_plane::xy:
throw parse_exception("current plane is XY, K word is invalid", line_number);
break;
case arc_plane::yz:
V = (*it).parameter * unit_multiplier + arc_incremental * state.position.z;
break;
case arc_plane::zx:
U = (*it).parameter * unit_multiplier + arc_incremental * state.position.z;
break;
}
ijk_used = true;
it = words.erase(it);
break;
}
// resolve radius
it = words.begin();
while (it != words.end()) {
if ((*it).command != 'R') {
it++;
continue;
}
if (ijk_used) {
throw parse_exception("both IJK and R notation used", line_number);
}
if (state.position == end_pos) {
throw parse_exception("arcs in R-notation must have non-coincident start and end points",
line_number);
}
double radius = (*it).parameter * unit_multiplier;
if (radius == 0)
throw parse_exception("radius can't be zero", line_number);
double A, B;
switch (state.plane) {
case arc_plane::yz:
A = end_pos.y;
B = end_pos.z;
break;
case arc_plane::zx:
A = end_pos.z;
B = end_pos.x;
break;
default:
A = end_pos.x;
B = end_pos.y;
break;
}
A -= U; //(AB) = vector from start to end of arc along the axes of the current plane
B -= V;
// see grbl/gcode.c
double h_x2_div_d = 4.0 * (radius * radius) - (A * A + B * B);
if (h_x2_div_d < 0) {
throw parse_exception("arc radius too small to reach both ends", line_number);
}
h_x2_div_d = -sqrt(h_x2_div_d) / sqrt(A * A + B * B);
if (MotionMode == 3 ^ radius < 0) {
h_x2_div_d = -h_x2_div_d;
}
U += 0.5 * (A - (B * h_x2_div_d));
V += 0.5 * (B + (A * h_x2_div_d));
it = words.erase(it);
break;
}
if (!words.empty()) {
warnings.push_back(
"motion command must be last in line (ignoring unused words (add serialized words here) in block). (line " +
std::to_string(line_number) + ")");
}
auto a = new arc_motion_cmd;
a->start = state.position;
a->end = end_pos;
a->feed = state.feed;
a->direction = (MotionMode == 2) ? arc_direction::cw : arc_direction::ccw;
a->u = U;
a->v = V;
a->line_number = line_number;
a->plane = state.plane;
commands.push_back(std::shared_ptr<arc_motion_cmd>(a));
state.position = end_pos;
return;
}
}
}
grbl::grbl_parser::grbl_parser() {
reset();
}
void grbl::grbl_parser::reset() {
state = parser_state{};
}