diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b1329b..16eba46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Inspection mode** (Normal): **Options -> Orthographic projection** (under **Selection**) toggles an orthographic camera (persisted as `gui.inspection_orthographic`). Sketch modes still force orthographic as before. +- **Options** pane: horizontal scrollbar when the window is resized narrower than its controls. +- **Shape List**: right-click a shape **name** to **Delete**; right-click the **M** button for **Delete** as well (click **M** for the material popup). - **Settings** (3D view grid) and saved **`occt_view`** JSON: configure Open CASCADE rectangular grid **step** (uniform X/Y), plus **graphic display extent X/Y** and **Z offset** (`V3d_RectangularGrid::SetGraphicValues`). Bundled **`res/ezycad_settings.json`** includes defaults for those keys. - **Keyboard zoom:** **NumPad +** / **NumPad -**, main **-**, and **Shift+=** (US **+**) zoom the 3D view in steps equal to **one mouse wheel tick** at the cursor (same internal scaling as scroll). diff --git a/res/ezycad_settings.json b/res/ezycad_settings.json index 2cc1901..8723e8f 100644 --- a/res/ezycad_settings.json +++ b/res/ezycad_settings.json @@ -13,6 +13,7 @@ "show_lua_console": true, "show_options": true, "show_python_console": true, + "ui_verbosity": 6, "view_roll_step_deg": 45.0, "view_zoom_scroll_scale": 4.0, "show_settings_dialog": true, diff --git a/src/gui.cpp b/src/gui.cpp index 99f1a76..4ddd95a 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -201,13 +201,16 @@ void GUI::menu_bar_() save_file_dialog_(); } - if (ImGui::MenuItem("Import")) - import_file_dialog_(); - - if (ImGui::MenuItem("Sketch underlay image...")) + if (ui_show_feature(2)) { - m_underlay_import_sketch_target.reset(); - sketch_underlay_import_dialog_(); + if (ImGui::MenuItem("Import")) + import_file_dialog_(); + + if (ImGui::MenuItem("Sketch underlay image...")) + { + m_underlay_import_sketch_target.reset(); + sketch_underlay_import_dialog_(); + } } if (ImGui::BeginMenu("Export")) @@ -227,7 +230,7 @@ void GUI::menu_bar_() ImGui::EndMenu(); } - if (ImGui::BeginMenu("Examples")) + if (ui_show_feature(2) && ImGui::BeginMenu("Examples")) { for (const Example_file& ex : m_example_files) if (ImGui::MenuItem(ex.label.c_str())) @@ -279,7 +282,7 @@ void GUI::menu_bar_() m_view->add_box(0, 0, 0, scale, scale, scale); } - if (ImGui::MenuItem("Add box_prms")) + if (ui_show_feature(3) && ImGui::MenuItem("Add box_prms")) { m_add_box_origin = glm::dvec3(0.0, 0.0, 0.0); m_add_box_size = glm::dvec3(1.0, 1.0, 1.0); @@ -292,7 +295,7 @@ void GUI::menu_bar_() m_view->add_pyramid(0, 0, 0, scale); } - if (ImGui::MenuItem("Add pyramid_prms")) + if (ui_show_feature(3) && ImGui::MenuItem("Add pyramid_prms")) { m_add_pyramid_origin = glm::dvec3(0.0, 0.0, 0.0); m_add_pyramid_side = 1.0; @@ -305,7 +308,7 @@ void GUI::menu_bar_() m_view->add_sphere(0, 0, 0, scale); } - if (ImGui::MenuItem("Add sphere_prms")) + if (ui_show_feature(3) && ImGui::MenuItem("Add sphere_prms")) { m_add_sphere_origin = glm::dvec3(0.0, 0.0, 0.0); m_add_sphere_radius = 1.0; @@ -318,7 +321,7 @@ void GUI::menu_bar_() m_view->add_cylinder(0, 0, 0, scale, scale); } - if (ImGui::MenuItem("Add cylinder_prms")) + if (ui_show_feature(3) && ImGui::MenuItem("Add cylinder_prms")) { m_add_cylinder_origin = glm::dvec3(0.0, 0.0, 0.0); m_add_cylinder_radius = m_add_cylinder_height = 1.0; @@ -331,7 +334,7 @@ void GUI::menu_bar_() m_view->add_cone(0, 0, 0, scale, 0.0, scale); } - if (ImGui::MenuItem("Add cone_prms")) + if (ui_show_feature(3) && ImGui::MenuItem("Add cone_prms")) { m_add_cone_origin = glm::dvec3(0.0, 0.0, 0.0); m_add_cone_R1 = 1.0; @@ -346,7 +349,7 @@ void GUI::menu_bar_() m_view->add_torus(0, 0, 0, scale, scale / 2.0); } - if (ImGui::MenuItem("Add torus_prms")) + if (ui_show_feature(3) && ImGui::MenuItem("Add torus_prms")) { m_add_torus_origin = glm::dvec3(0.0, 0.0, 0.0); m_add_torus_R1 = 1.0; @@ -366,43 +369,49 @@ void GUI::menu_bar_() save_panes = true; } - if (ImGui::MenuItem("Options", nullptr, m_show_options)) + if (ui_show_feature(1)) { - m_show_options = !m_show_options; - save_panes = true; - } + if (ImGui::MenuItem("Options", nullptr, m_show_options)) + { + m_show_options = !m_show_options; + save_panes = true; + } - if (ImGui::MenuItem("Sketch List", nullptr, m_show_sketch_list)) - { - m_show_sketch_list = !m_show_sketch_list; - save_panes = true; - } + if (ImGui::MenuItem("Sketch List", nullptr, m_show_sketch_list)) + { + m_show_sketch_list = !m_show_sketch_list; + save_panes = true; + } - if (ImGui::MenuItem("Shape List", nullptr, m_show_shape_list)) - { - m_show_shape_list = !m_show_shape_list; - save_panes = true; - } + if (ImGui::MenuItem("Shape List", nullptr, m_show_shape_list)) + { + m_show_shape_list = !m_show_shape_list; + save_panes = true; + } - if (ImGui::MenuItem("Log", nullptr, m_log_window_visible)) - { - m_log_window_visible = !m_log_window_visible; - save_panes = true; + if (ImGui::MenuItem("Log", nullptr, m_log_window_visible)) + { + m_log_window_visible = !m_log_window_visible; + save_panes = true; + } } - if (ImGui::MenuItem("Lua Console", nullptr, m_show_lua_console)) + if (ui_show_feature(2)) { - m_show_lua_console = !m_show_lua_console; - save_panes = true; - } + if (ImGui::MenuItem("Lua Console", nullptr, m_show_lua_console)) + { + m_show_lua_console = !m_show_lua_console; + save_panes = true; + } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Interactive Lua prompt and res/scripts/lua editors."); + if (ui_show_help(2) && ImGui::IsItemHovered()) + ImGui::SetTooltip("Interactive Lua prompt and res/scripts/lua editors."); - if (ImGui::MenuItem("Python Console", nullptr, m_show_python_console)) - { - m_show_python_console = !m_show_python_console; - save_panes = true; + if (ImGui::MenuItem("Python Console", nullptr, m_show_python_console)) + { + m_show_python_console = !m_show_python_console; + save_panes = true; + } } #ifndef NDEBUG if (ImGui::MenuItem("Debug", nullptr, m_show_dbg)) @@ -680,7 +689,7 @@ void GUI::toolbar_() } } - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(1) && ImGui::IsItemHovered()) ImGui::SetTooltip("%s", m_toolbar_buttons[i].tooltip); if (was_active) @@ -959,7 +968,7 @@ void GUI::sketch_list_inspector_(Sketch& sketch, int index) ImGui::SetNextItemWidth(86.f); if (ImGui::InputDouble("##dim_offset", &offset, 0.5, 2.0, "%.2f")) sketch.set_dimension_offset(i, offset); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Label offset from edge. 0 = automatic."); ImGui::PopID(); @@ -981,7 +990,7 @@ void GUI::sketch_list_inspector_(Sketch& sketch, int index) } void GUI::sketch_list_() { - if (!m_show_sketch_list) + if (!show_sketch_list_effective()) return; const ImGuiStyle& st = ImGui::GetStyle(); @@ -1022,14 +1031,17 @@ void GUI::sketch_list_() const Sketch* sk_key = sketch.get(); bool& expanded = m_sketch_list_expanded[sk_key]; - ImGui::PushID(("expand" + id_suffix).c_str()); - if (ImGui::SmallButton(expanded ? "v" : ">")) - expanded = !expanded; - if (m_show_tool_tips && ImGui::IsItemHovered()) - ImGui::SetTooltip(expanded ? "Collapse details" : "Expand details"); - ImGui::PopID(); + if (ui_show_sketch_list_expand()) + { + ImGui::PushID(("expand" + id_suffix).c_str()); + if (ImGui::SmallButton(expanded ? "v" : ">")) + expanded = !expanded; + if (ui_show_help(2) && ImGui::IsItemHovered()) + ImGui::SetTooltip(expanded ? "Collapse details" : "Expand details"); + ImGui::PopID(); - ImGui::SameLine(); + ImGui::SameLine(); + } // Radio button for selection ImGui::PushID(("select" + id_suffix).c_str()); @@ -1039,7 +1051,7 @@ void GUI::sketch_list_() set_mode(Mode::Sketch_inspection_mode); } - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Sets current"); ImGui::PopID(); @@ -1068,7 +1080,7 @@ void GUI::sketch_list_() if (ImGui::Checkbox("", &visible)) sketch->set_visible(visible); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Visibility"); ImGui::PopID(); @@ -1093,25 +1105,39 @@ void GUI::sketch_list_() if (!has_ul) ImGui::EndDisabled(); - if (m_show_tool_tips && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + if (ui_show_help(2) && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) ImGui::SetTooltip(has_ul ? "Display underlay" : "Import an image in Sketch properties to enable the underlay."); } ImGui::PopID(); - ImGui::SameLine(); - ImGui::PushID(("props" + id_suffix).c_str()); - if (ImGui::SmallButton("[P]")) + if (ui_show_sketch_list_props_slot()) { - m_sketch_properties_sketch = sketch; - m_sketch_properties_open = true; - } + ImGui::SameLine(); + ImGui::PushID(("props" + id_suffix).c_str()); + if (ui_show_sketch_list_props_button()) + { + if (ImGui::SmallButton("[P]")) + { + m_sketch_properties_sketch = sketch; + m_sketch_properties_open = true; + } - if (m_show_tool_tips && ImGui::IsItemHovered()) - ImGui::SetTooltip("Sketch properties"); + if (ui_show_help(2) && ImGui::IsItemHovered()) + ImGui::SetTooltip("Sketch properties"); + } + else + { + ImGui::BeginDisabled(); + ImGui::SmallButton("[P]"); + ImGui::EndDisabled(); + if (ui_show_help(2) && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) + ImGui::SetTooltip("Sketch properties ([P]) unlocks at UI verbosity 3."); + } - ImGui::PopID(); + ImGui::PopID(); + } - if (expanded) + if (expanded && ui_show_sketch_list_expand()) sketch_list_inspector_(*sketch, index); ++index; @@ -1260,16 +1286,19 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) m_underlay_panel_sketch = nullptr; } - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Import PNG/JPEG/BMP as a sketch underlay. Adjust half-width, half-height, center, and rotation " - "to match real dimensions; changes apply in real time."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Import PNG/JPEG/BMP as a sketch underlay. Adjust half-width, half-height, center, and rotation " + "to match real dimensions; changes apply in real time."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } if (!sk->has_underlay()) @@ -1278,14 +1307,14 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) if (ImGui::Checkbox("White paper -> transparent", &m_underlay_key_white)) sk->underlay_set_key_white_transparent(m_underlay_key_white); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Uses brightness: white background becomes clear; dark lines stay visible. " "Turn off for full-color photos. Inverting the image is not needed for typical scans."); if (ImGui::Checkbox("Tint visible lines", &m_underlay_line_tint)) sk->underlay_set_line_tint_enabled(m_underlay_line_tint); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Paints non-transparent pixels (after white key) with the line color. " "Default yellow reads well on dark backgrounds."); @@ -1304,7 +1333,7 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) if (ImGui::SliderFloat("Opacity", &m_underlay_opacity, 0.f, 1.f, "%.2f")) sk->underlay_set_opacity(m_underlay_opacity); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Overall opacity of the underlay image (0 = fully transparent, 1 = fully opaque)."); ImGui::Separator(); @@ -1364,7 +1393,7 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) begin_underlay_calib_set_x_(sk); ImGui::EndDisabled(); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Two clicks along width (+U), then type the real drawing distance (same units as sketch dimensions). " "You can use Set Y before or after; until both are set, the other axis still follows default aspect."); @@ -1374,7 +1403,7 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) begin_underlay_calib_set_y_(sk); ImGui::EndDisabled(); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip( "Two clicks along height (+V), then enter the drawing distance for Y. Order vs. Set X does not matter."); @@ -1383,7 +1412,7 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) begin_underlay_calib_define_datum_(sk); ImGui::EndDisabled(); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Two picks on the sketch plane: first sets bitmap corner (0,0); second sets the +U axis direction. " "Keeps current half width and height; aligns the image to your sketch datum."); } @@ -1392,7 +1421,7 @@ void GUI::sketch_underlay_panel_settings_(const Sketch::sptr& sk) ImGui::TextUnformatted("Transform (sketch plane, vs. current view)"); { const bool ul_ortho = sk->underlay_axes_orthogonal(); - if (!ul_ortho) + if (ui_show_help(3) && !ul_ortho) ImGui::TextWrapped( "Edge calibration produced shear (bitmap U and V are not perpendicular). These sliders only describe an " "orthogonal transform; they stay off so they cannot overwrite your calibrated affine."); @@ -1745,7 +1774,7 @@ bool GUI::try_underlay_calib_click_(const ScreenCoords& screen_coords) } void GUI::shape_list_() { - if (!m_show_shape_list) + if (!show_shape_list_effective()) return; float max_name_text_w = 0.f; @@ -1785,6 +1814,8 @@ void GUI::shape_list_() const float mat_popup_w = std::min(440.0f, std::max(280.0f, mat_label_w_max + st_mat.WindowPadding.x * 2.0f + st_mat.FramePadding.x * 2.0f + st_mat.ScrollbarSize + 8.0f)); + Shp_ptr shape_to_delete; + std::unordered_set selected_in_viewer; for (const AIS_Shape_ptr& ais : m_view->get_selected()) if (!ais.IsNull()) @@ -1844,15 +1875,11 @@ void GUI::shape_list_() if (ImGui::InputText("", name_buffer, sizeof(name_buffer))) shape->set_name(std::string(name_buffer)); - if (ImGui::BeginPopupContextItem("shape_name_mat")) + if (ImGui::BeginPopupContextItem("shape_name_ctx")) { - if (ImGui::BeginMenu("Material")) - { - for (int i = 0; i < nmat; ++i) - if (ImGui::MenuItem(mat_names[static_cast(i)].c_str(), nullptr, i == mat_idx)) - apply_shape_material(i); - ImGui::EndMenu(); - } + if (ImGui::MenuItem("Delete")) + shape_to_delete = shape; + ImGui::EndPopup(); } @@ -1864,7 +1891,7 @@ void GUI::shape_list_() if (ImGui::Checkbox("", &visible)) shape->set_visible(visible); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("visibility"); ImGui::PopID(); @@ -1876,7 +1903,7 @@ void GUI::shape_list_() if (ImGui::Checkbox("", &shaded)) shape->set_disp_mode(shaded ? AIS_Shaded : AIS_WireFrame); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("solid/wire"); ImGui::PopID(); @@ -1888,8 +1915,8 @@ void GUI::shape_list_() ImGui::OpenPopup("mat_pick"); ImGui::PopStyleVar(); - if (m_show_tool_tips && ImGui::IsItemHovered()) - ImGui::SetTooltip("%s\n(right-click name: Material menu)", mat_names[static_cast(mat_idx)].c_str()); + if (ui_show_help(2) && ImGui::IsItemHovered()) + ImGui::SetTooltip("%s\n(click: material; right-click name: Delete)", mat_names[static_cast(mat_idx)].c_str()); ImGui::SetNextWindowSize(ImVec2(mat_popup_w, 0.0f), ImGuiCond_Appearing); if (ImGui::BeginPopup("mat_pick")) @@ -1915,9 +1942,8 @@ void GUI::shape_list_() if (ImGui::BeginPopupContextItem("mat_btn_ctx")) { - for (int i = 0; i < nmat; ++i) - if (ImGui::MenuItem(mat_names[static_cast(i)].c_str(), nullptr, i == mat_idx)) - apply_shape_material(i); + if (ImGui::MenuItem("Delete")) + shape_to_delete = shape; ImGui::EndPopup(); } @@ -1928,10 +1954,14 @@ void GUI::shape_list_() { ImGui::EndGroup(); ImGui::PopStyleColor(4); - if (m_show_tool_tips && ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Selected in 3D viewer"); } } + + if (shape_to_delete) + m_view->delete_shapes({shape_to_delete}); + ImGui::EndChild(); ImGui::End(); } @@ -2036,7 +2066,7 @@ void GUI::log_message(const std::string& message) } void GUI::log_window_() { - if (!m_log_window_visible) + if (!log_window_visible_effective()) return; if (!ImGui::Begin("Log", &m_log_window_visible)) @@ -2062,7 +2092,7 @@ void GUI::log_window_() } void GUI::lua_console_() { - if (!m_show_lua_console) + if (!show_lua_console_effective()) return; if (!m_lua_console) m_lua_console = std::make_unique(this); @@ -2070,7 +2100,7 @@ void GUI::lua_console_() } void GUI::python_console_() { - if (!m_show_python_console) + if (!show_python_console_effective()) return; if (!m_python_console) diff --git a/src/gui.h b/src/gui.h index 7c9d8e7..4b0be26 100644 --- a/src/gui.h +++ b/src/gui.h @@ -67,6 +67,11 @@ inline constexpr double k_gui_view_roll_step_deg_default = 45.0; inline constexpr double k_gui_view_zoom_scroll_scale_min = 0.25; inline constexpr double k_gui_view_zoom_scroll_scale_max = 64.0; inline constexpr double k_gui_view_zoom_scroll_scale_default = 4.0; +/// `gui.ui_verbosity`: 0 = minimal UI; odd steps unlock feature tiers; even steps unlock help tiers. +inline constexpr int k_gui_ui_verbosity_min = 0; +inline constexpr int k_gui_ui_verbosity_default = 6; +inline constexpr int k_gui_ui_feature_tier_max = 3; +inline constexpr int k_gui_ui_help_tier_max = 3; class GUI { public: @@ -101,6 +106,9 @@ class GUI float edge_dim_arrow_size() const { return m_edge_dim_arrow_size; } bool get_hide_all_shapes() const { return m_hide_all_shapes; } void set_hide_all_shapes(bool hide) { m_hide_all_shapes = hide; } + /// Orthographic camera in Inspection mode (Mode::Normal); persisted as `gui.inspection_orthographic`. + bool inspection_orthographic() const { return m_inspection_orthographic; } + void set_inspection_orthographic(bool v) { m_inspection_orthographic = v; } bool get_dark_mode() const { return m_dark_mode; } ImVec4 get_clear_color() const; void set_mode(Mode mode); // gui_mode.cpp @@ -120,6 +128,24 @@ class GUI void set_show_shape_list(bool v) { m_show_shape_list = v; } void set_log_window_visible(bool v) { m_log_window_visible = v; } void set_show_settings_dialog(bool v) { m_show_settings_dialog = v; } + int ui_verbosity() const { return m_ui_verbosity; } + void set_ui_verbosity(int v); + /// Cumulative feature depth from `gui.ui_verbosity` (F1 at verbosity >= 1). + int ui_feature_tier() const { return (m_ui_verbosity + 1) / 2; } + /// Cumulative help depth from `gui.ui_verbosity` (H1 at verbosity >= 2). + int ui_help_tier() const { return m_ui_verbosity / 2; } + bool ui_show_feature(int tier) const { return tier <= ui_feature_tier(); } + bool ui_show_help(int tier) const { return tier <= ui_help_tier(); } + /// Sketch list: reserved [P] column at verbosity >= 2; active button at feature tier 2 (verbosity >= 3). + bool ui_show_sketch_list_props_slot() const { return m_ui_verbosity >= 2; } + bool ui_show_sketch_list_props_button() const { return ui_show_feature(2); } + bool ui_show_sketch_list_expand() const { return ui_show_feature(2); } + bool show_options_effective() const { return m_show_options && ui_show_feature(1); } + bool show_sketch_list_effective() const { return m_show_sketch_list && ui_show_feature(1); } + bool show_shape_list_effective() const { return m_show_shape_list && ui_show_feature(1); } + bool log_window_visible_effective() const { return m_log_window_visible && ui_show_feature(1); } + bool show_lua_console_effective() const { return m_show_lua_console && ui_show_feature(2); } + bool show_python_console_effective() const { return m_show_python_console && ui_show_feature(2); } #ifndef NDEBUG void set_show_dbg(bool v) { m_show_dbg = v; } #endif @@ -283,6 +309,7 @@ class GUI double m_view_roll_step_deg = k_gui_view_roll_step_deg_default; /// Multiplier for `UpdateZoom(Aspect_ScrollDelta(..., int(y * scale)))`; persisted in `gui.view_zoom_scroll_scale`. double m_view_zoom_scroll_scale = k_gui_view_zoom_scroll_scale_default; + bool m_inspection_orthographic = false; std::vector m_toolbar_buttons; // Message status window @@ -342,7 +369,7 @@ class GUI int m_new_sketch_plane{0}; // 0=XY, 1=XZ, 2=YZ double m_new_sketch_offset{}; bool m_hide_all_shapes{false}; - bool m_show_tool_tips{true}; + int m_ui_verbosity{k_gui_ui_verbosity_default}; bool m_dark_mode{false}; #ifndef NDEBUG bool m_show_dbg{false}; diff --git a/src/gui_mode.cpp b/src/gui_mode.cpp index 9bb008e..08f1e0d 100644 --- a/src/gui_mode.cpp +++ b/src/gui_mode.cpp @@ -24,6 +24,16 @@ using namespace glm; namespace { +constexpr ImGuiTableFlags k_options_table_flags = ImGuiTableFlags_SizingFixedFit; +constexpr float k_options_control_col_w = 148.f; +constexpr float k_options_sketch_control_col_w = 176.f; + +void options_table_setup_columns_(float label_col_w, float control_col_w) +{ + ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); + ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthFixed, control_col_w); +} + // Up to `max_frac` digits after the decimal, strip trailing zeros (and a trailing '.'). void format_double_trim_fraction(char* dst, std::size_t dst_sz, double v, int max_frac) { @@ -312,7 +322,7 @@ void GUI::on_key(int key, int scancode, int action, int mods) void GUI::options_() { - if (!m_show_options) + if (!show_options_effective()) return; if (!ImGui::Begin("Options", &m_show_options)) @@ -321,6 +331,8 @@ void GUI::options_() return; } + ImGui::BeginChild("##options_scroll", ImVec2(0.f, 0.f), false, ImGuiWindowFlags_HorizontalScrollbar); + // clang-format off switch (get_mode()) { @@ -385,10 +397,9 @@ void GUI::options_() sketch_label_col_w += ImGui::GetStyle().CellPadding.x * 2.0f + 8.0f; ImGui::TextUnformatted("Sketch options"); - if (ImGui::BeginTable("options_sketch_sketch", 2, ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("options_sketch_sketch", 2, k_options_table_flags)) { - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, sketch_label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + options_table_setup_columns_(sketch_label_col_w, k_options_sketch_control_col_w); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); @@ -417,7 +428,7 @@ void GUI::options_() Sketch_nodes::set_snap_guide_mode(static_cast(i)); ImGui::EndCombo(); } - if (ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Traditional: compact local snap marker.\n" "Fullscreen: full-view crosshair/axis guides.\n" "Both: show compact marker and fullscreen guides together."); @@ -447,7 +458,7 @@ void GUI::options_() } ImGui::EndCombo(); } - if (ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip( "Where to place the numeric value along the dimension line (near the first or second end, center, or auto).\n" "This does not flip which side of the edge the dimension sits on.\n" @@ -463,10 +474,9 @@ void GUI::options_() { ImGui::Separator(); ImGui::TextUnformatted("Extrude"); - if (ImGui::BeginTable("options_sketch_extrude", 2, ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("options_sketch_extrude", 2, k_options_table_flags)) { - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, sketch_label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + options_table_setup_columns_(sketch_label_col_w, k_options_sketch_control_col_w); bool extrude_both_sides = m_view->shp_extrude().get_both_sides(); ImGui::TableNextRow(); @@ -514,16 +524,20 @@ void GUI::options_() case Mode::Sketch_add_multi_edges: ImGui::Separator(); ImGui::TextUnformatted("Shortcuts"); - ImGui::TextWrapped("TAB: type edge length. Shift+TAB: type angle (degrees, CCW from +X)."); + if (ui_show_help(3)) + ImGui::TextWrapped("TAB: type edge length. Shift+TAB: type angle (degrees, CCW from +X)."); break; case Mode::Sketch_add_node: ImGui::Separator(); ImGui::TextUnformatted("Shortcuts"); - ImGui::TextWrapped("TAB: type length along the rubber band. Shift+TAB: type angle (degrees, CCW from +X)."); - ImGui::TextWrapped( - "Snap the first click to an existing sketch point to start a rubber band, then click to place the node " - "(or press Enter after typing a length). An unsnapped click still places a single node immediately."); + if (ui_show_help(3)) + { + ImGui::TextWrapped("TAB: type length along the rubber band. Shift+TAB: type angle (degrees, CCW from +X)."); + ImGui::TextWrapped( + "Snap the first click to an existing sketch point to start a rubber band, then click to place the node " + "(or press Enter after typing a length). An unsnapped click still places a single node immediately."); + } break; default: @@ -539,6 +553,7 @@ void GUI::options_() material_combo_only_("##default_material"); } + ImGui::EndChild(); ImGui::End(); } @@ -556,13 +571,13 @@ void GUI::options_normal_mode_() }; float label_col_w = ImGui::CalcTextSize("Selection Mode").x; + label_col_w = std::max(label_col_w, ImGui::CalcTextSize("Orthographic projection").x); label_col_w += ImGui::GetStyle().CellPadding.x * 2.0f + 8.0f; ImGui::TextUnformatted("Selection"); - if (ImGui::BeginTable("options_normal_selection", 2, ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("options_normal_selection", 2, k_options_table_flags)) { - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + options_table_setup_columns_(label_col_w, k_options_control_col_w); int current_item = static_cast(m_view->get_shp_selection_mode()); ImGui::TableNextRow(); @@ -579,15 +594,32 @@ void GUI::options_normal_mode_() ImGui::EndCombo(); } ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) + { + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Hotkeys: 1-9 (Normal mode) set filter when the 3D view has focus, not while typing in UI."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + } + + bool ortho = m_inspection_orthographic; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + right_aligned_label("Orthographic projection"); + ImGui::TableSetColumnIndex(1); + if (ImGui::Checkbox("##inspection_orthographic", &ortho)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Hotkeys: 1-9 (Normal mode) set filter when the 3D view has focus, not while typing in UI."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + m_inspection_orthographic = ortho; + save_occt_view_settings(); + m_view->apply_camera_projection(); } + if (ui_show_help(2) && ImGui::IsItemHovered()) + ImGui::SetTooltip("Use an orthographic camera while in Inspection mode (no perspective foreshortening)."); ImGui::EndTable(); } @@ -598,7 +630,8 @@ void GUI::options_normal_mode_() int current_mat = int(m_view->get_default_material().Name()); if (current_mat < 0 || current_mat >= static_cast(material_names.size())) current_mat = 0; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + const float material_row_w = label_col_w + k_options_control_col_w; + ImGui::SetNextItemWidth(std::max(ImGui::GetContentRegionAvail().x, material_row_w)); if (ImGui::BeginCombo("##default_material_normal", material_names[static_cast(current_mat)].data(), ImGuiComboFlags_HeightSmall)) { @@ -689,10 +722,9 @@ void GUI::options_shape_chamfer_mode_() label_col_w += ImGui::GetStyle().CellPadding.x * 2.0f + 8.0f; ImGui::TextUnformatted("Chamfer"); - if (ImGui::BeginTable("options_chamfer_tool", 2, ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("options_chamfer_tool", 2, k_options_table_flags)) { - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + options_table_setup_columns_(label_col_w, k_options_control_col_w); int current_mode = static_cast(m_chamfer_mode); ImGui::TableNextRow(); @@ -757,10 +789,9 @@ void GUI::options_shape_fillet_mode_() label_col_w += ImGui::GetStyle().CellPadding.x * 2.0f + 8.0f; ImGui::TextUnformatted("Fillet"); - if (ImGui::BeginTable("options_fillet_tool", 2, ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("options_fillet_tool", 2, k_options_table_flags)) { - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + options_table_setup_columns_(label_col_w, k_options_control_col_w); int current_mode = static_cast(m_fillet_mode); ImGui::TableNextRow(); @@ -835,10 +866,9 @@ void GUI::options_shape_polar_duplicate_mode_() label_col_w += ImGui::GetStyle().CellPadding.x * 2.0f + 8.0f; ImGui::TextUnformatted("Polar duplicate"); - if (ImGui::BeginTable("options_polar_dup_tool", 2, ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("options_polar_dup_tool", 2, k_options_table_flags)) { - ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, label_col_w); - ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + options_table_setup_columns_(label_col_w, k_options_control_col_w); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); diff --git a/src/gui_settings.cpp b/src/gui_settings.cpp index 4fed347..3333d17 100644 --- a/src/gui_settings.cpp +++ b/src/gui_settings.cpp @@ -39,6 +39,11 @@ nlohmann::json build_occt_view_settings_object(const Occt_view& view) } } // namespace +void GUI::set_ui_verbosity(int v) +{ + m_ui_verbosity = std::max(k_gui_ui_verbosity_min, v); +} + std::string GUI::occt_view_settings_json() const { using nlohmann::json; @@ -51,6 +56,7 @@ std::string GUI::occt_view_settings_json() const {"edge_dim_arrow_size", m_edge_dim_arrow_size}, {"view_roll_step_deg", m_view_roll_step_deg}, {"view_zoom_scroll_scale", m_view_zoom_scroll_scale}, + {"inspection_orthographic", m_inspection_orthographic}, {"snap_guide_color", [&]() { @@ -59,6 +65,7 @@ std::string GUI::occt_view_settings_json() const return nlohmann::json::array({r, g, b}); }()}, {"snap_guide_mode", static_cast(Sketch_nodes::get_snap_guide_mode())}, + {"ui_verbosity", m_ui_verbosity}, }; return j.dump(2); } @@ -89,6 +96,7 @@ void GUI::save_occt_view_settings() {"dark_mode", m_dark_mode}, {"show_lua_console", m_show_lua_console}, {"show_python_console", m_show_python_console}, + {"ui_verbosity", m_ui_verbosity}, {"edge_dim_label_h", m_edge_dim_label_h}, {"edge_dim_line_width", m_edge_dim_line_width}, {"edge_dim_arrow_size", m_edge_dim_arrow_size}, @@ -99,6 +107,7 @@ void GUI::save_occt_view_settings() {"imgui_rounding_tabs", m_imgui_rounding_tabs}, {"view_roll_step_deg", m_view_roll_step_deg}, {"view_zoom_scroll_scale", m_view_zoom_scroll_scale}, + {"inspection_orthographic", m_inspection_orthographic}, {"snap_guide_color", [&]() { @@ -208,6 +217,10 @@ void GUI::parse_gui_panes_settings_(const std::string& content) return v.get() != 0; return current; }; + m_ui_verbosity = k_gui_ui_verbosity_default; + if (g.contains("ui_verbosity") && g["ui_verbosity"].is_number_integer()) + m_ui_verbosity = std::max(k_gui_ui_verbosity_min, g["ui_verbosity"].get()); + set_show_options(b("show_options", true)); set_show_sketch_list(b("show_sketch_list", true)); set_show_shape_list(b("show_shape_list", true)); @@ -269,6 +282,10 @@ void GUI::parse_gui_panes_settings_(const std::string& content) ", " + std::to_string(k_gui_view_roll_step_deg_max) + "], got " + std::to_string(v) + "; using default."); } + m_inspection_orthographic = b("inspection_orthographic", false); + if (m_view) + m_view->apply_camera_projection(); + m_view_zoom_scroll_scale = k_gui_view_zoom_scroll_scale_default; if (g.contains("view_zoom_scroll_scale") && g["view_zoom_scroll_scale"].is_number()) { @@ -412,6 +429,47 @@ void GUI::settings_() constexpr float k_label_col_w = 230.f; + { + bool verb_changed = false; + if (ImGui::BeginTable("settings_ui_verbosity", 2, ImGuiTableFlags_SizingStretchProp)) + { + ImGui::TableSetupColumn("label", ImGuiTableColumnFlags_WidthFixed, k_label_col_w); + ImGui::TableSetupColumn("control", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("UI verbosity"); + ImGui::TableSetColumnIndex(1); + ImGui::BeginDisabled(m_ui_verbosity <= k_gui_ui_verbosity_min); + if (ImGui::ArrowButton("##ui_verbosity_dec", ImGuiDir_Left)) + { + --m_ui_verbosity; + verb_changed = true; + } + ImGui::EndDisabled(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SetNextItemWidth(56.0f); + int verb_edit = m_ui_verbosity; + if (ImGui::InputInt("##ui_verbosity_val", &verb_edit, 0, 0)) + { + m_ui_verbosity = std::max(k_gui_ui_verbosity_min, verb_edit); + verb_changed = true; + } + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + if (ImGui::ArrowButton("##ui_verbosity_inc", ImGuiDir_Right)) + { + ++m_ui_verbosity; + verb_changed = true; + } + ImGui::EndTable(); + } + if (verb_changed) + save_occt_view_settings(); + } + if (ui_show_help(3)) + ImGui::TextWrapped("0 = minimal UI. Odd values add more controls and panes; even values add more help (tooltips and " + "hints). Higher values are reserved for future tiers."); + if (ImGui::Checkbox("Dark mode", &m_dark_mode)) save_occt_view_settings(); @@ -434,16 +492,19 @@ void GUI::settings_() save_occt_view_settings(); m_view_roll_step_deg = std::clamp(m_view_roll_step_deg, k_gui_view_roll_step_deg_min, k_gui_view_roll_step_deg_max); - if (ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Degrees per key press: NumPad 8/2/4/6 orbit (like LMB drag), Shift+NumPad 4/6, Shift+4/6, or " "Shift+Left/Right roll. " "Ctrl+click the slider to type a value."); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - if (ImGui::SmallButton("?##view_roll_help")) - open_url_("https://ezycad.readthedocs.io/en/latest/usage.html#view-roll"); - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Help: view roll (opens the online usage guide)."); + if (ui_show_help(3)) + { + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + if (ImGui::SmallButton("?##view_roll_help")) + open_url_("https://ezycad.readthedocs.io/en/latest/usage.html#view-roll"); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Help: view roll (opens the online usage guide)."); + } ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); @@ -464,21 +525,22 @@ void GUI::settings_() m_view_zoom_scroll_scale = std::clamp(m_view_zoom_scroll_scale, k_gui_view_zoom_scroll_scale_min, k_gui_view_zoom_scroll_scale_max); - if (ImGui::IsItemHovered()) + if (ui_show_help(2) && ImGui::IsItemHovered()) ImGui::SetTooltip("Multiplier for mouse wheel and +/- zoom (same as UpdateZoom scroll delta). " "Hold Shift while zooming for Blender-style finer steps (x0.1). Ctrl+click to type a value."); ImGui::EndTable(); } - ImGui::TextWrapped( - "NumPad 8 / 2 / 4 / 6 orbit the view (same axes as left-drag orbit). Hold Shift and press NumPad 4 or NumPad 6, " - "main 4 / 6, or Left / Right arrow for Blender-style roll around the screen Z axis (hold to repeat). " - "Num Lock off is recommended for numpad shortcuts (see usage.md View navigation). " - "Hold Shift while scrolling or pressing +/- for finer zoom."); + if (ui_show_help(3)) + ImGui::TextWrapped( + "NumPad 8 / 2 / 4 / 6 orbit the view (same axes as left-drag orbit). Hold Shift and press NumPad 4 or NumPad 6, " + "main 4 / 6, or Left / Right arrow for Blender-style roll around the screen Z axis (hold to repeat). " + "Num Lock off is recommended for numpad shortcuts (see usage.md View navigation). " + "Hold Shift while scrolling or pressing +/- for finer zoom."); } - if (ImGui::CollapsingHeader("UI corner rounding")) + if (ui_show_feature(3) && ImGui::CollapsingHeader("UI corner rounding")) { bool r_changed = false; if (ImGui::BeginTable("settings_rounding", 2, ImGuiTableFlags_SizingStretchProp)) @@ -500,14 +562,17 @@ void GUI::settings_() ImGui::TableSetColumnIndex(1); r_changed |= ImGui::SliderFloat("##round_scr", &m_imgui_rounding_scroll, 0.f, 16.f, "%.0f"); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Scrollbars and sliders applies the same radius to scrollbar tracks and slider grabs."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Scrollbars and sliders applies the same radius to scrollbar tracks and slider grabs."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } ImGui::TableNextRow(); @@ -572,7 +637,7 @@ void GUI::settings_() } } - if (ImGui::CollapsingHeader("3D view grid")) + if (ui_show_feature(3) && ImGui::CollapsingHeader("3D view grid")) { float g1[3], g2[3]; m_view->get_grid_colors(g1, g2); @@ -624,16 +689,19 @@ void GUI::settings_() ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Grid extent X"); ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted( - "Full width of the drawn grid on X (edge to edge through the center). Same length scale as sketch dimensions. " - "OCCT uses half this value internally."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted( + "Full width of the drawn grid on X (edge to edge through the center). Same length scale as sketch dimensions. " + "OCCT uses half this value internally."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } ImGui::TableSetColumnIndex(1); if (ImGui::DragScalar("##ggx", ImGuiDataType_Double, &graphic_x_ui, spd_extent, nullptr, nullptr, "%.8g")) @@ -643,16 +711,19 @@ void GUI::settings_() ImGui::TableSetColumnIndex(0); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Grid extent Y"); - ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextUnformatted( - "Full height of the drawn grid on Y (edge to edge through the center). OCCT uses half this value internally."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted( + "Full height of the drawn grid on Y (edge to edge through the center). OCCT uses half this value internally."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } ImGui::TableSetColumnIndex(1); if (ImGui::DragScalar("##ggy", ImGuiDataType_Double, &graphic_y_ui, spd_extent, nullptr, nullptr, "%.8g")) @@ -704,15 +775,18 @@ void GUI::settings_() m_edge_dim_line_width = lw; dim_lw_changed = true; } - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Thickness of sketch edge length dimensions (Open CASCADE line width scale; 1.0 = default)."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Thickness of sketch edge length dimensions (Open CASCADE line width scale; 1.0 = default)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } } @@ -728,15 +802,18 @@ void GUI::settings_() m_edge_dim_arrow_size = arrow; dim_arrow_changed = true; } - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Arrow head length for sketch and extrude length dimensions (Open CASCADE display units)."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Arrow head length for sketch and extrude length dimensions (Open CASCADE display units)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } } @@ -746,16 +823,19 @@ void GUI::settings_() ImGui::TextUnformatted("Underlay highlight color"); ImGui::TableSetColumnIndex(1); ul_changed |= ImGui::ColorEdit4("##underlay_hi", &m_underlay_highlight_color[0], ImGuiColorEditFlags_Float); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Updates all sketch underlays immediately. Also used as the default when you import a new image. " - "Per-sketch overrides in Sketch List if needed."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Updates all sketch underlays immediately. Also used as the default when you import a new image. " + "Per-sketch overrides in Sketch List if needed."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } ImGui::TableNextRow(); @@ -771,15 +851,18 @@ void GUI::settings_() Sketch_nodes::set_snap_guide_color(snap_col[0], snap_col[1], snap_col[2]); save_occt_view_settings(); } - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Color used by fullscreen snap guides and snap markers in sketch mode."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Color used by fullscreen snap guides and snap markers in sketch mode."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } } @@ -838,7 +921,7 @@ void GUI::settings_() } } - if (ImGui::CollapsingHeader("Startup project")) + if (ui_show_feature(3) && ImGui::CollapsingHeader("Startup project")) { #ifndef __EMSCRIPTEN__ if (ImGui::BeginTable("settings_startup_native", 2, ImGuiTableFlags_SizingStretchProp)) @@ -852,19 +935,22 @@ void GUI::settings_() ImGui::TableSetColumnIndex(1); if (ImGui::Checkbox("##load_last", &m_load_last_opened_on_startup)) save_occt_view_settings(); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) + if (ui_show_help(2)) { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("When enabled, EzyCad opens the last .ezy file you opened (path is stored in settings)."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("When enabled, EzyCad opens the last .ezy file you opened (path is stored in settings)."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } ImGui::EndTable(); } - if (!m_last_opened_project_path.empty()) + if (ui_show_help(3) && !m_last_opened_project_path.empty()) ImGui::TextWrapped("Last opened path: %s", m_last_opened_project_path.c_str()); else ImGui::TextDisabled("(No path saved yet.)"); @@ -875,16 +961,19 @@ void GUI::settings_() ImGui::SameLine(); if (ImGui::Button("Clear saved startup")) clear_saved_startup_project_(); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::TextDisabled("(?)"); - if (ImGui::IsItemHovered()) - { - ImGui::BeginTooltip(); - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); - ImGui::TextDisabled("Save the current document (geometry, view, and tool mode) as what loads when EzyCad starts. " - "If none is saved, the install default (res/default.ezy) is used."); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); + if (ui_show_help(2)) + { + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextDisabled("Save the current document (geometry, view, and tool mode) as what loads when EzyCad starts. " + "If none is saved, the install default (res/default.ezy) is used."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } } } diff --git a/src/occt_view.cpp b/src/occt_view.cpp index 5aeb6aa..a300d57 100644 --- a/src/occt_view.cpp +++ b/src/occt_view.cpp @@ -1048,12 +1048,16 @@ void Occt_view::sketch_face_extrude(const ScreenCoords& screen_coords, bool is_m m_shp_extrude.sketch_face_extrude(screen_coords, is_mouse_move); } -void Occt_view::delete_selected() +void Occt_view::delete_selected() { delete_shapes(get_selected()); } + +void Occt_view::delete_shapes(std::vector to_delete) { + if (to_delete.empty()) + return; + push_undo_snapshot(); remove_selected_length_dimensions_from_sketches_(); - auto selected = get_selected(); - delete_(selected); + delete_(to_delete); cancel(Set_parent_mode::No); // In case we are in the middle of a operation. } @@ -1412,6 +1416,23 @@ bool Occt_view::is_headless() const { return m_headless_view; } // Mode related Mode Occt_view::get_mode() const { return m_gui.get_mode(); } +void Occt_view::apply_camera_projection() +{ + if (is_headless()) + return; + + const bool ortho = is_sketch_mode(get_mode()) || (get_mode() == Mode::Normal && m_gui.inspection_orthographic()); + + Graphic3d_Camera_ptr camera = m_view->Camera(); + if (ortho) + camera->SetProjectionType(Graphic3d_Camera::Projection_Orthographic); + else + camera->SetProjectionType(Graphic3d_Camera::Projection_Perspective); + + m_view->Redraw(); + m_ctx->UpdateCurrentViewer(); +} + void Occt_view::on_mode() { DBG_MSG(c_mode_strs[int(get_mode())]); @@ -1441,10 +1462,8 @@ void Occt_view::on_mode() } }; - bool ortho = false; if (is_sketch_mode(get_mode())) { - ortho = true; for (auto shp : m_shps) shp->set_visible(false); @@ -1486,17 +1505,7 @@ void Occt_view::on_mode() shp->set_visible(true); } - if (!is_headless()) - { - Graphic3d_Camera_ptr camera = m_view->Camera(); - if (ortho) - camera->SetProjectionType(Graphic3d_Camera::Projection_Orthographic); - else - camera->SetProjectionType(Graphic3d_Camera::Projection_Perspective); - - m_view->Redraw(); - m_ctx->UpdateCurrentViewer(); - } + apply_camera_projection(); } void Occt_view::on_chamfer_mode() diff --git a/src/occt_view.h b/src/occt_view.h index 86242ec..d67da04 100644 --- a/src/occt_view.h +++ b/src/occt_view.h @@ -104,6 +104,8 @@ class Occt_view : protected AIS_ViewController // Mode related. void on_mode(); + /// Updates camera perspective/orthographic from current mode and Inspection-mode option. + void apply_camera_projection(); void on_chamfer_mode(); void on_fillet_mode(); Mode get_mode() const; @@ -112,6 +114,7 @@ class Occt_view : protected AIS_ViewController // Delete related. void delete_selected(); + void delete_shapes(std::vector to_delete); void delete_(std::vector& to_delete); // Member function to delete variable arguments diff --git a/usage-settings.md b/usage-settings.md index 9486d96..d2b7669 100644 --- a/usage-settings.md +++ b/usage-settings.md @@ -6,7 +6,7 @@ This guide covers the **Settings** pane (what is on screen), the **View** menu ( 1. [View menu](#view-menu) 2. [Settings pane](#settings-pane) -3. [Options panel (sketch)](#options-panel-sketch) +3. [Options panel](#options-panel) 4. [Where settings are stored](#where-settings-are-stored) 5. [Startup project](#startup-project) 6. [Settings file reference](#settings-file-reference) @@ -55,13 +55,32 @@ Between those, the pane has **six** collapsible sections. Expand a section to se **Not in this pane** - **View** menu items such as **Options**, **Sketch List**, **Lua Console** — they only show or hide panes; they are not rows inside **Settings**. Their visibility is still saved under `gui.*` in the settings file (see [Settings file reference](#settings-file-reference)). -- **Length value placement** for edge dimensions — **Options** panel when the edge-dimension tool is active; see [Options panel (sketch)](#options-panel-sketch). +- **Length value placement** for edge dimensions — **Options** panel when the edge-dimension tool is active; see [Options panel](#options-panel). **Saving** — On desktop, settings are written when you change options that save, and on exit. On **Emscripten**, use **File -> Save settings** so the browser persists (see [Where settings are stored](#where-settings-are-stored)). -## Options panel (sketch) +## Options panel -Sketch-related preferences are edited in the **Options** panel while you use a tool, not in the **Settings** pane. They appear under section headings in the panel: +Open **View -> Options**. Content depends on the active tool mode. Related controls are grouped under headings (for example **Sketch options**, **Extrude**, **Selection**, **Material**). + +If you resize the pane narrower than its controls, a **horizontal scrollbar** appears so long labels (for example **Orthographic projection**) stay readable. + +### Normal mode (Inspection) + +Under **Selection**: + +- **Selection Mode** — combo for the 3D pick filter (vertices, edges, faces, solids, and combinations). The **`(?)`** marker links to [shape selection filter hotkeys](usage.md#shape-selection-filter-normal-mode-only) in the usage guide. +- **Orthographic projection** — checkbox toggling an orthographic camera in inspection mode (sketch modes still force orthographic as before). Persisted as **`gui.inspection_orthographic`**. + +Under **Material**: + +- Document preset for new solids that do not inherit from a clicked shape (toolbar **Box**, **polar duplicate** output, and similar). To change material on an existing solid, use the [Shape List](usage.md#shape-list). + +For other non-sketch Options content (for example **Polar duplicate**), see [usage.md -> User Interface](usage.md#user-interface) (Options Panel). + +### Sketch tools + +Sketch-related preferences are edited in the **Options** panel while you use a sketch tool, not in the **Settings** pane: - **Sketch options** (all sketch tools): **Snap dist** and **Snap guide mode** (*Traditional*, *Fullscreen*, *Both*). - **Toggle edge dimension** (length dimensions): **Length value placement** - combo: *Near first point*, *Near second point*, *Center on dimension line*, *Automatic*. Maps to the `edge_dim_label_h` key (integers **0** through **3**). Changing it persists like other GUI flags. @@ -71,8 +90,6 @@ Sketch-related preferences are edited in the **Options** panel while you use a t **Dimension line width** for length dimensions is in **Settings -> Sketch** (see above). -For **Normal** mode (selection and document **Material** preset), **polar duplicate**, and other non-sketch Options content, see [usage.md](usage.md#user-interface) under **User Interface** (Options Panel). - ## Where settings are stored EzyCad reads and writes a single JSON file named **`ezycad_settings.json`**. @@ -135,7 +152,8 @@ String: ImGui `.ini` text for window positions and docking saved with **SaveIniS | `show_lua_console` | boolean | Lua console pane visible. | | `show_python_console` | boolean | Python console pane visible (native builds with Python). | | `show_dbg` | boolean | Debug pane visible (debug builds only). | -| `edge_dim_label_h` | integer | Length dimension label placement: **0** to **3** (see [Options panel (sketch)](#options-panel-sketch)). Values outside this range are ignored. | +| `inspection_orthographic` | boolean | **Normal** mode Options: orthographic camera when true (default false). | +| `edge_dim_label_h` | integer | Length dimension label placement: **0** to **3** (see [Options panel](#options-panel)). Values outside this range are ignored. | | `edge_dim_line_width` | number | Sketch length dimension line width (allowed range **0.5** to **8.0** in code). | | `imgui_rounding_general` | number | Window/child/frame/popup rounding (**0** to **32** clamped in code; sliders stop at 16 in the UI). | | `imgui_rounding_scroll` | number | Scrollbar and grab rounding (same clamp). | @@ -146,7 +164,7 @@ String: ImGui `.ini` text for window positions and docking saved with **SaveIniS | `load_last_opened_on_startup` | boolean | Desktop: open the last `.ezy` on launch. **Legacy:** `load_last_saved_on_startup` is read as a fallback if the newer key is absent. | | `last_opened_project_path` | string | Path of the last opened project for the option above. **Legacy:** `last_saved_project_path` is accepted if the newer key is missing. | -Scripting API **`ezy.occt_view_settings_json()`** returns a JSON string with **`occt_view`** plus selected **`gui`** keys (including **`gui.edge_dim_label_h`**, **`gui.edge_dim_line_width`**, **`gui.view_roll_step_deg`**, **`gui.view_zoom_scroll_scale`** when saved). See [scripting.md](scripting.md). +Scripting API **`ezy.occt_view_settings_json()`** returns a JSON string with **`occt_view`** plus selected **`gui`** keys (including **`gui.inspection_orthographic`**, **`gui.edge_dim_label_h`**, **`gui.edge_dim_line_width`**, **`gui.view_roll_step_deg`**, **`gui.view_zoom_scroll_scale`** when saved). See [scripting.md](scripting.md). --- diff --git a/usage.md b/usage.md index 16818a5..c25e2e6 100644 --- a/usage.md +++ b/usage.md @@ -58,11 +58,12 @@ EzyCad (Easy CAD) is a CAD application for hobbyist machinists to design and edi 5. **Options Panel** - Adjust tool parameters; related controls are grouped by headings (for example **Sketch options**, **Extrude**, **Selection**, **Material**, **Polar duplicate**), depending on the active tool. - - **Normal** mode: **Selection** is the 3D pick filter. **Material** is the document preset for new solids that do not inherit from a clicked shape (for example toolbar **Box**, **polar duplicate** output). **Face extrude** reads the same preset in its Options **Material** row. + - If you resize the pane narrower than its controls, a **horizontal scrollbar** appears so long labels (for example **Orthographic projection**) stay readable. + - **Normal** mode (Inspection): **Selection** is the 3D pick filter and **Orthographic projection** toggles the camera (persisted as `gui.inspection_orthographic`). **Material** is the document preset for new solids that do not inherit from a clicked shape (for example toolbar **Box**, **polar duplicate** output). **Face extrude** reads the same preset in its Options **Material** row. - To change material on a solid already in the scene, use the [Shape List](#shape-list). - **Chamfer** and **Fillet**: distance and mode only; the result solid keeps the **source shape's material**. - **Move**, **Rotate**, and **Scale**: transform options only (no material row there). - - Sketch-related options (snap, length dimension placement, face extrude, shortcuts) are summarized in **[usage-settings.md](usage-settings.md#options-panel-sketch)**. + - Sketch-related options (snap, length dimension placement, face extrude, shortcuts) are summarized in **[usage-settings.md](usage-settings.md#options-panel)**. 6. **Log Window** - View operation history @@ -74,7 +75,7 @@ EzyCad (Easy CAD) is a CAD application for hobbyist machinists to design and edi - **About** - Opens the [project README](README.md) in the browser. - **Usage Guide** - Opens the [online usage guide](https://ezycad.readthedocs.io/en/latest/usage.html) (Read the Docs; source is [usage.md](usage.md) in this repository). -For **View** (Settings, pane toggles, consoles), saving preferences, and the **Settings** pane sections, see **[usage-settings.md](usage-settings.md)**. +For **View** (Settings, pane toggles, consoles), saving preferences, and the **Settings** pane sections, see **[usage-settings.md](usage-settings.md)**. For **Options** panel details by mode, see **[Options panel](usage-settings.md#options-panel)**. ### Sketch List @@ -87,7 +88,7 @@ Each row is laid out left to right: - **Rename** - Click the name field and type a new name. - **Visibility** - Checkbox to show or hide the sketch in the 3D view. - **Underlay** - Checkbox to show or hide an [image underlay](usage-sketch.md#image-underlay) when one is imported (disabled until an underlay exists; tooltip *Display underlay*). -- **Sketch properties** - **`[P]`** opens the **Sketch properties** window (import/remove underlay, calibration, transform). See [Image underlay](usage-sketch.md#image-underlay). +- **Sketch properties** - **`[P]`** opens **Sketch properties** (import/remove underlay, calibration, transform). See [Image underlay](usage-sketch.md#image-underlay). - **Delete** - Right-click the name and choose **Delete**. When expanded, the row shows: @@ -108,10 +109,10 @@ At the top: For each shape, one row includes: - **Name** - Editable text field; change the label stored with the shape. -- **Right-click the name** - Opens a context menu with **Material** and the same material list as the **M** button. +- **Right-click the name** - **Delete** removes the shape from the document. - **Visibility** - Checkbox (tooltip *visibility*) to show or hide that shape in the 3D view. - **Solid / wire** - Checkbox (tooltip *solid/wire*) to switch **shaded** display or **wireframe** for that shape. -- **M** - Opens a **Material** popup to pick an Open CASCADE material preset (also shown in the tooltip). You can also use the context menu on the name. +- **M** - Click to open a **Material** popup; right-click **M** for **Delete**. The tooltip on **M** also notes that right-clicking the name deletes the shape. Rows that match the **current 3D selection** are drawn with a slightly brighter style so the list stays in sync with what is selected in the viewer (tooltip *Selected in 3D viewer* when you hover the highlighted row).