534 lines
18 KiB
C++
534 lines
18 KiB
C++
/*
|
|
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>
|
|
#include <nanogui/layout.h>
|
|
#include <nanogui/label.h>
|
|
#include <nanogui/checkbox.h>
|
|
#include <nanogui/button.h>
|
|
#include <nanogui/toolbutton.h>
|
|
#include <nanogui/popupbutton.h>
|
|
#include <nanogui/combobox.h>
|
|
#include <nanogui/progressbar.h>
|
|
#include <nanogui/icons.h>
|
|
#include <nanogui/messagedialog.h>
|
|
#include <nanogui/textbox.h>
|
|
#include <nanogui/slider.h>
|
|
#include <nanogui/imagepanel.h>
|
|
#include <nanogui/imageview.h>
|
|
#include <nanogui/vscrollpanel.h>
|
|
#include <nanogui/colorwheel.h>
|
|
#include <nanogui/colorpicker.h>
|
|
#include <nanogui/graph.h>
|
|
#include <nanogui/tabwidget.h>
|
|
#include <nanogui/texture.h>
|
|
#include <nanogui/textarea.h>
|
|
#include <nanogui/shader.h>
|
|
#include <nanogui/renderpass.h>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
#define STB_IMAGE_STATIC
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#if defined(_MSC_VER)
|
|
# pragma warning (disable: 4505) // don't warn about dead code in stb_image.h
|
|
#elif defined(__GNUC__)
|
|
# pragma GCC diagnostic ignored "-Wunused-function"
|
|
#endif
|
|
|
|
#include <stb_image.h>
|
|
#include "grbl.h"
|
|
#include <gtest/gtest.h>
|
|
#include <regex>
|
|
#include "grbl_communication.h"
|
|
#include "grbl_machine.h"
|
|
#include "string_utils.h"
|
|
#include "render.h"
|
|
|
|
#include <glm/vec3.hpp> // glm::vec3
|
|
#include <glm/vec4.hpp> // glm::vec4
|
|
#include <glm/mat4x4.hpp> // glm::mat4
|
|
#include <glm/ext/matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale
|
|
#include <glm/ext/matrix_clip_space.hpp> // glm::perspective
|
|
#include <glm/ext/scalar_constants.hpp> // glm::pi
|
|
|
|
using namespace nanogui;
|
|
|
|
grbl::machine cnc{};
|
|
|
|
|
|
class SenderApp : public Screen, public grbl::machine_listener {
|
|
public:
|
|
|
|
Window *window;
|
|
grbl::jog_state jog;
|
|
Label *m_pos_x, *m_pos_y, *m_pos_z;
|
|
TextArea *lblStatus, *lblSubstatus;
|
|
nanogui::Color colRed = nanogui::Color(255, 0, 0, 255);
|
|
nanogui::Color colGreen = nanogui::Color(0, 255, 0, 255);
|
|
nanogui::Color colBg;
|
|
int last_alarm = 0;
|
|
grbl::program pgm;
|
|
Button *btnLoadProgram, *btnCheckProgram, *btnRunProgram;
|
|
|
|
|
|
grbl::program_renderer renderer;
|
|
|
|
glm::vec3 cam_target = glm::vec3(0);
|
|
glm::vec3 cam_src = glm::vec3(0);
|
|
glm::vec2 cam_rotate = {0, 0};
|
|
|
|
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);
|
|
|
|
|
|
new Label(window, "Status", "sans-bold");
|
|
Widget *status_holder = new Widget(window);
|
|
status_holder->set_layout(new GridLayout());
|
|
|
|
lblStatus = new TextArea(window);
|
|
lblStatus->set_fixed_height(20);
|
|
lblStatus->set_font("sans");
|
|
|
|
lblSubstatus = new TextArea(window);
|
|
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());
|
|
|
|
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]));
|
|
|
|
// buttons to change state
|
|
new Label(window, "Actions", "sans-bold");
|
|
Widget *actions = new Widget(window);
|
|
actions->set_layout(new BoxLayout(Orientation::Horizontal));
|
|
|
|
Button *btnUnlock = new Button(actions, "Unlock");
|
|
btnUnlock->set_callback([&] {
|
|
cnc.request_unlock();
|
|
});
|
|
Button *btnHome = new Button(actions, "Home");
|
|
btnHome->set_callback([&] {
|
|
cnc.request_home();
|
|
});
|
|
Button *btnReset = new Button(actions, "Reset");
|
|
btnReset->set_background_color(colRed);
|
|
btnReset->set_callback([&] {
|
|
cnc.request_reset();
|
|
});
|
|
Button *btnCycleStart = new Button(actions, "Cycle Start");
|
|
btnCycleStart->set_callback([&] {
|
|
cnc.request_cycle_start();
|
|
});
|
|
Button *btnFeedHold = new Button(actions, "Feed Hold");
|
|
btnFeedHold->set_callback([&] {
|
|
cnc.request_feed_hold();
|
|
});
|
|
|
|
// 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");
|
|
|
|
Widget *pgm_actions = new Widget(window);
|
|
pgm_actions->set_layout(new BoxLayout(Orientation::Horizontal));
|
|
|
|
|
|
btnLoadProgram = new Button(pgm_actions, "Load");
|
|
btnLoadProgram->set_callback([&] {
|
|
auto path = file_dialog(
|
|
{{"gcode", "G-Code files"},
|
|
{"nc", "G-Code files"},
|
|
{"ngc", "G-Code files"}}, true);
|
|
|
|
btnCheckProgram->set_background_color(colBg);
|
|
btnRunProgram->set_background_color(colBg);
|
|
|
|
if (pgm.load_from_file(path)) {
|
|
btnCheckProgram->set_enabled(true);
|
|
// btnRunProgram->set_enabled(true);
|
|
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_src = cam_target;
|
|
cam_src.z = (max_pos.x - min_pos.x);
|
|
|
|
cam_rotate = {0, 0};
|
|
} else {
|
|
btnCheckProgram->set_enabled(false);
|
|
btnRunProgram->set_enabled(false);
|
|
}
|
|
});
|
|
btnLoadProgram->set_tooltip("Load program");
|
|
|
|
btnCheckProgram = new Button(pgm_actions, "Check");
|
|
btnCheckProgram->set_enabled(false);
|
|
btnCheckProgram->set_callback([&] {
|
|
cnc.check_program(pgm);
|
|
});
|
|
btnCheckProgram->set_tooltip("Check program");
|
|
|
|
btnRunProgram = new Button(pgm_actions, "Run");
|
|
btnRunProgram->set_enabled(false);
|
|
btnRunProgram->set_callback([&] {
|
|
cnc.run_program(pgm);
|
|
});
|
|
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));
|
|
// btnLoadProgram->set_callback([] { std::cout << "pushed!" << std::endl; });
|
|
// btnLoadProgram->set_tooltip("This button has a fairly long tooltip. It is so long, in "
|
|
// "fact, that the shown text will span several lines.");
|
|
//
|
|
// new Label(window, "Toggle buttons", "sans-bold");
|
|
// btnLoadProgram = new Button(window, "Toggle me");
|
|
// btnLoadProgram->set_flags(Button::ToggleButton);
|
|
// btnLoadProgram->set_change_callback([](bool state) { std::cout << "Toggle button state: " << state << std::endl; });
|
|
//
|
|
// new Label(window, "Radio buttons", "sans-bold");
|
|
// btnLoadProgram = new Button(window, "Radio button 1");
|
|
// btnLoadProgram->set_flags(Button::RadioButton);
|
|
// btnLoadProgram = new Button(window, "Radio button 2");
|
|
// btnLoadProgram->set_flags(Button::RadioButton);
|
|
//
|
|
// new Label(window, "A tool palette", "sans-bold");
|
|
// Widget *tools = new Widget(window);
|
|
// tools->set_layout(new BoxLayout(Orientation::Horizontal,
|
|
// Alignment::Middle, 0, 6));
|
|
//
|
|
// btnLoadProgram = new ToolButton(tools, FA_CLOUD);
|
|
// btnLoadProgram = new ToolButton(tools, FA_FAST_FORWARD);
|
|
// btnLoadProgram = new ToolButton(tools, FA_COMPASS);
|
|
// btnLoadProgram = new ToolButton(tools, FA_UTENSILS);
|
|
//
|
|
// new Label(window, "Popup buttons", "sans-bold");
|
|
// PopupButton *popup_btn = new PopupButton(window, "Popup", FA_FLASK);
|
|
// Popup *popup = popup_btn->popup();
|
|
// popup->set_layout(new GroupLayout());
|
|
// new Label(popup, "Arbitrary widgets can be placed here");
|
|
// new CheckBox(popup, "A check box");
|
|
// // popup right
|
|
// popup_btn = new PopupButton(popup, "Recursive popup", FA_CHART_PIE);
|
|
// Popup *popup_right = popup_btn->popup();
|
|
// popup_right->set_layout(new GroupLayout());
|
|
// new CheckBox(popup_right, "Another check box");
|
|
// // popup left
|
|
// popup_btn = new PopupButton(popup, "Recursive popup", FA_DNA);
|
|
// popup_btn->set_side(Popup::Side::Left);
|
|
// Popup *popup_left = popup_btn->popup();
|
|
// popup_left->set_layout(new GroupLayout());
|
|
// new CheckBox(popup_left, "Another check box");
|
|
|
|
perform_layout();
|
|
|
|
// All NanoGUI widgets are initialized at this point. Now
|
|
// create shaders to draw the main window contents.
|
|
//
|
|
// NanoGUI comes with a simple wrapper around OpenGL 3, which
|
|
// eliminates most of the tedious and error-prone shader and buffer
|
|
// object management.
|
|
|
|
m_render_pass = new RenderPass({this});
|
|
m_render_pass->set_clear_color(0, Color(0.3f, 0.3f, 0.32f, 1.f));
|
|
m_render_pass->set_depth_test(RenderPass::DepthTest::Always, true);
|
|
m_render_pass->set_cull_mode(RenderPass::CullMode::Disabled);
|
|
}
|
|
|
|
bool resize_event(const Vector2i& size) override {
|
|
return Screen::resize_event(size);
|
|
}
|
|
|
|
void on_connected() override {
|
|
}
|
|
|
|
void on_disconnected() override {
|
|
}
|
|
|
|
|
|
grbl::realtime_status_report last_report;
|
|
|
|
void on_realtime_status_report(grbl::realtime_status_report report) override {
|
|
if (report == last_report) return;
|
|
|
|
if (last_report.status != report.status) {
|
|
lblStatus->clear();
|
|
lblStatus->append(grbl::status_to_string(cnc.get_status().status));
|
|
|
|
lblSubstatus->clear();
|
|
if (cnc.get_status().status == grbl::machine_status::alarm) {
|
|
lblSubstatus->append(grbl::alarm_to_string(last_alarm));
|
|
} else {
|
|
lblSubstatus->append(cnc.get_status().sub_status);
|
|
}
|
|
}
|
|
|
|
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]));
|
|
|
|
last_report = report;
|
|
}
|
|
|
|
void on_banner(std::string line) override {
|
|
}
|
|
|
|
void on_message(std::string message) override {
|
|
set_caption(message);
|
|
}
|
|
|
|
void on_alarm(int alarm) override {
|
|
last_alarm = alarm;
|
|
}
|
|
|
|
void on_check_completed(bool success, size_t failed_index, size_t error) override {
|
|
btnCheckProgram->set_background_color(success ? colGreen : colRed);
|
|
if (success) {
|
|
btnRunProgram->set_enabled(true);
|
|
new MessageDialog(this, MessageDialog::Type::Information, "Check result", "Program checked successfully");
|
|
} else {
|
|
btnRunProgram->set_enabled(false);
|
|
std::stringstream ss;
|
|
auto i = pgm.instruction_at(failed_index);
|
|
ss << "Program check failed at line " << i.line << " (" << i.command << "). Error: " << error << ", "
|
|
<< grbl::error_to_string(error);
|
|
new MessageDialog(this, MessageDialog::Type::Warning, "Check result", ss.str());
|
|
}
|
|
}
|
|
|
|
void on_run_completed(bool success, size_t failed_index, size_t error) override {
|
|
btnRunProgram->set_background_color(success ? colGreen : colRed);
|
|
if (success) {
|
|
new MessageDialog(this, MessageDialog::Type::Information, "Run result", "Program executed successfully");
|
|
} else {
|
|
std::stringstream ss;
|
|
auto i = pgm.instruction_at(failed_index);
|
|
ss << "Program execution failed at line " << i.line << " (" << i.command << "). Error: " << error << ", "
|
|
<< grbl::error_to_string(error);
|
|
new MessageDialog(this, MessageDialog::Type::Warning, "Run result", ss.str());
|
|
}
|
|
}
|
|
|
|
|
|
bool keyboard_event(int key, int scancode, int action, int modifiers) override {
|
|
if (Screen::keyboard_event(key, scancode, action, modifiers))
|
|
return true;
|
|
|
|
// if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
|
|
// set_visible(false);
|
|
// return true;
|
|
// }
|
|
|
|
auto new_jog = jog;
|
|
|
|
if (key == GLFW_KEY_LEFT_SHIFT) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.speed_fast_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.speed_fast_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_LEFT_CONTROL) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.speed_slow_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.speed_slow_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_UP) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.up_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.up_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_DOWN) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.down_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.down_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_LEFT) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.left_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.left_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_RIGHT) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.right_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.right_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_PAGE_UP) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.z_up_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.z_up_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (key == GLFW_KEY_PAGE_DOWN) {
|
|
if (action == GLFW_PRESS) {
|
|
new_jog.z_down_pressed = true;
|
|
} else if (action == GLFW_RELEASE) {
|
|
new_jog.z_down_pressed = false;
|
|
}
|
|
}
|
|
|
|
if (jog != new_jog) {
|
|
cnc.request_jog(new_jog);
|
|
jog = new_jog;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool mouse_motion_event(const Vector2i& p, const Vector2i& rel, int button, int modifiers) override {
|
|
auto result = Widget::mouse_motion_event(p, rel, button, modifiers);
|
|
if (window->mouse_focused()) {
|
|
return result;
|
|
} else {
|
|
std::cout << "Mouse motion p:" << p << ", rel:" << rel << ", button:" << button << ", .modifiers:" << modifiers << std::endl;
|
|
if (button == 1) {
|
|
cam_rotate += glm::vec2((float) rel.x() / 100.0f, (float) rel.y() / 100.0f);
|
|
} else if (button == 2) {
|
|
cam_src.x += (float) rel.x() / 10.0f;
|
|
cam_src.y += (float) rel.y() / 10.0f;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool scroll_event(const Vector2i& p, const Vector2f& rel) override {
|
|
if (window->mouse_focused()) {
|
|
return Widget::scroll_event(p, rel);
|
|
} else {
|
|
std::cout << "Scroll event: p:" << p << ", rel:" << rel << std::endl;
|
|
cam_src.z += rel.y();
|
|
cam_src.z = std::max(cam_src.z, 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::mat4 view = glm::lookAt(cam_src, cam_target, glm::vec3{0.0f, 1.0f, 0.0f});
|
|
view = glm::rotate(view, cam_rotate.y, glm::vec3(-1.0f, 0.0f, 0.0f));
|
|
view = glm::rotate(view, cam_rotate.x, glm::vec3(0.0f, 1.0f, 0.0f));
|
|
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()));
|
|
|
|
m_render_pass->end();
|
|
}
|
|
|
|
private:
|
|
ProgressBar *m_progress;
|
|
ref<RenderPass> m_render_pass;
|
|
|
|
using ImageHolder = std::unique_ptr<uint8_t[], void (*)(void *)>;
|
|
std::vector<std::pair<ref<Texture>, ImageHolder>> m_images;
|
|
int m_current_image;
|
|
};
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
testing::InitGoogleTest(&argc, argv);
|
|
auto result = RUN_ALL_TESTS();
|
|
if (result) {
|
|
exit(result);
|
|
}
|
|
|
|
try {
|
|
nanogui::init();
|
|
|
|
// scoped variables
|
|
{
|
|
ref<SenderApp> app = new SenderApp();
|
|
app->dec_ref();
|
|
app->draw_all();
|
|
app->set_visible(true);
|
|
|
|
cnc.set_listener(app);
|
|
cnc.connect();
|
|
|
|
nanogui::mainloop(1 / 60.f * 1000);
|
|
}
|
|
|
|
nanogui::shutdown();
|
|
} catch (const std::exception& e) {
|
|
std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what());
|
|
std::cerr << error_msg << std::endl;
|
|
return -1;
|
|
} catch (...) {
|
|
std::cerr << "Caught an unknown error!" << std::endl;
|
|
return -2;
|
|
}
|
|
|
|
return 0;
|
|
}
|