Introduced machine state handlers (check and run still need work).

Added proper init handler, fetching settings and parameters and exposing them to UI.
Added spindle rendering.
This commit is contained in:
2023-05-04 14:15:33 +03:00
parent a63d58a1ec
commit cacbe1b8aa
5 changed files with 842 additions and 200 deletions
+426 -43
View File
@@ -9,6 +9,13 @@ static bool starts_with(const std::string& line, const std::string& prefix) {
grbl::machine::machine() {
pipe = new tcp_transport("192.168.5.39", 23);
states[grbl_machine_state::disconnected] = new machine_state_connect;
states[grbl_machine_state::init] = new machine_state_init;
states[grbl_machine_state::idle] = new machine_state_idle;
states[grbl_machine_state::check_program] = new machine_state_check_program;
states[grbl_machine_state::run_program] = new machine_state_run_program;
switch_to_state(grbl_machine_state::disconnected);
}
grbl::machine::~machine() {
@@ -21,14 +28,15 @@ void grbl::machine::connect() {
void grbl::machine::on_connected(grbl::transport *transport) {
std::cout << "grbl machine connected" << std::endl;
states[state]->on_connected(this);
// telnet handshake so that we get the banner. banner won't be coming otherwise
// t->send("\xff\xfd\x18\xff\xfd\x20\xff\xfd\x23\xff\xfd\x27");
pipe->send("\xff\xfd\x18");
// pipe->send("\xff\xfd\x18");
}
void grbl::machine::on_disconnected(grbl::transport *transport) {
std::cout << "grbl machine disconnected" << std::endl;
switch_to_state(grbl_machine_state::disconnected);
}
grbl::realtime_status_report grbl::parse_status_report(std::string line, grbl::realtime_status_report& result) {
@@ -234,56 +242,260 @@ std::string grbl::error_to_string(size_t error) {
}
}
std::string grbl::setting_description(int number) {
switch (number) {
case 0:
return "Step pulse time (microseconds)\nSets time length per step. Minimum 3 microseconds.";
case 1:
return "Step idle delay (milliseconds)\nSets a short hold delay when stopping to let dynamics settle before disabling steppers. Value 255 keeps motors enabled.";
case 2:
return "Step pulse invert (mask)\nInverts the step signals (active low).";
case 3:
return "Step direction invert (mask)\nInverts the direction signals (active low).";
case 4:
return "Invert step enable pin (boolean)\nInverts the stepper driver enable signals (active low). If the stepper drivers shares the same enable signal only X is used.";
case 5:
return "Invert limit pins (mask)\nInverts the axis limit input signals. ";
case 6:
return "Invert probe pin (boolean)\nInverts the probe input pin signal.";
case 10:
return "Status report options (mask)\nSpecifies optional data included in status reports.";
case 11:
return "Junction deviation (mm)\nSets how fast Grbl travels through consecutive motions. Lower value slows it down.";
case 12:
return "Arc tolerance (mm)\nSets the G2 and G3 arc tracing accuracy based on radial error. Beware: A very small value may effect performance.";
case 13:
return "Report in inches (boolean)\nEnables inch units when returning any position and rate value that is not a settings value.";
case 14:
return "Invert control pins (mask)\nInverts the control signals (active low).";
case 15:
return "Invert coolant pins (mask)\nInverts the coolant and mist signals (active low).";
case 16:
return "Invert spindle signals (mask)\nInverts the spindle on counterclockwise and PWM signals (active low).";
case 17:
return "Pullup disable control pins (mask)\nDisable the control signals pullup resistors. Potentially enables pulldown resistor if available.";
case 18:
return "Pullup disable limit pins (mask)\nDisable the limit signals pullup resistors. Potentially enables pulldown resistor if available.";
case 19:
return "Pullup disable probe pin (boolean)\nDisable the probe signal pullup resistor. Potentially enables pulldown resistor if available.";
case 20:
return "Soft limits enable (boolean)\nEnables soft limits checks within machine travel and sets alarm when exceeded. Requires homing.";
case 21:
return "Hard limits enable (mask)\nWhen enabled immediately halts motion and throws an alarm when switch is triggered. In strict mode only homing is possible after switch is triggered. ";
case 22:
return "Homing cycle enable (boolean)\nEnables homing cycle. Requires limit switches on all axes.";
case 23:
return "Homing direction invert (mask)\nHoming searches for a switch in the positive direction. Set axis bit to search in negative direction.";
case 24:
return "Homing locate feed rate (mm/min)\nFeed rate to slowly engage limit switch to determine its location accurately.";
case 25:
return "Homing search seek rate (mm/min)\nSeek rate to quickly find the limit switch before the slower locating phase.";
case 26:
return "Homing switch debounce delay (milliseconds)\nSets a short delay between phases of homing cycle to let a switch debounce.";
case 27:
return "Homing switch pull-off distance (mm)\nRetract distance after triggering switch to disengage it. Homing will fail if switch isn't cleared.";
case 28:
return "G73 Retract distance (mm)\nG73 retract distance (for chip breaking drilling).";
case 29:
return "Pulse delay (microseconds)\nStep pulse delay.";
case 30:
return "Maximum spindle speed (RPM)\nMaximum spindle speed. Sets PWM to maximum duty cycle.";
case 31:
return "Minimum spindle speed (RPM)\nMinimum spindle speed. Sets PWM to minimum duty cycle.";
case 32:
return "Mode of operation (integer)\nLaser mode: consecutive G1/2/3 commands will not halt when spindle speed is changed. Lathe mode: allows use of G7, G8, G96 and G97.";
case 33:
return "Spindle PWM frequency (Hz)\nSpindle PWM frequency.";
case 34:
return "Spindle PWM off value (percent)\nSpindle PWM off value in percent (duty cycle).";
case 35:
return "Spindle PWM min value (percent)\nSpindle PWM min value in percent (duty cycle).";
case 36:
return "Spindle PWM max value (percent)\nSpindle PWM max value in percent (duty cycle).";
case 37:
return "Steppers deenergize (mask)\nSpecifies which steppers not to disable when stopped.";
case 38:
return "Spindle PPR (pulses)\nSpindle encoder pulses per revolution.";
case 39:
return "Enable legacy RT commands (boolean)\nEnables \"normal\" processing of ?, ! and ~ characters when part of $-setting or comment. If disabled then they are added to the input string instead.";
case 40:
return "Limit jog commands (boolean)\nLimit jog commands to machine limits for homed axes.";
case 43:
return "Homing passes, (Number of homing passes. Minimum 1)\n maximum 128.";
case 44:
return "Axes homing, first pass (mask)\nAxes to home in first pass.";
case 45:
return "Axes homing, second pass (mask)\nAxes to home in second pass.";
case 46:
return "Axes homing,third pass (mask)\nAxes to home in third pass.";
case 47:
return "Axes homing, fourth pass (mask)\nAxes to home in fourth pass.";
case 48:
return "Axes homing, fifthpass (mask)\nAxes to home in fifth pass.";
case 49:
return "Axes homing, sixth pass (mask)\nAxes to home in sixth pass.";
case 50:
return "Step jog speed (mm/min)\nStep jogging speed in millimeters per minute.";
case 51:
return "Slow jog speed (mm/min)\nSlow jogging speed in millimeters per minute.";
case 52:
return "Fast jog speed (mm/min)\nFast jogging speed in millimeters per minut.";
case 53:
return "Step jog distance (mm)\nJog distance for single step jogging.";
case 54:
return "Slow jog distance (mm)\nJog distance before automatic stop.";
case 55:
return "Fast jog distance (mm)\nJog distance before automatic stop.";
case 60:
return "Restore overrides ()\nRestore overrides to default values at program end.";
case 61:
return "Ignore door when idle ()\nEnable this if it is desirable to open the safety door when in IDLE mode (eg. for jogging).";
case 62:
return "Sleep enable ()\nEnable sleep mode. ";
case 63:
return "Disable laser ()\nDisable laser during hold. ";
case 64:
return "Force init alarm ()\nStarts Grbl in alarm mode after a cold reset.";
case 65:
return "Check limits at init ()\nIf limit switches are engaged after reset this forces Grbl to start in alarm mode.";
case 66:
return "Homing init lock, (If homing is enabled)\n homing init lock sets Grbl into an alarm state upon power up. Clear by performing a homing cycle.";
case 70:
return "Stream,,Input stream source: 0 - serial, 1 - bluetooth ( 2 - ethernet)\n 3 - WiFi";
case 71:
return "WiFi SSID ()\nWiFi SSID.";
case 72:
return "WiFi Password ()\nWiFi Password.";
case 73:
return "WiFi Port ()\nWiFi Port Number listening for incoming connections.";
case 74:
return "Bluetooth device ()\nBluetooth device name.";
case 75:
return "Bluetooth service ()\nBluetooth service name.";
case 80: return "Spindle P-gain";
case 81: return "Spindle I-gain";
case 82: return "Spindle D-gain";
case 84: return "Spindle PID max error";
case 85: return "Spindle PID max I error\nSpindle PID max integrator error";
case 90: return "Spindle sync P-gain";
case 91: return "Spindle sync I-gain";
case 92: return "Spindle sync D-gain";
case 95: return "Spindle sync PID max I error\nSpindle sync PID max integrator error";
case 100:
return "X-axis travel resolution (step/mm)\nX-axis travel resolution in steps per millimeter.";
case 101:
return "Y-axis travel resolution (step/mm)\nY-axis travel resolution in steps per millimeter.";
case 102:
return "Z-axis travel resolution (step/mm)\nZ-axis travel resolution in steps per millimeter.";
case 103:
return "A-axis travel resolution (step/mm)\nA-axis travel resolution in steps per millimeter.";
case 104:
return "B-axis travel resolution (step/mm)\nB-axis travel resolution in steps per millimeter.";
case 105:
return "C-axis travel resolution (step/mm)\nC-axis travel resolution in steps per millimeter.";
case 110:
return "X-axis maximum rate (mm/min)\nX-axis maximum rate. Used as G0 rapid rate.";
case 111:
return "Y-axis maximum rate (mm/min)\nY-axis maximum rate. Used as G0 rapid rate.";
case 112:
return "Z-axis maximum rate (mm/min)\nZ-axis maximum rate. Used as G0 rapid rate.";
case 113:
return "A-axis maximum rate (mm/min)\nA-axis maximum rate. Used as G0 rapid rate.";
case 114:
return "B-axis maximum rate (mm/min)\nB-axis maximum rate. Used as G0 rapid rate.";
case 115:
return "C-axis maximum rate (mm/min)\nC-axis maximum rate. Used as G0 rapid rate.";
case 120:
return "X-axis acceleration (mm/sec^2)\nX-axis acceleration. Used for motion planning to not exceed motor torque and lose steps.";
case 121:
return "Y-axis acceleration (mm/sec^2)\nY-axis acceleration. Used for motion planning to not exceed motor torque and lose steps.";
case 122:
return "Z-axis acceleration (mm/sec^2)\nZ-axis acceleration. Used for motion planning to not exceed motor torque and lose steps.";
case 123:
return "A-axis acceleration (mm/sec^2)\nA-axis acceleration. Used for motion planning to not exceed motor torque and lose steps.";
case 124:
return "B-axis acceleration (mm/sec^2)\nB-axis acceleration. Used for motion planning to not exceed motor torque and lose steps.";
case 125:
return "C-axis acceleration (mm/sec^2)\nC-axis acceleration. Used for motion planning to not exceed motor torque and lose steps.";
case 130:
return "X-axis maximum travel (mm)\nMaximum X-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances.";
case 131:
return "Y-axis maximum travel (mm)\nMaximum Y-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances.";
case 132:
return "Z-axis maximum travel (mm)\nMaximum Z-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances.";
case 133:
return "A-axis maximum travel (mm)\nMaximum A-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances.";
case 134:
return "B-axis maximum travel (mm)\nMaximum B-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances.";
case 135:
return "C-axis maximum travel (mm)\nMaximum B-axis travel distance from homing switch. Determines valid machine space for soft-limits and homing search distances.";
case 160:
return "X-axis backlash compensation (mm)\nX-axis backlash distance to compensate for.";
case 161:
return "Y-axis backlash compensation (mm)\nY-axis backlash distance to compensate for.";
case 162:
return "Z-axis backlash compensation (mm)\nZ-axis backlash distance to compensate for.";
case 163:
return "A-axis backlash compensation (mm)\nA-axis backlash distance to compensate for.";
case 164:
return "B-axis backlash compensation (mm)\nB-axis backlash distance to compensate for.";
case 165:
return "C-axis backlash compensation (mm)\nB-axis backlash distance to compensate for.";
default:
return "unknown setting";
}
}
void grbl::machine::on_line_received(std::string line, grbl::transport *transport) {
if (line.at(0) != '<')
std::cout << ">> " << line << std::endl;
if (starts_with(line, "ok")) {
if (state == grbl_machine_state::run_program || state == grbl_machine_state::check_program) {
if (!entered_check_mode) {
entered_check_mode = true;
}
continue_program();
} else if (state == grbl_machine_state::idle && entered_check_mode) {
entered_check_mode = false;
}
states[state]->on_line_received(line);
} else if (starts_with(line, "error")) {
size_t error = std::stoi(line.substr(6));
if (state == grbl_machine_state::run_program) {
listener->on_run_completed(false, executed_instructions - 1, error);
state = grbl_machine_state::idle;
} else if (state == grbl_machine_state::check_program) {
listener->on_check_completed(false, executed_instructions - 1, error);
state = grbl_machine_state::idle;
pipe->send("$C"); // exit check mode
}
// on_error(error);
} else {
// we have a push message
if (starts_with(line, "Grbl")) {
listener->on_banner(line);
reset_machine_state();
} else if (starts_with(line, "<")) {
last_report = parse_status_report(line, last_report);
listener->on_realtime_status_report(last_report);
} else if (starts_with(line, "[MSG:")) {
listener->on_message(line.substr(5, line.size() - 6));
} else if (starts_with(line, "ALARM:")) {
listener->on_alarm(std::stoi(line.substr(6)));
}
}
// if (state == grbl_machine_state::run_program) {
// if (line.rfind("ok", 0) == 0) {
// if (starts_with(line, "ok")) {
// if (state == grbl_machine_state::run_program || state == grbl_machine_state::check_program) {
// if (!entered_check_mode) {
// entered_check_mode = true;
// }
// continue_program();
// } else if (line.rfind("error", 0) == 0) {
// std::cerr << "Received error" << std::endl;
// } else {
// } else if (state == grbl_machine_state::idle && entered_check_mode) {
// entered_check_mode = false;
// }
//
// } else if (starts_with(line, "error")) {
// size_t error = std::stoi(line.substr(6));
// if (state == grbl_machine_state::run_program) {
// listener->on_run_completed(false, executed_instructions - 1, error);
// state = grbl_machine_state::idle;
// } else if (state == grbl_machine_state::check_program) {
// listener->on_check_completed(false, executed_instructions - 1, error);
// state = grbl_machine_state::idle;
// pipe->send("$C"); // exit check mode
// }
//// on_error(error);
// } else {
// // evaluate responses when not running a program
// // we have a push message
// if (starts_with(line, "Grbl")) {
// listener->on_banner(line);
// pipe->send("$10=0"); // display position in work pos, please
// pipe->send("$$"); // get all settings
// pipe->send("$#"); // get all offsets
// reset_machine_state();
// } else if (starts_with(line, "<")) {
// last_report = parse_status_report(line, last_report);
// listener->on_realtime_status_report(last_report);
// } else if (starts_with(line, "[MSG:")) {
// listener->on_message(line.substr(5, line.size() - 6));
// } else if (starts_with(line, "ALARM:")) {
// listener->on_alarm(std::stoi(line.substr(6)));
// } else {
// std::cout << "received >> " << line << std::endl;
// }
// }
}
void grbl::machine::continue_program() {
@@ -383,6 +595,177 @@ void grbl::machine::reset_machine_state() {
executed_instructions = false;
}
void grbl::machine::switch_to_state(grbl::grbl_machine_state new_state) {
states[state]->on_exit(this);
state = new_state;
states[state]->on_enter(this);
}
const std::map<std::string, std::string, grbl::settings_cmp>& grbl::machine::get_settings() const {
return settings;
}
const std::map<std::string, std::string, grbl::parameters_cmp>& grbl::machine::get_parameters() const {
return parameters;
}
bool grbl::jog_state::no_jogging() const {
return !(up_pressed || down_pressed || left_pressed || right_pressed || z_up_pressed || z_down_pressed);
}
// connect state
void grbl::machine_state_connect::on_connected(machine *m) {
// trigger a soft reset
// m->pipe->send("\xff\xfd\x18");
m->request_reset();
}
void grbl::machine_state_connect::on_disconnected(machine *m) {
}
void grbl::machine_state_connect::on_enter(grbl::machine *m) {
cnc = m;
}
void grbl::machine_state_connect::on_exit(grbl::machine *m) {
}
void grbl::machine_state_connect::on_line_received(std::string line) {
// std::cerr << "Should not get content while connecting!" << std::endl;
if (starts_with(line, "Grbl")) {
cnc->listener->on_banner(line);
cnc->switch_to_state(grbl_machine_state::init);
}
}
// init state
void grbl::machine_state_init::on_enter(grbl::machine *m) {
cnc = m;
init_state = init_stage::start;
move_to_next_init_stage();
}
void grbl::machine_state_init::on_exit(grbl::machine *m) {
cnc->listener->on_init_completed();
}
void grbl::machine_state_init::on_line_received(std::string line) {
if (starts_with(line, "ok")) {
move_to_next_init_stage();
} else if (starts_with(line, "error")) {
// TODO: how should we act?
std::cerr << "Init failed!!!!!!!!!!!!!!!!!! FIXME!" << std::endl;
} else {
if (starts_with(line, "$")) {
auto pieces = split_string(line, "=");
cnc->settings[pieces[0]] = pieces[1];
} else if (starts_with(line, "[G") || starts_with(line, "[H") || starts_with(line, "[T") || starts_with(line, "[P")) {
line = line.substr(1, line.size() - 2);
// TODO: some parameters have more than two :
auto pieces = split_string(line, ":");
cnc->parameters[pieces[0]] = pieces[1];
}
}
}
void grbl::machine_state_init::on_connected(machine *m) {
}
void grbl::machine_state_init::on_disconnected(machine *m) {
}
void grbl::machine_state_init::move_to_next_init_stage() {
switch (init_state) {
case init_stage::start:
init_state = init_stage::set_work_pos;
cnc->pipe->send("$10=0");
break;
case init_stage::set_work_pos:
init_state = init_stage::fetch_settings;
cnc->pipe->send("$$");
break;
case init_stage::fetch_settings:
init_state = init_stage::fetch_parameters;
cnc->pipe->send("$#");
break;
case init_stage::fetch_parameters:
std::cout << "CNC initialization done" << std::endl;
cnc->switch_to_state(grbl_machine_state::idle);
break;
}
}
// idle state
void grbl::machine_state_idle::on_connected(machine *m) {
}
void grbl::machine_state_idle::on_disconnected(machine *m) {
}
void grbl::machine_state_idle::on_enter(grbl::machine *m) {
cnc = m;
}
void grbl::machine_state_idle::on_exit(grbl::machine *m) {
}
void grbl::machine_state_idle::on_line_received(std::string line) {
if (starts_with(line, "ok")) {
} else if (starts_with(line, "error")) {
} else if (starts_with(line, "Grbl")) {
cnc->listener->on_banner(line);
cnc->reset_machine_state();
} else if (starts_with(line, "<")) {
cnc->last_report = parse_status_report(line, cnc->last_report);
cnc->listener->on_realtime_status_report(cnc->last_report);
} else if (starts_with(line, "[MSG:")) {
cnc->listener->on_message(line.substr(5, line.size() - 6));
} else if (starts_with(line, "ALARM:")) {
cnc->listener->on_alarm(std::stoi(line.substr(6)));
}
}
// check program
void grbl::machine_state_check_program::on_connected(machine *m) {
}
void grbl::machine_state_check_program::on_disconnected(machine *m) {
}
void grbl::machine_state_check_program::on_enter(grbl::machine *m) {
}
void grbl::machine_state_check_program::on_exit(grbl::machine *m) {
}
void grbl::machine_state_check_program::on_line_received(std::string line) {
}
// run program
void grbl::machine_state_run_program::on_connected(machine *m) {
}
void grbl::machine_state_run_program::on_disconnected(machine *m) {
}
void grbl::machine_state_run_program::on_enter(grbl::machine *m) {
}
void grbl::machine_state_run_program::on_exit(grbl::machine *m) {
}
void grbl::machine_state_run_program::on_line_received(std::string line) {
}
+99 -1
View File
@@ -1,5 +1,7 @@
#pragma once
#include <map>
#include <unordered_map>
#include "grbl_communication.h"
#include "grbl.h"
@@ -23,6 +25,7 @@ machine_status status_from_string(const std::string& status);
std::string status_to_string(const machine_status& status);
std::string alarm_to_string(int alarm);
std::string error_to_string(size_t error);
std::string setting_description(int number);
struct realtime_status_report {
machine_status status;
@@ -61,6 +64,7 @@ inline bool operator==(const realtime_status_report& a, const realtime_status_re
realtime_status_report parse_status_report(std::string line, grbl::realtime_status_report& result);
enum class grbl_machine_state {
disconnected,
init,
run_program,
check_program,
@@ -105,10 +109,89 @@ struct machine_listener {
virtual void on_banner(std::string line) = 0;
virtual void on_message(std::string message) = 0;
virtual void on_alarm(int alarm) = 0;
virtual void on_init_completed() = 0;
virtual void on_run_completed(bool success, size_t failed_index, size_t error) = 0;
virtual void on_check_completed(bool success, size_t failed_index, size_t error) = 0;
};
enum class init_stage {
start,
set_work_pos,
fetch_settings,
fetch_parameters
};
struct machine;
struct machine_state {
virtual void on_connected(machine *m) = 0;
virtual void on_disconnected(machine *m) = 0;
virtual void on_enter(machine *m) = 0;
virtual void on_exit(machine *m) = 0;
virtual void on_line_received(std::string line) = 0;
};
struct machine_state_connect : public machine_state {
void on_connected(machine *m) override;
void on_disconnected(machine *m) override;
void on_enter(machine *m) override;
void on_exit(machine *m) override;
void on_line_received(std::string line) override;
machine *cnc;
};
struct machine_state_init : public machine_state {
void on_connected(machine *m) override;
void on_disconnected(machine *m) override;
void on_enter(machine *m) override;
void on_exit(machine *m) override;
void on_line_received(std::string line) override;
init_stage init_state = init_stage::start;
void move_to_next_init_stage();
machine* cnc;
};
struct machine_state_idle : public machine_state {
void on_connected(machine *m) override;
void on_disconnected(machine *m) override;
void on_enter(machine *m) override;
void on_exit(machine *m) override;
void on_line_received(std::string line) override;
machine *cnc;
};
struct machine_state_check_program : public machine_state {
void on_connected(machine *m) override;
void on_disconnected(machine *m) override;
void on_enter(machine *m) override;
void on_exit(machine *m) override;
void on_line_received(std::string line) override;
};
struct machine_state_run_program : public machine_state {
void on_connected(machine *m) override;
void on_disconnected(machine *m) override;
void on_enter(machine *m) override;
void on_exit(machine *m) override;
void on_line_received(std::string line) override;
};
struct settings_cmp {
bool operator()(const std::string& a, const std::string& b) const {
return std::stoi(a.substr(1)) < std::stoi(b.substr(1));
}
};
struct parameters_cmp {
bool operator()(const std::string& a, const std::string& b) const {
return a < b;
}
};
struct machine : public transport_callbacks {
machine();
~machine();
@@ -121,6 +204,8 @@ struct machine : public transport_callbacks {
void cancel_jog() const;
[[nodiscard]] realtime_status_report get_status() const { return last_report; };
const std::map<std::string, std::string, settings_cmp>& get_settings() const;
const std::map<std::string, std::string, parameters_cmp>& get_parameters() const;
void request_unlock();
void request_home();
@@ -129,15 +214,28 @@ struct machine : public transport_callbacks {
void request_cycle_start();
void request_feed_hold();
protected:
void on_connected(transport *transport) override;
void on_disconnected(transport *transport) override;
void on_line_received(std::string line, transport *transport) override;
void switch_to_state(grbl_machine_state new_state);
friend class machine_state_connect;
friend class machine_state_init;
friend class machine_state_idle;
friend class machine_state_check_program;
friend class machine_state_run_program;
std::map<grbl_machine_state, machine_state *> states;
std::map<std::string, std::string, settings_cmp> settings;
std::map<std::string, std::string, parameters_cmp> parameters;
realtime_status_report last_report{};
machine_listener *listener = nullptr;
transport *pipe = nullptr;
grbl_machine_state state = grbl_machine_state::init;
grbl_machine_state state = grbl_machine_state::disconnected;
program running_program;
size_t executed_instructions = 0;
bool entered_check_mode = false;
+173 -79
View File
@@ -1,16 +1,3 @@
/*
src/example1.cpp -- C++ version of an example application that shows
how to use the various widget classes. For a Python implementation, see
'../python/example1.py'.
NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
The widget drawing code is based on the NanoVG demo application
by Mikko Mononen.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE.txt file.
*/
#include <nanogui/opengl.h>
#include <nanogui/screen.h>
#include <nanogui/window.h>
@@ -22,6 +9,8 @@
#include <nanogui/messagedialog.h>
#include <nanogui/texture.h>
#include <nanogui/textarea.h>
#include <nanogui/textbox.h>
#include <nanogui/tabwidget.h>
#include <nanogui/shader.h>
#include <nanogui/renderpass.h>
#include <iostream>
@@ -43,6 +32,7 @@
#include "string_utils.h"
#include "render.h"
#include "glm/gtx/quaternion.hpp"
#include "nanogui/nanogui.h"
#include <glm/ext/quaternion_float.hpp>
#include <glm/ext/quaternion_trigonometric.hpp>
#include <glm/vec3.hpp> // glm::vec3
@@ -77,58 +67,152 @@ public:
float cam_zoom = 0;
glm::quat cam_src_rotation = glm::quat(1.0, 0.0, 0.0, 0.0); // identity quaternion
void init_program_geometry() {
renderer.update(pgm, cnc);
TabWidget *tab_widget;
VScrollPanel *settings_vscroll;
Widget *settings_layer;
auto max_pos = renderer.get_extents_max();
auto min_pos = renderer.get_extents_min();
VScrollPanel *parameters_vscroll;
Widget *parameters_layer;
TextBox *mpos_x_text, *mpos_y_text, *mpos_z_text;
cam_target = (max_pos - min_pos) / 2.0f;
cam_zoom = (max_pos.x - min_pos.x);
cam_src_rotation = glm::quat(1.0, 0.0, 0.0, 0.0);
void fill_in_settings() {
settings_vscroll = new VScrollPanel(tab_widget);
tab_widget->append_tab("Settings", settings_vscroll);
settings_layer = new Widget(settings_vscroll);
settings_layer->set_layout(new GridLayout(Orientation::Horizontal, 1, Alignment::Middle));
auto& settings = cnc.get_settings();
for (auto& s: settings) {
auto w = settings_layer->add<Widget>();
w->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 2, 2));
auto x = w->add<Label>(s.first, "sans-bold", 20);
x->set_fixed_width(40);
auto desc = grbl::setting_description(std::stoi(s.first.substr(1)));
auto y = w->add<TextBox>(s.second);
y->set_editable(true);
y->set_tooltip(desc);
y->set_fixed_width(200);
auto z = w->add<ToolButton>(FA_SAVE);
z->set_flags(Button::Flags::NormalButton); // no toggle, please
z->set_tooltip("save");
auto t = w->add<TextArea>();
t->set_fixed_width(200);
t->set_fixed_height(20);
t->clear();
t->append(desc.substr(0, desc.find_first_of('\n')));
}
}
void fill_in_parameters() {
parameters_vscroll = new VScrollPanel(tab_widget);
tab_widget->append_tab("Parameters", parameters_vscroll);
parameters_layer = new Widget(parameters_vscroll);
parameters_layer->set_layout(new GridLayout(Orientation::Horizontal, 1, Alignment::Middle));
auto& parameters = cnc.get_parameters();
for (auto& entry: parameters) {
auto w = parameters_layer->add<Widget>();
w->set_layout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 2, 2));
auto x = w->add<Label>(entry.first, "sans-bold", 20);
x->set_fixed_width(50);
auto y = w->add<TextBox>(entry.second);
y->set_editable(true);
y->set_fixed_width(200);
auto z = w->add<ToolButton>(FA_SAVE);
z->set_flags(Button::Flags::NormalButton); // no toggle, please
z->set_tooltip("save");
}
}
SenderApp() : Screen(Vector2i(1024, 768), "GRBL Sender") {
inc_ref();
window = new Window(this, "Machine status");
// window->set_fixed_height(Screen::size().y());
window->set_position(Vector2i(0, 0));
window->set_layout(new GroupLayout());
// window->set_size(Screen::size());
// save regular button color
auto b = new Button(this);
colBg = b->background_color();
b->set_visible(false);
colBg = theme()->m_button_gradient_bot_focused;
// create main window
window = new Window(this, "Machine status");
window->set_fixed_height((Screen::size().y() - 40) / 2);
window->set_position(Vector2i(0, 0));
window->set_layout(new BoxLayout(nanogui::Orientation::Vertical));
tab_widget = window->add<TabWidget>();
tab_widget->set_callback([&](int index) {
if (index == 1 || index == 2) {
perform_layout();
}
tab_widget->set_selected_index(index);
});
tab_widget->set_fixed_height((this->height() - 100) / 2);
Widget *layer = new Widget(tab_widget);
layer->set_layout(new GroupLayout());
tab_widget->append_tab("Info", layer);
// Widget *layer3 = new Widget(tab_widget);
// tab_widget->append_tab("Offsets", layer3);
// layer3->set_layout(new GroupLayout());
// layer3->add<Label>("Parameters");
// layer3->add<TextBox>();
new Label(window, "Status", "sans-bold");
Widget *status_holder = new Widget(window);
new Label(layer, "Status", "sans-bold");
Widget *status_holder = new Widget(layer);
status_holder->set_layout(new GridLayout());
lblStatus = new TextArea(window);
lblStatus = new TextArea(layer);
lblStatus->set_fixed_height(20);
lblStatus->set_font("sans");
lblSubstatus = new TextArea(window);
lblSubstatus = new TextArea(layer);
lblSubstatus->set_font("sans");
lblSubstatus->set_fixed_height(50);
// Machine pos
new Label(window, "Machine pos", "sans-bold");
Widget *mpos = new Widget(window);
mpos->set_layout(new GridLayout());
layer->add<Label>("Machine pos", "sans-bold");
Widget *mpos = new Widget(layer);
mpos->set_layout(new GridLayout(Orientation::Horizontal, 1, Alignment::Middle));
new Label(mpos, "X");
m_pos_x = new Label(mpos, std::to_string(cnc.get_status().machine_pos[0]));
new Label(mpos, "Y");
m_pos_y = new Label(mpos, std::to_string(cnc.get_status().machine_pos[1]));
new Label(mpos, "Z");
m_pos_z = new Label(mpos, std::to_string(cnc.get_status().machine_pos[2]));
auto x_holder = mpos->add<Widget>();
x_holder->set_layout(new BoxLayout(Orientation::Horizontal));
auto lbl = x_holder->add<Label>("X");
lbl->set_font_size(20);
lbl->set_fixed_width(50);
mpos_x_text = x_holder->add<TextBox>(std::to_string(cnc.get_status().work_pos[0]));
mpos_x_text->set_fixed_width(200);
auto y_holder = mpos->add<Widget>();
y_holder->set_layout(new BoxLayout(Orientation::Horizontal));
lbl = y_holder->add<Label>("Y");
lbl->set_font_size(20);
lbl->set_fixed_width(50);
mpos_y_text = y_holder->add<TextBox>(std::to_string(cnc.get_status().work_pos[1]));
mpos_y_text->set_fixed_width(200);
auto z_holder = mpos->add<Widget>();
z_holder->set_layout(new BoxLayout(Orientation::Horizontal));
lbl = z_holder->add<Label>("Z");
lbl->set_font_size(20);
lbl->set_fixed_width(50);
mpos_z_text = z_holder->add<TextBox>(std::to_string(cnc.get_status().work_pos[0]));
mpos_z_text->set_fixed_width(200);
// buttons to change state
new Label(window, "Actions", "sans-bold");
Widget *actions = new Widget(window);
new Label(layer, "Actions", "sans-bold");
Widget *actions = new Widget(layer);
actions->set_layout(new BoxLayout(Orientation::Horizontal));
Button *btnUnlock = new Button(actions, "Unlock");
@@ -155,9 +239,9 @@ public:
// No need to store a pointer, the data structure will be automatically
// freed when the parent window is deleted
new Label(window, "Program", "sans-bold");
new Label(layer, "Program", "sans-bold");
Widget *pgm_actions = new Widget(window);
Widget *pgm_actions = new Widget(layer);
pgm_actions->set_layout(new BoxLayout(Orientation::Horizontal));
@@ -196,6 +280,7 @@ public:
});
btnRunProgram->set_tooltip("Execute program");
// // Alternative construction notation using variadic template
// btnLoadProgram = window->add<Button>("Styled", FA_ROCKET);
// btnLoadProgram->set_background_color(Color(0, 0, 255, 25));
@@ -257,6 +342,17 @@ public:
m_render_pass->set_cull_mode(RenderPass::CullMode::Disabled);
}
void init_program_geometry() {
renderer.update(pgm, cnc);
auto max_pos = renderer.get_extents_max();
auto min_pos = renderer.get_extents_min();
cam_target = (max_pos - min_pos) / 2.0f;
cam_zoom = (max_pos.x - min_pos.x);
cam_src_rotation = glm::quat(1.0, 0.0, 0.0, 0.0);
}
bool resize_event(const Vector2i& size) override {
return Screen::resize_event(size);
}
@@ -270,6 +366,11 @@ public:
grbl::realtime_status_report last_report;
void on_init_completed() override {
fill_in_settings();
fill_in_parameters();
}
void on_realtime_status_report(grbl::realtime_status_report report) override {
if (report == last_report) return;
@@ -285,9 +386,10 @@ public:
}
}
m_pos_x->set_caption(std::to_string(cnc.get_status().machine_pos[0]));
m_pos_y->set_caption(std::to_string(cnc.get_status().machine_pos[1]));
m_pos_z->set_caption(std::to_string(cnc.get_status().machine_pos[2]));
mpos_x_text->set_value(std::to_string(cnc.get_status().work_pos[0]));
mpos_y_text->set_value(std::to_string(cnc.get_status().work_pos[1]));
mpos_z_text->set_value(std::to_string(cnc.get_status().work_pos[2]));
last_report = report;
}
@@ -341,6 +443,11 @@ public:
// return true;
// }
if (key == GLFW_KEY_SPACE && action == GLFW_PRESS && !window->mouse_focused()) {
// reset trackball rotation
cam_src_rotation = glm::quat(1, 0, 0, 0);
}
auto new_jog = jog;
if (key == GLFW_KEY_LEFT_SHIFT) {
@@ -443,50 +550,37 @@ public:
return Widget::scroll_event(p, rel);
} else {
// std::cout << "Scroll event: p:" << p << ", rel:" << rel << std::endl;
cam_zoom -= rel.y() * cam_zoom/10.0f;
cam_zoom -= rel.y() * cam_zoom / 10.0f;
cam_zoom = std::max(cam_zoom, 0.1f);
return true;
}
}
virtual void draw(NVGcontext *ctx) {
// Animate the scrollbar
// m_progress->set_value(std::fmod((float) glfwGetTime() / 10, 1.0f));
// Draw the user interface
Screen::draw(ctx);
}
virtual void draw_contents() {
auto fb_size = framebuffer_size();
renderer.update(pgm, cnc);
// compute mvp
glm::mat4 projection = glm::perspective(45.0f * glm::pi<float>() / 180.0f, (float) fb_size.x() / (float) fb_size.y(), 0.1f, 1000.f);
glm::vec3 cam_src = cam_target + glm::vec3(glm::vec4(0, 0, cam_zoom, 1) * cam_src_rotation);
// glm::mat4 view = glm::lookAt(cam_src, cam_target, glm::vec3{0.0f, 1.0f, 0.0f});
// glm::mat4 view = glm::rotate(glm::mat4(1.0f), glm::pi<float>()/2.0f, glm::vec3(1, 0, 0));
// glm::mat4 view = glm::toMat4(cam_src_rotation) * glm::rotate(glm::mat4(1.0f), glm::pi<float>()/2.0f, glm::vec3(1, 0, 0)) * glm::translate(glm::mat4(1.0f), -cam_target);
// glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, -cam_zoom)) * glm::toMat4(cam_src_rotation) * glm::rotate(glm::mat4(1.0f), glm::pi<float>()/2.0f, glm::vec3(1, 0, 0)) * glm::translate(glm::mat4(1.0f), -cam_target);
glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, -cam_zoom)) *
glm::toMat4(cam_src_rotation) *
// glm::rotate(glm::mat4(1.0f), glm::pi<float>()/2.0f, glm::vec3(1, 0, 0)) *
glm::translate(glm::mat4(1.0f), -cam_target);
// glm::mat4 view = glm::rotate(glm::mat4(1.0f), -glm::pi<float>()/2.0f, glm::vec3(1, 0, 0)) * glm::translate(glm::mat4(1.0f), -cam_src);
glm::mat4 model = glm::mat4(1.0f);
auto mvp = projection * view * model;
// start rendering
m_render_pass->resize(framebuffer_size());
m_render_pass->begin();
renderer.render(mvp, glm::vec2(fb_size.x(), fb_size.y()));
if (pgm.is_loaded) {
renderer.update(pgm, cnc);
// compute mvp
glm::mat4 projection = glm::perspective(45.0f * glm::pi<float>() / 180.0f,
(float) fb_size.x() / (float) fb_size.y(),
0.1f,
10000.f);
glm::mat4 view = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, -cam_zoom)) *
glm::toMat4(cam_src_rotation) *
glm::translate(glm::mat4(1.0f), -cam_target);
glm::mat4 model = glm::mat4(1.0f);
auto mvp = projection * view * model;
renderer.render(mvp, glm::vec2(fb_size.x(), fb_size.y()));
}
m_render_pass->end();
}
+132 -75
View File
@@ -63,6 +63,14 @@ void grbl::program_renderer::render(glm::mat4 mvp, glm::vec2 viewport_size) {
glBindVertexArray(vao_id);
glDrawArrays(GL_LINES, 0, vertices_count);
auto spindle_xform = glm::translate(glm::mat4(1.0f), spindle_pos);
auto spindle_view = mvp * spindle_xform;
shader->set_mat4(glm::value_ptr(spindle_view), "mvp");
glBindVertexArray(spindle_vao_id);
glDrawArrays(GL_LINES, 0, spindle_vertices_count);
shader->unbind();
// draw bit location
@@ -73,24 +81,8 @@ void grbl::program_renderer::update(const grbl::program& pgm, const grbl::machin
shader = new shader_program(vs_code, ps_code);
glGenBuffers(1, &vbo_id);
glGenVertexArrays(1, &vao_id);
// vertex format: x, y, z, r, g, b, a
// stride: 28 bytes
const GLsizei sizeOfVertexInBytes = 28;
glBindVertexArray(vao_id);
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
glEnableVertexAttribArray(0); // vertices on stream 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeOfVertexInBytes, (void *) 0);
glEnableVertexAttribArray(1); // vertex colors on stream 1
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeOfVertexInBytes, (void *) (sizeof(float) * 3));
// unbind vao
glBindVertexArray(0);
initialize_spindle_buffers();
initialize_program_buffers();
initialized = true;
}
@@ -98,6 +90,88 @@ void grbl::program_renderer::update(const grbl::program& pgm, const grbl::machin
// update program with machine status
// build vbo and vao
vertices_count = build_vbo(pgm);
auto x = cnc.get_status().work_pos;
spindle_pos = glm::vec3(x[0], x[1], x[2]);
}
void grbl::program_renderer::initialize_spindle_buffers() {
glGenBuffers(1, &spindle_vbo_id);
glGenVertexArrays(1, &spindle_vao_id);
// vertex format: x, y, z, r, g, b, a
// stride: 28 bytes
const GLsizei sizeOfVertexInBytes = 28;
glBindVertexArray(spindle_vao_id);
glBindBuffer(GL_ARRAY_BUFFER, spindle_vbo_id);
glEnableVertexAttribArray(0); // vertices on stream 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeOfVertexInBytes, (void *) 0);
glEnableVertexAttribArray(1); // vertex colors on stream 1
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeOfVertexInBytes, (void *) (sizeof(float) * 3));
// unbind vao
glBindVertexArray(0);
// construct spindle model
glm::vec4 col(1, 1, 0, 1); // yellow
std::vector<float> buffer_data;
const float spindle_length = 3.5;
const size_t cone_granularity = 20;
for (int i = 0; i < cone_granularity; i++) {
float x = sinf((i / (float) cone_granularity) * 2.0f * M_PI);
float y = cosf((i / (float) cone_granularity) * 2.0f * M_PI);
// from
buffer_data.push_back(0);
buffer_data.push_back(0);
buffer_data.push_back(0);
buffer_data.push_back(col.r);
buffer_data.push_back(col.g);
buffer_data.push_back(col.b);
buffer_data.push_back(col.a);
// to
buffer_data.push_back(x);
buffer_data.push_back(y);
buffer_data.push_back(spindle_length);
buffer_data.push_back(col.r);
buffer_data.push_back(col.g);
buffer_data.push_back(col.b);
buffer_data.push_back(col.a);
}
glBindBuffer(GL_ARRAY_BUFFER, spindle_vbo_id);
glBufferData(GL_ARRAY_BUFFER, sizeOfVertexInBytes * buffer_data.size(), buffer_data.data(), GL_STATIC_DRAW);
spindle_vertices_count = buffer_data.size() * sizeof(float) / sizeOfVertexInBytes;
}
void grbl::program_renderer::initialize_program_buffers() {
glGenBuffers(1, &vbo_id);
glGenVertexArrays(1, &vao_id);
// vertex format: x, y, z, r, g, b, a
// stride: 28 bytes
const GLsizei sizeOfVertexInBytes = 28;
glBindVertexArray(vao_id);
glBindBuffer(GL_ARRAY_BUFFER, vbo_id);
glEnableVertexAttribArray(0); // vertices on stream 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeOfVertexInBytes, (void *) 0);
glEnableVertexAttribArray(1); // vertex colors on stream 1
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeOfVertexInBytes, (void *) (sizeof(float) * 3));
// unbind vao
glBindVertexArray(0);
}
GLsizei grbl::program_renderer::build_vbo(const grbl::program& pgm) {
@@ -203,85 +277,68 @@ GLsizei grbl::program_renderer::build_vbo(const grbl::program& pgm) {
return number_of_vertices;
}
void check_compile_error(GLuint shader_id) {
GLint isCompiled = 0;
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &isCompiled);
if (!isCompiled) {
GLint maxLength = 0;
glGetShaderiv(shader_id, GL_INFO_LOG_LENGTH, &maxLength);
std::string get_shader_info_log(GLuint id) {
GLint log_length = 0;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &log_length);
//The maxLength includes the NULL character
char errorLog[maxLength];
glGetShaderInfoLog(shader_id, maxLength, &maxLength, &errorLog[0]);
char error_log[log_length]; // length includes the NULL character
glGetShaderInfoLog(id, log_length, &log_length, &error_log[0]);
std::cerr << "Shader compile error: " << "(id: " << shader_id << ", code: " << isCompiled << ", bytes: "
<< maxLength << ") - " << std::string(errorLog, maxLength);
//Provide the infolog in whatever manor you deem best.
//Exit with failure.
glDeleteShader(shader_id); //Don't leak the shader.
} else {
std::cout << "Shader " << shader_id << " successfully compiled";
}
return std::string(error_log, log_length);
}
void check_link_error(GLuint program_id) {
std::string get_program_info_log(GLuint id) {
GLint log_length = 0;
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &log_length);
char error_log[log_length]; // length includes the NULL character
glGetProgramInfoLog(id, log_length, &log_length, &error_log[0]);
return std::string(error_log, log_length);
}
bool check_compile_error(GLuint shader_id) {
GLint is_compiled = 0;
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &is_compiled);
if (is_compiled == GL_TRUE)
return true;
std::cerr << "Shader compile error: " << "(id: " << shader_id << ") - " << get_shader_info_log(shader_id) << std::endl;
glDeleteShader(shader_id);
return false;
}
bool check_link_error(GLuint program_id) {
GLint is_linked = 0;
glGetShaderiv(program_id, GL_LINK_STATUS, &is_linked);
if (!is_linked) {
GLint maxLength = 0;
glGetShaderiv(program_id, GL_INFO_LOG_LENGTH, &maxLength);
glGetProgramiv(program_id, GL_LINK_STATUS, &is_linked);
if (is_linked == GL_TRUE)
return true;
//The maxLength includes the NULL character
char errorLog[maxLength];
glGetShaderInfoLog(program_id, maxLength, &maxLength, &errorLog[0]);
std::cerr << "Shader program link error: " << "(id: " << program_id << ", code: " << is_linked << ", bytes: "
<< maxLength << ") - " << std::string(errorLog, maxLength);
//Provide the info log in whatever manner you deem best.
//Exit with failure.
glDeleteShader(program_id); //Don't leak the program shader.
} else {
std::cout << "Shader program " << program_id << " successfully linked";
}
std::cerr << "Shader program link error: " << "(id: " << program_id << ") - " << get_program_info_log(program_id) << std::endl;
glDeleteProgram(program_id);
return false;
}
grbl::shader_program::shader_program(const char *vs_content, const char *ps_content)
: shader_ids{0, 0},
program_id{0} {
shader_ids[0] = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(shader_ids[0], 1, &vs_content, NULL);
glShaderSource(shader_ids[0], 1, &vs_content, nullptr);
glCompileShader(shader_ids[0]);
int success;
char infoLog[512];
glGetShaderiv(shader_ids[0], GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader_ids[0], 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
check_compile_error(shader_ids[0]);
shader_ids[1] = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(shader_ids[1], 1, &ps_content, NULL);
glShaderSource(shader_ids[1], 1, &ps_content, nullptr);
glCompileShader(shader_ids[1]);
glGetShaderiv(shader_ids[1], GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(shader_ids[1], 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
check_compile_error(shader_ids[1]);
program_id = glCreateProgram();
glAttachShader(program_id, shader_ids[0]);
glAttachShader(program_id, shader_ids[1]);
glLinkProgram(program_id);
glGetProgramiv(program_id, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(program_id, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n" << infoLog << std::endl;
}
check_link_error(program_id);
glUseProgram(program_id);
}
+12 -2
View File
@@ -15,17 +15,27 @@ public:
void render(glm::mat4 mvp, glm::vec2 viewport_size);
glm::vec3 get_extents_min() const { return min_pos; };
glm::vec3 get_extents_max() const { return max_pos; };
private:
GLsizei build_vbo(const grbl::program& pgm);
GLuint spindle_vbo_id;
GLuint spindle_vao_id;
GLuint vbo_id;
GLuint vao_id;
shader_program *shader = nullptr;
bool initialized = false;
glm::vec3 min_pos, max_pos;
GLsizei vertices_count;
glm::vec3 min_pos, max_pos, spindle_pos;
GLsizei vertices_count, spindle_vertices_count;
void initialize_program_buffers();
void initialize_spindle_buffers();
};