#include "sender_app.h" SenderApp::SenderApp() : nanogui::Screen(nanogui::Vector2i(1024, 768), "GRBL Sender") { inc_ref(); window = new nanogui::Window(this, "Machine status"); window->set_position(nanogui::Vector2i(0, 0)); window->set_layout(new nanogui::BoxLayout(nanogui::Orientation::Vertical)); tab_widget = window->add(); tab_widget->set_tabs_draggable(true); tab_widget->set_callback([&](int index) { tab_widget->set_selected_index(index); }); info_vscroll = tab_widget->add(); tab_widget->append_tab("Info", info_vscroll); info_layer = info_vscroll->add(); info_layer->set_layout(new nanogui::GroupLayout(10, 20, 30, 0)); info_layer->add(""); auto probe_layer = tab_widget->add(); probe_layer->set_layout(new nanogui::GroupLayout(10, 20, 30, 0)); probe_layer->add(""); add_status_markup(); add_dro_markup(); add_program_extents(); add_pins_markup(); add_jogging_markup(); add_work_parameters_markup(); add_z_probe_markup(); heightmap_layer = tab_widget->add(); heightmap_layer->set_layout(new nanogui::GroupLayout(10, 20, 30, 0)); heightmap_layer->add(""); tab_widget->append_tab("Heightmap", heightmap_layer); add_heightmap_markup(); perform_layout(); m_render_pass = new nanogui::RenderPass({this}, nullptr, nullptr, nullptr, true); m_render_pass->set_clear_color(0, nanogui::Color(0.13f, 0.13f, 0.13f, 1.f)); m_render_pass->set_depth_test(nanogui::RenderPass::DepthTest::Greater, true); m_render_pass->set_cull_mode(nanogui::RenderPass::CullMode::Disabled); cnc.connect(); } void SenderApp::add_z_probe_markup() { info_layer->add("Z-Probe", "sans-bold", 20); auto z_probe_holder = info_layer->add(); z_probe_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 2, 2)); auto another_holder = z_probe_holder->add(); another_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill, 2, 2)); another_holder->add("Feed", "sans-bold"); txt_feed = another_holder->add("100"); txt_feed->set_editable(true); txt_feed->set_fixed_width(100); another_holder->add("Min Z", "sans-bold"); txt_min_z = another_holder->add("-65"); txt_min_z->set_editable(true); txt_min_z->set_fixed_width(100); z_probe_holder->add(""); auto btn_start_probing = z_probe_holder->add("Start probing"); btn_start_probing->set_callback([&]() { cnc.start_z_probe(std::stof(txt_min_z->value()), std::stof(txt_feed->value())); }); } void SenderApp::add_work_parameters_markup() { // work parameters info_layer->add("Work parameters", "sans-bold", 20); auto work_holder = info_layer->add(); work_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Fill, 6, 6)); auto work_data_holder = work_holder->add(); work_data_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill, 6, 6)); work_data_holder->add("Offset", "sans-bold"); std::vector offset_items = {"G54", "G55", "G56", "G57", "G58", "G59"}; cbo_work_offset = work_data_holder->add(offset_items); cbo_work_offset->set_callback([&](int idx) { refresh_offset(); update_dro(); }); work_data_holder->add("Tool", "sans-bold"); std::vector tool_items = {"None"}; cbo_tool = work_data_holder->add(tool_items); auto work_btn_holder = work_holder->add(); work_btn_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 6)); btn_load_program = work_btn_holder->add("Load"); btn_load_program->set_callback([&] { auto path = nanogui::file_dialog( {{"gcode", "G-Code files"}, {"nc", "G-Code files"}, {"ngc", "G-Code files"}}, true); if (pgm.load_from_file(path)) { init_program_geometry(); set_program_extents(); fill_heightmap_from_model(); update_grid(); } else { } }); btn_load_program->set_tooltip("Load program"); btn_check_program = work_btn_holder->add("Check"); // btnCheckProgram->set_enabled(false); btn_check_program->set_callback([&] { cnc.check_program(pgm); }); btn_check_program->set_tooltip("Check program"); btn_run_program = work_btn_holder->add("Run"); // btnRunProgram->set_enabled(false); btn_run_program->set_callback([&] { // auto leveled_pgm = pgm.apply_heightmap(heightmap_grid); // leveled_pgm.save("output_corrected6.nc"); cnc.run_program(pgm, "G" + std::to_string(cbo_work_offset->selected_index() + 54)); }); btn_run_program->set_tooltip("Execute program"); } void SenderApp::add_status_markup() { auto status_holder = info_layer->add(); status_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); auto status_text_holder = status_holder->add(); status_text_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 0)); status_text_holder->add("Status", "sans-bold", 20); txt_status = status_text_holder->add(); txt_status->set_editable(false); txt_status->set_fixed_width(300); auto *btn_reset = status_text_holder->add("Reset"); btn_reset->set_background_color(color_red); btn_reset->set_callback([&] { cnc.request_reset(); }); // status_text_holder->add(""); txt_message = status_text_holder->add(""); txt_message->set_fixed_width(300); status_text_holder->add(""); // buttons auto status_btn_holder = status_holder->add(); status_btn_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 2, nanogui::Alignment::Fill, 6, 6)); auto *btn_homing = new nanogui::Button(status_btn_holder, "Homing", FA_HOME); btn_homing->set_callback([&] { cnc.request_home(); }); auto *btn_unlock = new nanogui::Button(status_btn_holder, "Unlock", FA_UNLOCK); btn_unlock->set_callback([&] { cnc.request_unlock(); }); // buttons to change state auto *btn_cycle_start = status_btn_holder->add("Cycle Start", FA_PLAY); btn_cycle_start->set_callback([&] { cnc.request_cycle_start(); }); auto *btn_feed_hold = status_btn_holder->add("Feed Hold", FA_PAUSE); btn_feed_hold->set_callback([&] { cnc.request_feed_hold(); }); } void SenderApp::add_jogging_markup() { // jogging info_layer->add("Jogging", "sans-bold", 20); auto holder = new nanogui::Widget(info_layer); holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 0)); // feed and distance auto jog_params_holder = holder->add(); jog_params_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 4)); jog_params_holder->add("Feed", "sans-bold"); cbo_jog_feed_rates = jog_params_holder->add(jog_feed_rates); cbo_jog_feed_rates->set_selected_index(2); jog_params_holder->add("mm/min"); jog_params_holder->add("Distance", "sans-bold"); cbo_jog_distance = jog_params_holder->add(jog_distances); cbo_jog_distance->set_selected_index(2); jog_params_holder->add("mm"); jog_params_holder->add("Keyboard Jog", "sans-bold"); btn_keyboard_jog = jog_params_holder->add("", FA_KEYBOARD); btn_keyboard_jog->set_flags(nanogui::Button::ToggleButton); jog_params_holder->add(""); // X and Y auto jog_btn_holder = holder->add(); jog_btn_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 0, 6)); jog_btn_holder->add(""); auto jog_btn = jog_btn_holder->add("", FA_ARROW_ALT_CIRCLE_UP); jog_btn->set_callback([&]() { // FIXME: get rid of std::stof cnc.request_jog_fixed(grbl::jog_direction::y_up, std::stof(jog_distances[cbo_jog_distance->selected_index()]), std::stof(jog_feed_rates[cbo_jog_feed_rates->selected_index()])); }); jog_btn_holder->add(""); jog_btn = jog_btn_holder->add("", FA_ARROW_ALT_CIRCLE_LEFT); jog_btn->set_callback([&]() { // FIXME: get rid of std::stof cnc.request_jog_fixed(grbl::jog_direction::x_down, std::stof(jog_distances[cbo_jog_distance->selected_index()]), std::stof(jog_feed_rates[cbo_jog_feed_rates->selected_index()])); }); jog_btn = jog_btn_holder->add("", FA_HOME); jog_btn->set_callback([&]() { cnc.go_to_zero(true, true, false); }); jog_btn = jog_btn_holder->add("", FA_ARROW_ALT_CIRCLE_RIGHT); jog_btn->set_callback([&]() { // FIXME: get rid of std::stof cnc.request_jog_fixed(grbl::jog_direction::x_up, std::stof(jog_distances[cbo_jog_distance->selected_index()]), std::stof(jog_feed_rates[cbo_jog_feed_rates->selected_index()])); }); jog_btn_holder->add(""); jog_btn = jog_btn_holder->add("", FA_ARROW_ALT_CIRCLE_DOWN); jog_btn->set_callback([&]() { // FIXME: get rid of std::stof cnc.request_jog_fixed(grbl::jog_direction::y_down, std::stof(jog_distances[cbo_jog_distance->selected_index()]), std::stof(jog_feed_rates[cbo_jog_feed_rates->selected_index()])); }); jog_btn_holder->add(""); // z up/down auto jog_z_btn_holder = holder->add(); jog_z_btn_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 6)); jog_btn = jog_z_btn_holder->add("", FA_CHEVRON_UP); jog_btn->set_callback([&]() { // FIXME: get rid of std::stof cnc.request_jog_fixed(grbl::jog_direction::z_up, std::stof(jog_distances[cbo_jog_distance->selected_index()]), std::stof(jog_feed_rates[cbo_jog_feed_rates->selected_index()])); }); jog_btn = jog_z_btn_holder->add("", FA_HOME); jog_btn->set_callback([&]() { cnc.go_to_zero(false, false, true); }); jog_btn = jog_z_btn_holder->add("", FA_CHEVRON_DOWN); jog_btn->set_callback([&]() { // FIXME: get rid of std::stof cnc.request_jog_fixed(grbl::jog_direction::z_down, std::stof(jog_distances[cbo_jog_distance->selected_index()]), std::stof(jog_feed_rates[cbo_jog_feed_rates->selected_index()])); }); } void SenderApp::add_dro_markup() { dro_ss << std::setprecision(3) << std::fixed; // DRO info_layer->add("DRO", "sans-bold", 20); auto dro_holder = info_layer->add(); dro_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 1, nanogui::Alignment::Middle, 0, 6)); auto x_holder = dro_holder->add(); x_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 0, 6)); auto lbl = x_holder->add("X", "sans-bold", 20); lbl->set_fixed_width(30); mpos_x_text = x_holder->add(std::to_string(cnc.get_status().machine_pos[0])); mpos_x_text->set_fixed_width(200); auto zero_btn = x_holder->add(FA_BAN); zero_btn->set_flags(nanogui::Button::NormalButton); zero_btn->set_tooltip("Zero axis"); zero_btn->set_callback([&]() { cnc.zero_offset_axis(cbo_work_offset->selected_index(), 0); }); auto y_holder = dro_holder->add(); y_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 0, 6)); lbl = y_holder->add("Y", "sans-bold", 20); lbl->set_fixed_width(30); mpos_y_text = y_holder->add(std::to_string(cnc.get_status().machine_pos[1])); mpos_y_text->set_fixed_width(200); zero_btn = y_holder->add(FA_BAN); zero_btn->set_flags(nanogui::Button::NormalButton); zero_btn->set_tooltip("Zero axis"); zero_btn->set_callback([&]() { cnc.zero_offset_axis(cbo_work_offset->selected_index(), 1); }); auto z_holder = dro_holder->add(); z_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 0, 6)); lbl = z_holder->add("Z", "sans-bold", 20); lbl->set_fixed_width(30); mpos_z_text = z_holder->add(std::to_string(cnc.get_status().machine_pos[0])); mpos_z_text->set_fixed_width(200); zero_btn = z_holder->add(FA_BAN); zero_btn->set_flags(nanogui::Button::NormalButton); zero_btn->set_tooltip("Zero axis"); zero_btn->set_callback([&]() { cnc.zero_offset_axis(cbo_work_offset->selected_index(), 2); }); auto axis_btn_holder = dro_holder->add(); axis_btn_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 0, 6)); auto zero_all_btn = axis_btn_holder->add("Zero all"); zero_all_btn->set_callback([&]() { cnc.zero_offset(cbo_work_offset->selected_index()); }); auto goto_zero_btn = axis_btn_holder->add("Goto 0"); goto_zero_btn->set_callback([&]() { cnc.go_to_zero(true, true, true); }); } void SenderApp::add_program_extents() { dro_ss << std::setprecision(3) << std::fixed; // Program extents info_layer->add("Program extents", "sans-bold", 20); auto extents_holder = info_layer->add(); extents_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 4, 4)); extents_holder->add("X", "sans-bold", 20); extents_min_x = extents_holder->add(""); extents_max_x = extents_holder->add(""); extents_holder->add("Y", "sans-bold", 20); extents_min_y = extents_holder->add(""); extents_max_y = extents_holder->add(""); extents_holder->add("Z", "sans-bold", 20); extents_min_z = extents_holder->add(""); extents_max_z = extents_holder->add(""); } void SenderApp::save_heightmap(const grbl::heightmap &grid, std::string path) { std::ofstream out(path); if (out) { out << "from_x:" << grid.from_x << std::endl; out << "from_y:" << grid.from_y << std::endl; out << "to_x:" << grid.to_x << std::endl; out << "to_y:" << grid.to_y << std::endl; out << "resolution:" << grid.resolution << std::endl; out << "probes:" << grid.vertices.size() << std::endl; for (auto &v: grid.vertices) { out << v.x << ":" << v.y << ":" << v.z << std::endl; } } } grbl::heightmap SenderApp::load_heightmap(std::string path) { grbl::heightmap result{}; std::ifstream in_file(path); if (in_file) { float from_x, from_y; float to_x, to_y; float resolution; size_t probe_location = 0; for (std::string line; std::getline(in_file, line);) { auto pieces = split_string(line, ":"); if (pieces[0] == "from_x") { from_x = std::stof(pieces[1]); } else if (pieces[0] == "from_y") { from_y = std::stof(pieces[1]); } else if (pieces[0] == "to_x") { to_x = std::stof(pieces[1]); } else if (pieces[0] == "to_y") { to_y = std::stof(pieces[1]); } else if (pieces[0] == "resolution") { resolution = std::stof(pieces[1]); } else if (pieces[0] == "probes") { result = grbl::heightmap::from_params(from_x, from_y, to_x, to_y, resolution); } else { result.vertices[probe_location].x = std::stof(pieces[0]); result.vertices[probe_location].y = std::stof(pieces[1]); result.vertices[probe_location].z = std::stof(pieces[2]); probe_location++; } } } return result; } void SenderApp::fill_heightmap_controls_from_grid(const grbl::heightmap &grid) const { txt_heightmap_from_x->set_value(std::to_string(grid.from_x)); txt_heightmap_from_y->set_value(std::to_string(grid.from_y)); txt_heightmap_to_x->set_value(std::to_string(grid.to_x)); txt_heightmap_to_y->set_value(std::to_string(grid.to_y)); txt_heightmap_res->set_value(std::to_string(grid.resolution)); } void SenderApp::add_heightmap_markup() { heightmap_layer->add("Grid definition", "sans-bold", 20); auto heightmap_params_holder = heightmap_layer->add(); heightmap_params_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 4, 4)); // grid size auto grid_size_holder = heightmap_params_holder->add(); grid_size_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Middle, 4, 4)); grid_size_holder->add("", "sans-bold", 20); grid_size_holder->add("X", "sans-bold", 20); grid_size_holder->add("Y", "sans-bold", 20); grid_size_holder->add("From", "sans-bold", 20); txt_heightmap_from_x = grid_size_holder->add(""); txt_heightmap_from_x->set_editable(true); txt_heightmap_from_x->set_fixed_width(150); txt_heightmap_from_y = grid_size_holder->add(""); txt_heightmap_from_y->set_editable(true); txt_heightmap_from_y->set_fixed_width(150); grid_size_holder->add("To", "sans-bold", 20); txt_heightmap_to_x = grid_size_holder->add(""); txt_heightmap_to_x->set_editable(true); txt_heightmap_to_x->set_fixed_width(150); txt_heightmap_to_y = grid_size_holder->add(""); txt_heightmap_to_y->set_editable(true); txt_heightmap_to_y->set_fixed_width(150); grid_size_holder->add(""); auto btn = grid_size_holder->add("Fetch from model"); btn->set_callback([&]() { fill_heightmap_from_model(); }); auto btn_load_heightmap = grid_size_holder->add("Load from file"); btn_load_heightmap->set_callback([&]() { auto path = nanogui::file_dialog( {{"hmap", "Heightmap files"} }, true); if (!path.empty()) { heightmap_grid = load_heightmap(path); fill_heightmap_controls_from_grid(heightmap_grid); renderer.update_grid(heightmap_grid); } }); txt_heightmap_from_x->set_callback([&](const std::string &new_value) { update_grid(); return true; }); txt_heightmap_from_y->set_callback([&](const std::string &new_value) { update_grid(); return true; }); txt_heightmap_to_x->set_callback([&](const std::string &new_value) { update_grid(); return true; }); txt_heightmap_to_y->set_callback([&](const std::string &new_value) { update_grid(); return true; }); // grid resolution auto grid_res_holder = heightmap_params_holder->add(); grid_res_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 4, 4)); grid_res_holder->add("Grid resolution", "sans-bold", 20); txt_heightmap_res = grid_res_holder->add("5"); txt_heightmap_res->set_editable(true); txt_heightmap_res->set_fixed_width(150); txt_heightmap_res->set_callback([&](const std::string &new_value) { update_grid(); return true; }); grid_res_holder->add("mm", "sans-bold", 20); // business params auto heightmap_business_holder = heightmap_params_holder->add(); heightmap_business_holder->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 3, nanogui::Alignment::Fill, 4, 4)); heightmap_business_holder->add("Clearance height Z", "sans-bold", 20); txt_clearance_height = heightmap_business_holder->add("1.5"); txt_clearance_height->set_editable(true); heightmap_business_holder->add("", "sans-bold"); heightmap_business_holder->add("Start probing at Z", "sans-bold", 20); txt_start_probing_at = heightmap_business_holder->add("0.5"); txt_start_probing_at->set_editable(true); heightmap_business_holder->add("", "sans-bold"); heightmap_business_holder->add("Max negative Z", "sans-bold", 20); txt_max_negative_z = heightmap_business_holder->add("-1.0"); txt_max_negative_z->set_editable(true); heightmap_business_holder->add("(when to fail probing)", "sans-bold"); heightmap_business_holder->add("Final Z safety height", "sans-bold", 20); txt_final_z_height = heightmap_business_holder->add("15"); txt_final_z_height->set_editable(true); heightmap_business_holder->add("", "sans-bold"); heightmap_business_holder->add("", "sans-bold"); auto btn_start_probing = heightmap_business_holder->add("Start Probing"); btn_start_probing->set_callback([&]() { cnc.probe_heightmap(heightmap_grid); }); auto btn_save_heightmap = heightmap_business_holder->add("Save heightmap"); btn_save_heightmap->set_callback([&]() { auto path = nanogui::file_dialog( {{"hmap", "Heightmap files"} }, true); if (!path.empty()) { save_heightmap(heightmap_grid, path); } }); } void SenderApp::fill_heightmap_from_model() const { auto min = renderer.get_extents_min(); auto max = renderer.get_extents_max(); txt_heightmap_from_x->set_value(std::to_string(min.x)); txt_heightmap_from_y->set_value(std::to_string(min.y)); txt_heightmap_to_x->set_value(std::to_string(max.x)); txt_heightmap_to_y->set_value(std::to_string(max.y)); } void SenderApp::update_grid() { auto from_x = std::stof(txt_heightmap_from_x->value()); auto from_y = std::stof(txt_heightmap_from_y->value()); auto to_x = std::stof(txt_heightmap_to_x->value()); auto to_y = std::stof(txt_heightmap_to_y->value()); auto res = std::stof(txt_heightmap_res->value()); if (from_x != heightmap_grid.from_x || from_y != heightmap_grid.from_y || to_x != heightmap_grid.to_x || to_y != heightmap_grid.to_y || res != heightmap_grid.resolution) { heightmap_grid = std::move(grbl::heightmap::from_params(from_x, from_y, to_x, to_y, res)); renderer.update_grid(heightmap_grid); } } void SenderApp::add_pins_markup() { // Pins info_layer->add("Pins", "sans-bold", 20); auto pins_holder = info_layer->add(); pins_holder->set_layout( new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Fill, 4, 4)); btn_pin_door = pins_holder->add(0, "D"); // btn_pin_door->set_enabled(false); btn_pin_door->set_flags(nanogui::Button::Flags::NormalButton); btn_pin_hold = pins_holder->add(0, "H"); // btn_pin_hold->set_enabled(false); btn_pin_hold->set_flags(nanogui::Button::Flags::NormalButton); btn_pin_reset = pins_holder->add(0, "R"); // btn_pin_reset->set_enabled(false); btn_pin_reset->set_flags(nanogui::Button::Flags::NormalButton); btn_pin_cycle_start = pins_holder->add(0, "S"); // btn_pin_cycle_start->set_enabled(false); btn_pin_cycle_start->set_flags(nanogui::Button::Flags::NormalButton); auto a = pins_holder->add(""); a->set_fixed_width(30); btn_pin_limit_x = pins_holder->add(0, "X"); // btn_pin_limit_x->set_enabled(false); btn_pin_limit_x->set_flags(nanogui::Button::Flags::NormalButton); btn_pin_limit_y = pins_holder->add(0, "Y"); // btn_pin_limit_y->set_enabled(false); btn_pin_limit_y->set_flags(nanogui::Button::Flags::NormalButton); btn_pin_limit_z = pins_holder->add(0, "Z"); // btn_pin_limit_z->set_enabled(false); btn_pin_limit_z->set_flags(nanogui::Button::Flags::NormalButton); btn_pin_probe = pins_holder->add(0, "P"); // btn_pin_probe->set_enabled(false); btn_pin_probe->set_flags(nanogui::Button::Flags::NormalButton); } void SenderApp::refresh_offset() { auto offset_name = "G" + std::to_string(cbo_work_offset->selected_index() + 54); cnc.set_work_offset(offset_name); } void SenderApp::fill_in_parameters() { parameters_vscroll = new nanogui::VScrollPanel(tab_widget); tab_widget->append_tab("Parameters", parameters_vscroll); parameters_layer = new Widget(parameters_vscroll); parameters_layer->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 1, nanogui::Alignment::Middle)); auto ¶meters = cnc.get_parameters(); for (auto &entry: parameters) { auto w = parameters_layer->add(); w->set_layout(new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 2, 2)); auto x = w->add(entry.first, "sans-bold", 20); x->set_fixed_width(50); auto y = w->add(entry.second); y->set_editable(true); y->set_fixed_width(200); auto z = w->add("", FA_SAVE); // z->set_flags(Button::Flags::NormalButton); // no toggle, please z->set_tooltip("save"); } } void SenderApp::fill_in_settings() { settings_vscroll = new nanogui::VScrollPanel(tab_widget); tab_widget->append_tab("Settings", settings_vscroll); settings_layer = new nanogui::Widget(settings_vscroll); settings_layer->set_layout( new nanogui::GridLayout(nanogui::Orientation::Horizontal, 1, nanogui::Alignment::Middle)); auto &settings = cnc.get_settings(); for (auto &s: settings) { auto w = settings_layer->add(); w->set_layout(new nanogui::BoxLayout(nanogui::Orientation::Horizontal, nanogui::Alignment::Middle, 2, 2)); auto x = w->add(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(s.second); y->set_editable(true); y->set_tooltip(desc); y->set_fixed_width(150); auto z = w->add(FA_SAVE); z->set_flags(nanogui::Button::Flags::NormalButton); // no toggle, please z->set_tooltip("save"); auto t = w->add(); t->set_fixed_width(200); t->set_fixed_height(20); t->clear(); t->append(desc.substr(0, desc.find_first_of('\n'))); } } void SenderApp::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); } void SenderApp::set_program_extents() const { auto min = renderer.get_extents_min(); auto max = renderer.get_extents_max(); extents_min_x->set_value(std::to_string(min.x)); extents_max_x->set_value(std::to_string(max.x)); extents_min_y->set_value(std::to_string(min.y)); extents_max_y->set_value(std::to_string(max.y)); extents_min_z->set_value(std::to_string(min.z)); extents_max_z->set_value(std::to_string(max.z)); } bool SenderApp::resize_event(const nanogui::Vector2i &size) { // window->set_fixed_height(this->height() / 2); tab_widget->set_fixed_height((this->height() - 30)); perform_layout(); return Screen::resize_event(size); } void SenderApp::update_dro() { auto work_pos = cnc.get_work_pos(); dro_ss.str(""); dro_ss << work_pos[0]; mpos_x_text->set_value(dro_ss.str()); dro_ss.str(""); dro_ss << work_pos[1]; mpos_y_text->set_value(dro_ss.str()); dro_ss.str(""); dro_ss << work_pos[2]; mpos_z_text->set_value(dro_ss.str()); } bool SenderApp::keyboard_event(int key, int scancode, int action, int modifiers) { if (Screen::keyboard_event(key, scancode, action, modifiers)) return true; // if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { // set_visible(false); // 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) { 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 (btn_keyboard_jog->pushed() && jog != new_jog) { cnc.request_jog(new_jog); jog = new_jog; } return false; } bool SenderApp::mouse_motion_event(const nanogui::Vector2i &p, const nanogui::Vector2i &rel, int button, int modifiers) { 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; bool is_rotating = button == 1; bool is_panning = button == 2; if (is_rotating) { if (abs(rel.x()) > abs(rel.y())) { cam_src_rotation *= glm::angleAxis(rel.x() / 100.0f, glm::vec3(0, 1, 0)); } else { cam_src_rotation *= glm::angleAxis(rel.y() / 100.0f, glm::vec3(1, 0, 0)); } } else if (is_panning) { cam_pan.x += (float) rel.x() / 10.0f; cam_pan.y -= (float) rel.y() / 10.0f; } return true; } } bool SenderApp::scroll_event(const nanogui::Vector2i &p, const nanogui::Vector2f &rel) { if (window->mouse_focused()) { 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 = std::max(cam_zoom, 0.1f); return true; } } void SenderApp::draw(NVGcontext *ctx) { std::shared_ptr event; while ((event = cnc.pop_event()) != nullptr) { switch (event->type) { case grbl::machine_event_type::connected: break; case grbl::machine_event_type::disconnected: break; case grbl::machine_event_type::report_received: { auto ev = dynamic_cast(event.get()); txt_status->set_value(grbl::status_to_string(cnc.get_status().status)); if (cnc.get_status().status == grbl::machine_status::alarm) { txt_status->set_tooltip(grbl::alarm_to_string(last_alarm)); } else { txt_status->set_tooltip(cnc.get_status().sub_status); } // FIXME: ugly way of retrieving the bg color btn_pin_door->set_background_color( ev->report.signals.bit.door ? color_red : btn_load_program->background_color()); btn_pin_hold->set_background_color( ev->report.signals.bit.hold ? color_red : btn_load_program->background_color()); btn_pin_reset->set_background_color( ev->report.signals.bit.soft_reset ? color_red : btn_load_program->background_color()); btn_pin_cycle_start->set_background_color( ev->report.signals.bit.cycle_start ? color_red : btn_load_program->background_color()); btn_pin_limit_x->set_background_color( ev->report.signals.bit.x_limit ? color_red : btn_load_program->background_color()); btn_pin_limit_y->set_background_color( ev->report.signals.bit.y_limit ? color_red : btn_load_program->background_color()); btn_pin_limit_z->set_background_color( ev->report.signals.bit.z_limit ? color_red : btn_load_program->background_color()); btn_pin_probe->set_background_color( ev->report.signals.bit.probe ? color_red : btn_load_program->background_color()); update_dro(); last_report = ev->report; break; } case grbl::machine_event_type::banner: break; case grbl::machine_event_type::message: { auto ev = dynamic_cast(event.get()); txt_message->set_value(ev->message); set_caption(ev->message); break; } case grbl::machine_event_type::alarm: { auto ev = dynamic_cast(event.get()); last_alarm = ev->alarm; break; } case grbl::machine_event_type::init_completed: fill_in_settings(); fill_in_parameters(); perform_layout(); refresh_offset(); update_dro(); break; case grbl::machine_event_type::run_completed: { auto ev = dynamic_cast(event.get()); btn_run_program->set_background_color(ev->success ? color_green : color_red); if (ev->success) { new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Information, "Run result", "Program executed successfully"); } else { std::stringstream ss; auto i = pgm.instruction_at(ev->failed_index); ss << "Program execution failed at line " << i.line << " (" << i.command << "). Error: " << ev->error << ", " << grbl::error_to_string(ev->error); new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Run result", ss.str()); } break; } case grbl::machine_event_type::check_completed: { auto ev = dynamic_cast(event.get()); btn_check_program->set_background_color(ev->success ? color_green : color_red); if (ev->success) { btn_run_program->set_enabled(true); new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Information, "Check result", "Program checked successfully"); } else { btn_run_program->set_enabled(false); std::stringstream ss; auto i = pgm.instruction_at(ev->failed_index); ss << "Program check failed at line " << i.line << " (" << i.command << "). Error: " << ev->error << ", " << grbl::error_to_string(ev->error); new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Check result", ss.str()); } break; } case grbl::machine_event_type::settings_reloaded: break; case grbl::machine_event_type::parameters_reloaded: refresh_offset(); update_dro(); break; case grbl::machine_event_type::probe_result: { auto ev = dynamic_cast(event.get()); std::stringstream ss; ss << "Probing ended. Result: " << std::boolalpha << ev->probe_touched << " at coords: "; for (float probe_coord: ev->probe_coords) { ss << probe_coord << ", "; } new nanogui::MessageDialog(this, nanogui::MessageDialog::Type::Warning, "Probe result", ss.str()); break; } case grbl::machine_event_type::heightmap_probe_acquired: { std::cout << "Updating grid" << std::endl; auto ev = dynamic_cast(event.get()); renderer.update_grid(*ev->grid); break; } } } Widget::draw(ctx); } void SenderApp::draw_contents() { auto fb_size = framebuffer_size(); // start rendering m_render_pass->resize(framebuffer_size()); m_render_pass->begin(); renderer.update(pgm, cnc); // compute mvp glm::mat4 projection = glm::perspective(45.0f * glm::pi() / 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_pan - cam_target); glm::mat4 model = glm::mat4(1.0f); glm::mat3 normal_mat = glm::inverseTranspose(glm::mat3(view * model)); renderer.render(model, view, projection, normal_mat, glm::vec2(fb_size.x(), fb_size.y())); m_render_pass->end(); }