diff --git a/src/gui.cpp b/src/gui.cpp index 5cf58e1..4d63c05 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -87,6 +87,7 @@ void GUI::apply_sketch_dimensions_visibility() if (m_view) m_view->apply_sketch_dimensions_visibility(); } + ImFont* GUI::console_font() const { if (!m_console_font || !ImGui::GetCurrentContext()) @@ -102,10 +103,12 @@ ImFont* GUI::console_font() const return nullptr; } + GUI::~GUI() { cleanup_log_redirection_(); // Clean up stream redirection } + ImVec4 GUI::get_clear_color() const { if (m_dark_mode) @@ -113,6 +116,7 @@ ImVec4 GUI::get_clear_color() const return ImVec4(0.45f, 0.55f, 0.60f, 1.00f); } + #ifdef __EMSCRIPTEN__ GUI& GUI::instance() { @@ -120,6 +124,7 @@ GUI& GUI::instance() return *gui_instance; } #endif + void GUI::render_gui() { // Underlay transform sliders use sketch_plane_view_aabb_2d -> pt_on_plane -> view projection. @@ -158,6 +163,7 @@ void GUI::render_gui() settings_(); dbg_(); } + void GUI::render_occt() { m_view->do_frame(); } // Initialize toolbar buttons void GUI::initialize_toolbar_() @@ -194,6 +200,7 @@ void GUI::initialize_toolbar_() {load_texture("res/icons/Part_Common.png"), false, "Shape common", Command::Shape_common}, }; } + void GUI::load_examples_list_() { m_example_files.clear(); @@ -221,6 +228,7 @@ void GUI::load_examples_list_() std::sort(m_example_files.begin(), m_example_files.end(), [](const Example_file& a, const Example_file& b) { return a.label < b.label; }); } + void GUI::menu_bar_() { if (!ImGui::BeginMainMenuBar()) @@ -483,6 +491,7 @@ void GUI::menu_bar_() ImGui::EndMainMenuBar(); } + void GUI::about_dialog_() { if (m_open_about_popup) @@ -523,6 +532,7 @@ void GUI::about_dialog_() ImGui::EndPopup(); } + std::string GUI::project_title_segment_() const { if (m_last_saved_path.empty() || m_last_saved_path == "(startup)") @@ -541,6 +551,7 @@ std::string GUI::project_title_segment_() const return "untitled"; } } + void GUI::update_window_title_() { EZY_ASSERT(m_glfw_window != nullptr); @@ -552,6 +563,7 @@ void GUI::update_window_title_() m_cached_window_title = title; glfwSetWindowTitle(m_glfw_window, m_cached_window_title.c_str()); } + void GUI::open_url_(const std::string& url) { #ifdef __EMSCRIPTEN__ @@ -582,6 +594,7 @@ void GUI::open_url_(const std::string& url) system(cmd.c_str()); #endif } + void GUI::ensure_about_assets_() { if (m_about_assets_loaded) @@ -633,6 +646,7 @@ void GUI::ensure_about_assets_() break; } } + ImGui::MarkdownImageData GUI::about_markdown_image_(ImGui::MarkdownLinkCallbackData data) { ImGui::MarkdownImageData out{}; @@ -657,6 +671,7 @@ ImGui::MarkdownImageData GUI::about_markdown_image_(ImGui::MarkdownLinkCallbackD } return out; } + void GUI::about_markdown_link_cb_(ImGui::MarkdownLinkCallbackData data) { if (data.isImage || !data.userData) @@ -665,6 +680,7 @@ void GUI::about_markdown_link_cb_(ImGui::MarkdownLinkCallbackData data) std::string url(data.link, data.linkLength); static_cast(data.userData)->open_url_(url); } + ImGui::MarkdownImageData GUI::about_markdown_image_cb_(ImGui::MarkdownLinkCallbackData data) { if (!data.userData) @@ -672,6 +688,7 @@ ImGui::MarkdownImageData GUI::about_markdown_image_cb_(ImGui::MarkdownLinkCallba return static_cast(data.userData)->about_markdown_image_(data); } + // Render toolbar with ImGui void GUI::toolbar_() { @@ -745,6 +762,7 @@ void GUI::toolbar_() ImGui::End(); } + // Distance edit related void GUI::set_dist_edit(float dist, std::function&& callback, const std::optional screen_coords) @@ -767,6 +785,7 @@ void GUI::set_dist_edit(float dist, std::function&& callback, if (!already_editing) m_dist_edit_focus_pending = true; } + void GUI::hide_dist_edit() { if (m_dist_callback) @@ -782,6 +801,7 @@ void GUI::hide_dist_edit() callback(m_dist_val, true); } } + void GUI::dist_edit_() { if (!m_dist_callback) @@ -827,6 +847,7 @@ void GUI::dist_edit_() ImGui::End(); } + // Angle edit related void GUI::set_angle_edit(float angle, std::function&& callback, const std::optional screen_coords) @@ -847,6 +868,7 @@ void GUI::set_angle_edit(float angle, std::function&& callbac if (!already_editing) m_angle_edit_focus_pending = true; } + void GUI::hide_angle_edit() { if (m_angle_callback) @@ -859,7 +881,9 @@ void GUI::hide_angle_edit() callback(m_angle_val, true); } } + bool GUI::is_dist_or_angle_edit_active() const { return m_dist_callback != nullptr || m_angle_callback != nullptr; } + void GUI::angle_edit_() { if (!m_angle_callback) @@ -903,6 +927,7 @@ void GUI::angle_edit_() ImGui::End(); } + bool GUI::parse_dist_text_to_float_(const char* buf, float& out) { if (!buf) @@ -927,6 +952,7 @@ bool GUI::parse_dist_text_to_float_(const char* buf, float& out) out = v; return true; } + bool GUI::is_valid_project_json_(const std::string& s) { try @@ -939,6 +965,7 @@ bool GUI::is_valid_project_json_(const std::string& s) return false; } } + const std::vector& GUI::occt_material_combo_labels_() { static std::vector names; @@ -951,6 +978,7 @@ const std::vector& GUI::occt_material_combo_labels_() return names; } + void GUI::sketch_list_inspector_(Sketch& sketch, int index) { ImGui::Indent(); @@ -1031,6 +1059,7 @@ void GUI::sketch_list_inspector_(Sketch& sketch, int index) ImGui::PopID(); ImGui::Unindent(); } + void GUI::sketch_list_() { if (!show_sketch_list_effective()) @@ -1195,6 +1224,7 @@ void GUI::sketch_list_() ImGui::End(); } + void GUI::sketch_underlay_import_dialog_() { #ifndef __EMSCRIPTEN__ @@ -1219,23 +1249,7 @@ void GUI::sketch_underlay_import_dialog_() sketch_underlay_file_dialog_async(); #endif } -void GUI::on_sketch_underlay_file(const std::string& file_path, const std::string& file_bytes) -{ - Sketch::sptr sk = m_underlay_import_sketch_target.lock(); - m_underlay_import_sketch_target.reset(); - if (!sk) - sk = m_view->curr_sketch_shared(); - m_view->push_undo_snapshot(); - if (!sk->load_underlay_image(file_bytes)) - { - show_message("Could not decode image: " + std::filesystem::path(file_path).filename().string()); - return; - } - - m_underlay_panel_sketch = nullptr; - show_message("Underlay: " + std::filesystem::path(file_path).filename().string()); -} void GUI::sketch_properties_dialog_() { if (!m_sketch_properties_open) @@ -1263,6 +1277,7 @@ void GUI::sketch_properties_dialog_() m_sketch_properties_open = open; ImGui::End(); } + void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) { EZY_ASSERT(sk); @@ -1540,6 +1555,25 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) ImGui::EndDisabled(); } } + +void GUI::on_sketch_underlay_file(const std::string& file_path, const std::string& file_bytes) +{ + Sketch::sptr sk = m_underlay_import_sketch_target.lock(); + m_underlay_import_sketch_target.reset(); + if (!sk) + sk = m_view->curr_sketch_shared(); + + m_view->push_undo_snapshot(); + if (!sk->load_underlay_image(file_bytes)) + { + show_message("Could not decode image: " + std::filesystem::path(file_path).filename().string()); + return; + } + + m_underlay_panel_sketch = nullptr; + show_message("Underlay: " + std::filesystem::path(file_path).filename().string()); +} + void GUI::cancel_underlay_calib_() { m_dist_callback = nullptr; @@ -1549,6 +1583,7 @@ void GUI::cancel_underlay_calib_() m_underlay_calib_axis_u = gp_Vec2d(0., 0.); } + void GUI::begin_underlay_calib_set_x_(const Sketch::sptr& sk) { if (!sk || !sk->has_underlay()) @@ -1567,6 +1602,7 @@ void GUI::begin_underlay_calib_set_x_(const Sketch::sptr& sk) m_underlay_calib_phase = Underlay_calib_phase::PickX1; show_message("Underlay X: uses the current transform. Click bitmap corner (0,0), then along +U; then enter the distance."); } + void GUI::begin_underlay_calib_set_y_(const Sketch::sptr& sk) { if (!sk || !sk->has_underlay()) @@ -1585,6 +1621,7 @@ void GUI::begin_underlay_calib_set_y_(const Sketch::sptr& sk) m_underlay_calib_phase = Underlay_calib_phase::PickY1; show_message("Underlay Y: uses the current transform. Click two points along +V; then enter the drawing distance."); } + void GUI::begin_underlay_calib_define_datum_(const Sketch::sptr& sk) { if (!sk || !sk->has_underlay()) @@ -1603,6 +1640,7 @@ void GUI::begin_underlay_calib_define_datum_(const Sketch::sptr& sk) m_underlay_calib_phase = Underlay_calib_phase::PickDatumO; show_message("Datum: click where bitmap corner (0,0) should lie on the sketch plane."); } + void GUI::underlay_calib_prompt_x_distance_(const Sketch::sptr& sk) { m_underlay_calib_phase = Underlay_calib_phase::AwaitDistX; @@ -1655,6 +1693,7 @@ void GUI::underlay_calib_prompt_x_distance_(const Sketch::sptr& sk) set_dist_edit(dist_show, std::move(on_dist), spos); } + void GUI::underlay_calib_prompt_y_distance_(const Sketch::sptr& sk) { m_underlay_calib_phase = Underlay_calib_phase::AwaitDistY; @@ -1707,6 +1746,7 @@ void GUI::underlay_calib_prompt_y_distance_(const Sketch::sptr& sk) set_dist_edit(dist_show, std::move(on_dist), spos); } + bool GUI::try_underlay_calib_click_(const ScreenCoords& screen_coords) { if (m_underlay_calib_phase == Underlay_calib_phase::None) @@ -1812,6 +1852,7 @@ bool GUI::try_underlay_calib_click_(const ScreenCoords& screen_coords) return false; } } + void GUI::shape_list_() { if (!show_shape_list_effective()) @@ -2013,6 +2054,7 @@ void GUI::shape_list_() ImGui::EndChild(); ImGui::End(); } + float GUI::list_name_field_width_(const ImGuiStyle& st, const float max_name_text_w) { constexpr float k_name_field_cap = 480.f; @@ -2021,6 +2063,7 @@ float GUI::list_name_field_width_(const ImGuiStyle& st, const float max_name_tex const float name_pad_x = st.FramePadding.x * 2.0f; return std::clamp(max_name_text_w + name_pad_x, k_name_field_floor, k_name_field_cap); } + #ifndef NDEBUG void GUI::dbg_() { @@ -2053,12 +2096,14 @@ void GUI::dbg_() #else void GUI::dbg_() {} #endif + void GUI::show_message(const std::string& message) { m_message = message; m_message_visible = true; m_message_start_time = std::chrono::steady_clock::now(); } + void GUI::message_status_window_() { if (!m_message_visible || m_message.empty()) @@ -2097,6 +2142,7 @@ void GUI::message_status_window_() ImGui::End(); } + // Log window implementation void GUI::log_message(const std::string& message) { @@ -2112,6 +2158,7 @@ void GUI::log_message(const std::string& message) m_log_buffer.push_back('\0'); m_log_scroll_to_bottom = true; } + void GUI::log_window_() { if (!log_window_visible_effective()) @@ -2138,6 +2185,7 @@ void GUI::log_window_() ImGui::EndChild(); ImGui::End(); } + void GUI::lua_console_() { if (!show_lua_console_effective()) @@ -2146,6 +2194,7 @@ void GUI::lua_console_() m_lua_console = std::make_unique(this); m_lua_console->render(&m_show_lua_console); } + void GUI::python_console_() { if (!show_python_console_effective()) @@ -2155,6 +2204,7 @@ void GUI::python_console_() m_python_console = std::make_unique(this); m_python_console->render(&m_show_python_console); } + void GUI::init(GLFWwindow* window, ImFont* console_font) { m_glfw_window = window; @@ -2178,6 +2228,7 @@ void GUI::init(GLFWwindow* window, ImFont* console_font) load_default_project_(); } + void GUI::persist_last_opened_project_path_(const std::string& path) { #ifndef __EMSCRIPTEN__ @@ -2198,6 +2249,7 @@ void GUI::persist_last_opened_project_path_(const std::string& path) } #endif } + void GUI::load_default_project_() { static constexpr char k_bundled_default[] = "res/default.ezy"; @@ -2278,6 +2330,7 @@ void GUI::load_default_project_() else log_message("EzyCad: bundled " + std::string(k_bundled_default) + " is invalid; keeping initial empty document."); } + std::string GUI::serialized_project_json_() const { using namespace nlohmann; @@ -2286,6 +2339,7 @@ std::string GUI::serialized_project_json_() const j["mode"] = static_cast(get_mode()); return j.dump(2); } + void GUI::save_startup_project_() { if (!settings::save_user_startup_project(serialized_project_json_())) @@ -2301,11 +2355,13 @@ void GUI::save_startup_project_() #endif show_message("Startup project saved. It will load automatically the next time you start EzyCad."); } + void GUI::clear_saved_startup_project_() { settings::clear_user_startup_project(); show_message("Saved startup cleared. Next launch uses the install default (res/default.ezy)."); } + void GUI::on_mouse_pos(const ScreenCoords& screen_coords) { m_view->on_mouse_move(screen_coords); @@ -2354,6 +2410,7 @@ void GUI::on_mouse_pos(const ScreenCoords& screen_coords) break; } } + void GUI::on_mouse_button(int button, int action, int mods) { const ScreenCoords screen_coords(dvec2(ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y)); @@ -2431,6 +2488,7 @@ void GUI::on_mouse_button(int button, int action, int mods) break; } } + void GUI::on_mouse_scroll(double xoffset, double yoffset) { EZY_ASSERT(m_glfw_window != nullptr); @@ -2440,7 +2498,9 @@ void GUI::on_mouse_scroll(double xoffset, double yoffset) m_view->on_mouse_scroll(xoffset, yoffset, shift_finer); } + void GUI::on_resize(int width, int height) { m_view->on_resize(width, height); } + void GUI::setup_log_redirection_() { // Store original stream buffers @@ -2455,6 +2515,7 @@ void GUI::setup_log_redirection_() std::cout.rdbuf(m_cout_log_buf); std::cerr.rdbuf(m_cerr_log_buf); } + void GUI::cleanup_log_redirection_() { // Restore original stream buffers @@ -2473,6 +2534,7 @@ void GUI::cleanup_log_redirection_() m_original_cout_buf = nullptr; m_original_cerr_buf = nullptr; } + // Import/export related void GUI::export_file_dialog_(Export_format fmt) { @@ -2555,6 +2617,7 @@ void GUI::export_file_dialog_(Export_format fmt) show_message("Exported: " + download_name); #endif } + void GUI::import_file_dialog_() { #ifndef __EMSCRIPTEN__ @@ -2582,12 +2645,14 @@ void GUI::import_file_dialog_() import_file_dialog_async(); #endif } -// Open save related + +// New, open, save related void GUI::new_project_() { m_last_saved_path.clear(); m_view->new_file(); } + void GUI::open_file_dialog_() { #ifndef __EMSCRIPTEN__ @@ -2615,6 +2680,7 @@ void GUI::open_file_dialog_() open_file_dialog_async(); #endif } + void GUI::save_file_dialog_() { const std::string json_str = serialized_project_json_(); @@ -2653,6 +2719,7 @@ void GUI::save_file_dialog_() save_file_dialog_async("Save EzyCad project", default_file, json_str); #endif } + void GUI::on_file(const std::string& file_path, const std::string& json_str, bool announce_load) { using namespace nlohmann; @@ -2675,6 +2742,7 @@ void GUI::on_file(const std::string& file_path, const std::string& json_str, boo if (announce_load) show_message("Opened: " + std::filesystem::path(file_path).filename().string()); } + void GUI::on_import_file(const std::string& file_path, const std::string& file_data) { std::string ext = std::filesystem::path(file_path).extension().string(); @@ -2695,6 +2763,7 @@ void GUI::on_import_file(const std::string& file_path, const std::string& file_d else show_message("Imported: " + std::filesystem::path(file_path).filename().string()); } + #ifdef __EMSCRIPTEN__ void GUI::open_file_dialog_async() { diff --git a/src/gui_mode.cpp b/src/gui_mode.cpp index 972d484..802b6bf 100644 --- a/src/gui_mode.cpp +++ b/src/gui_mode.cpp @@ -523,6 +523,8 @@ void GUI::options_() void GUI::options_normal_mode_() { + EZY_ASSERT(get_mode() == Mode::Normal); + const auto right_aligned_label = [](const char* text) { ImGui::AlignTextToFramePadding(); diff --git a/src/occt_view.cpp b/src/occt_view.cpp index 613cdfd..f9cf3d2 100644 --- a/src/occt_view.cpp +++ b/src/occt_view.cpp @@ -1517,18 +1517,20 @@ void Occt_view::set_shape_list_hover(const Shp_ptr& shp) TopAbs_ShapeEnum Occt_view::get_shp_selection_mode() const { return m_shp_selection_mode; } -void Occt_view::set_shp_selection_mode(const TopAbs_ShapeEnum mode) +void Occt_view::set_shp_selection_mode(const TopAbs_ShapeEnum selection_mode) { - if (m_shp_selection_mode == mode) + m_modes_selection_mode_map[get_mode()] = selection_mode; + + if (m_shp_selection_mode == selection_mode) return; - m_shp_selection_mode = mode; - const std::size_t idx = static_cast(mode); - if (idx < c_names_TopAbs_ShapeEnum.size()) - m_gui.log_message(std::string("Selection mode: ") + std::string(c_names_TopAbs_ShapeEnum[idx])); + m_shp_selection_mode = selection_mode; + const std::size_t idx = static_cast(selection_mode); + EZY_ASSERT(idx < c_names_TopAbs_ShapeEnum.size()); + m_gui.log_message(std::string("Selection mode: ") + std::string(c_names_TopAbs_ShapeEnum[idx])); for (auto& shp : m_shps) - shp->set_selection_mode(mode); + shp->set_selection_mode(selection_mode); } // Material related @@ -1617,10 +1619,17 @@ void Occt_view::on_mode() switch (get_mode()) { // clang-format off - case Mode::Sketch_from_planar_face: set_shp_selection_mode(TopAbs_FACE); break; - case Mode::Shape_chamfer: on_chamfer_mode(); break; // Will update selection mode - case Mode::Shape_fillet: on_fillet_mode(); break; // Will update selection mode - default: break; + case Mode::Sketch_from_planar_face: set_shp_selection_mode(TopAbs_FACE); break; + case Mode::Shape_chamfer: on_chamfer_mode(); break; // Will update selection mode + case Mode::Shape_fillet: on_fillet_mode(); break; // Will update selection mode + case Mode::Move: set_shp_selection_mode(TopAbs_COMPOUND); break; + case Mode::Rotate: set_shp_selection_mode(TopAbs_COMPOUND); break; + case Mode::Scale: set_shp_selection_mode(TopAbs_COMPOUND); break; + default: + if(m_modes_selection_mode_map.count(get_mode())) + set_shp_selection_mode(m_modes_selection_mode_map.at(get_mode())); + + break; // clang-format on } @@ -1681,10 +1690,10 @@ Aspect_VKeyFlags Occt_view::key_flags_from_glfw_(int theFlags) { // clang-format off Aspect_VKeyFlags flags = Aspect_VKeyFlags_NONE; - if ((theFlags & GLFW_MOD_SHIFT) != 0) flags |= Aspect_VKeyFlags_SHIFT; + if ((theFlags & GLFW_MOD_SHIFT) != 0) flags |= Aspect_VKeyFlags_SHIFT; if ((theFlags & GLFW_MOD_CONTROL) != 0) flags |= Aspect_VKeyFlags_CTRL; - if ((theFlags & GLFW_MOD_ALT) != 0) flags |= Aspect_VKeyFlags_ALT; - if ((theFlags & GLFW_MOD_SUPER) != 0) flags |= Aspect_VKeyFlags_META; + if ((theFlags & GLFW_MOD_ALT) != 0) flags |= Aspect_VKeyFlags_ALT; + if ((theFlags & GLFW_MOD_SUPER) != 0) flags |= Aspect_VKeyFlags_META; // clang-format on return flags; } @@ -1752,7 +1761,6 @@ Shp_fuse& Occt_view::shp_fuse() { return m_shp_fuse; } Shp_common& Occt_view::shp_common() { return m_shp_common; } Shp_polar_dup& Occt_view::shp_polar_dup() { return m_shp_polar_dup; } Shp_extrude& Occt_view::shp_extrude() { return m_shp_extrude; } - // clang-format on // --------------------------------------------------------------------------- @@ -1777,6 +1785,7 @@ void Occt_view::pop_undo_snapshot() { if (m_restoring || m_undo_stack.empty()) return; + m_undo_stack.pop_back(); } @@ -1823,11 +1832,9 @@ bool Occt_view::redo() } bool Occt_view::can_undo() const { return !m_undo_stack.empty(); } - bool Occt_view::can_redo() const { return !m_redo_stack.empty(); } size_t Occt_view::undo_stack_size() const { return m_undo_stack.size(); } - size_t Occt_view::redo_stack_size() const { return m_redo_stack.size(); } // --------------------------------------------------------------------------- diff --git a/src/occt_view.h b/src/occt_view.h index 8265f58..bf5068a 100644 --- a/src/occt_view.h +++ b/src/occt_view.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -224,7 +225,7 @@ class Occt_view : protected AIS_ViewController // Selection related std::vector get_selected() const; TopAbs_ShapeEnum get_shp_selection_mode() const; - void set_shp_selection_mode(const TopAbs_ShapeEnum mode); + void set_shp_selection_mode(const TopAbs_ShapeEnum selection_mode); /// Highlight \a shp in the 3D viewer while the Shape List row is hovered (null clears). void set_shape_list_hover(const Shp_ptr& shp); @@ -351,6 +352,9 @@ class Occt_view : protected AIS_ViewController Shp_common m_shp_common; Shp_polar_dup m_shp_polar_dup; Shp_extrude m_shp_extrude; + // -------------------------------------------------------------------- + // Selection related + std::map m_modes_selection_mode_map; }; template