Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Configurable hotkeys**: **Settings -> Keyboard shortcuts** remaps shape tools (Move/Rotate/Scale/Extrude/Chamfer/Fillet/Polar duplicate/Cross-section), sketch tools (inspection, from face, operation axis, node/line/multi-line/arc/square/rectangle/circle/slot, Dimension), boolean commands (Cut/Fuse/Common), Delete, New/Open/Save, and Undo/Redo. Bindings persist as **`gui.hotkeys`** (human-readable chords; missing keys use defaults). Toolbar tooltips follow remaps. **Delete** / **Backspace** stay fixed delete aliases; <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> stays a fixed redo alias. Fixed chords are rejected on assign; per-row **Reset** rejects when the factory chord is already bound elsewhere (same conflict message as assign); hotkey capture clears when Settings closes.
- **Configurable hotkeys**: **Settings -> Keyboard shortcuts** remaps shape tools (Move/Rotate/Scale/Extrude/Chamfer/Fillet/Polar duplicate/Cross-section), sketch tools (inspection, from face, operation axis, node/line/multi-line/arc/square/rectangle/circle/slot, Dimension), boolean commands (Cut/Fuse/Common), Delete, New/Open/Save, and Undo/Redo. Bindings persist as **`gui.hotkeys`** (human-readable chords; missing keys use defaults). Toolbar tooltips follow remaps. **Delete** / **Backspace** stay fixed delete aliases; <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> stays a fixed redo alias. Fixed, unsupported, and conflicting chords are rejected on assign with a status toast (and inline message); remaps accept letters, digits, and <kbd>Space</kbd> only (punctuation/numpad rejected); per-row **Reset** rejects when the factory chord is already bound elsewhere (same conflict message as assign); hotkey capture clears when Settings closes. A **?** at the top of the section opens the [Keyboard shortcuts](https://ezycad.readthedocs.io/en/latest/usage-settings.html#keyboard-shortcuts) settings guide.

- **Extrude Twist**: Options **Twist** checkbox. Two-phase flow locks height first, then sets twist angle about the face centroid (mouse, or <kbd>Shift+Tab</kbd> for degrees). Height length dim is cleared on lock; a temporary angle annotation on the extruded front face shows degrees during twist. With **Both sides**, ends twist symmetrically by +/- half the angle. Geometry uses ruled thru-sections with compatibility off (keeps tooth pairing) and cuts twisted hole solids so face bores survive; straight prism when twist is zero. Dense-face **Extrude fast preview** also applies during Twist (face copies translate and rotate; finalize builds the solid).

Expand All @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- **`gui.hotkeys` load**: duplicate-chord cleanup no longer leaves two actions on the same key when the later row's factory chord is the colliding key (e.g. Move and Rotate both `"R"`). Earlier remaps that steal a later action's factory chord are restored to defaults so each binding stays unique.

- **Move / Rotate axis keys**: unmodified <kbd>X</kbd> / <kbd>Y</kbd> / <kbd>Z</kbd> are reserved so Settings remaps cannot steal axis constraints (Move) or rotation-axis pick (Rotate). Existing `"X"` / `"Y"` / `"Z"` bindings in **`gui.hotkeys`** are dropped on load.

- **Hotkey capture**: punctuation (e.g. <kbd>,</kbd> / <kbd>.</kbd>) and other non-round-trippable keys are rejected with an unsupported-key message instead of saving as opaque `KeyN` chords that fail to reload.

## [0.4.0] - 2026-07-25

### Performance
Expand Down
41 changes: 38 additions & 3 deletions docs/usage-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ This guide covers the **Settings** pane (what is on screen), the **View** menu (
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)
6. [Keyboard shortcuts](#keyboard-shortcuts)
7. [Settings file reference](#settings-file-reference)

## View menu

Expand Down Expand Up @@ -77,7 +78,7 @@ Between those, the pane has collapsible sections. Expand a section to see its co

**WebAssembly build** — Open CASCADE line-width controls (`SetWidth` / `Prs3d` line width) have no visible effect in the browser (WebGL/GLES). The **Settings** pane hides **Edge thickness**, **Dimension line width**, and **Snap guide line width** on the web build. Saved JSON may still contain those keys from a desktop session; they are not shown as editable rows in the browser UI.

7. **Keyboard shortcuts** — Table of remappable actions: shape tools (Move, Rotate, Scale, Extrude, Chamfer, Fillet, Polar duplicate, Cross-section), sketch tools (inspection, from face, operation axis, node/line/multi-line/arc/square/rectangle/circle/slot, Dimension), boolean commands (Cut, Fuse, Common), Delete, New/Open/Save, and Undo/Redo. Click a shortcut cell, then press the new key combination; **Esc** cancels capture. Closing Settings cancels capture. **Reset** restores that row's factory chord (same conflict rules as assign: rejected if another action already uses that chord). Duplicate chords and fixed/reserved chords (Esc, Enter, Tab, Delete/Backspace, selection digits, view zoom/orbit/roll, <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd>) are rejected with an inline message. Stored as **`gui.hotkeys`** (human-readable strings such as `"G"`, `"Ctrl+S"`, `"Shift+L"`). Missing keys merge to built-in defaults. **Defaults** at the bottom of Settings restores factory chords. Toolbar tooltips for remappable modes and boolean commands follow the current bindings. See [Hotkeys](usage.md#hotkeys).
7. **Keyboard shortcuts** — Remappable chords for modeling and sketch tools, booleans, Delete, New/Open/Save, and Undo/Redo. See [Keyboard shortcuts](#keyboard-shortcuts).

8. **Startup project** — **Desktop only:** **Load last opened on startup** (checkbox, with **?**), then **Last opened path:** … or **(No path saved yet.)** Then **Save current as startup project**, **Clear saved startup** (with **?**). **WebAssembly:** no load-last row; only the two buttons and **?**. See [Startup project](#startup-project).

Expand Down Expand Up @@ -144,6 +145,40 @@ Similar to Blender's startup file: EzyCad can load a **default document** when i
- **Next runs** - If a saved startup exists, it is loaded instead of the bundled file. The session starts **untitled** (so **Save** does not overwrite your startup file until you pick a path).
- **Clear saved startup** - In **Settings -> Startup project**, click **Clear saved startup**; the next launch uses the bundled `res/default.ezy` again.

## Keyboard shortcuts

Remap modeling and sketch tool chords, boolean commands, Delete, New/Open/Save, and Undo/Redo in **View -> Settings -> Keyboard shortcuts**. Default key lists live in [usage.md -> Hotkeys](usage.md#hotkeys) (and [usage-sketch.md -> Hotkeys](usage-sketch.md#hotkeys) for sketch-focused summaries). Toolbar tooltips for remappable modes and boolean commands follow the current bindings.

**How to remap**

1. Expand **Keyboard shortcuts**.
2. Click the shortcut cell for an action (it shows **Press key...**).
3. Press the new key combination. **Esc** cancels capture without changing the binding. Closing **Settings** also cancels capture.
4. Per-row **Reset** restores that action's factory chord (rejected if another action already uses that chord; same conflict message as assign).
5. **Defaults** at the bottom of Settings restores all factory chords (and other bundled settings).

A **?** at the top of the section (when [UI verbosity](#settings-pane) is high enough) opens this page.

**What you can bind**

- A letter, digit, or <kbd>Space</kbd>, with optional <kbd>Ctrl</kbd> / <kbd>Shift</kbd> / <kbd>Alt</kbd> / <kbd>Super</kbd>.
- Punctuation (for example <kbd>,</kbd> / <kbd>.</kbd>) and numpad keys are rejected so stored chords stay human-readable.

**Reserved (cannot remap)**

| Keys | Role |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| <kbd>Esc</kbd>, <kbd>Enter</kbd>, <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Cancel, confirm, distance / angle input |
| <kbd>Delete</kbd>, <kbd>Backspace</kbd> | Always delete selection (fixed aliases) |
| Selection filter digits (<kbd>1</kbd>–<kbd>9</kbd>) | Normal-mode shape filter |
| View zoom / orbit / roll | See [usage.md -> View navigation](usage.md#view-navigation) |
| Unmodified <kbd>X</kbd> / <kbd>Y</kbd> / <kbd>Z</kbd> | Move axis constraints; Rotate axis pick |
| <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> | Fixed redo alias (alongside remappable <kbd>Ctrl</kbd>+<kbd>Y</kbd>) |

Two actions cannot share the same chord. Reserved, unsupported, and duplicate chords show an inline message under the table and a status toast with the reason.

**Persistence** — Bindings are stored as **`gui.hotkeys`**: action id keys to chord strings such as `"G"`, `"Ctrl+S"`, `"Shift+L"`. Missing ids merge to built-in defaults; reserved or invalid chords are dropped on load. See [Settings file reference](#gui) (`hotkeys`).

## Settings file reference

The on-disk (or localStorage) document is JSON with a **version**, optional **ImGui** layout blob, an **`occt_view`** object for the 3D view, and a **`gui`** object. **`gui`** includes pane visibility and other flags that are **not** all exposed as controls in the **Settings** pane (for example **show sketch list** comes from **View**, not from a row inside **Settings**).
Expand Down Expand Up @@ -232,7 +267,7 @@ If saved layout text has no `[Docking]` section (older installs), a default dock
| `default_2d_view_height` | number | Vertical sketch-plane span, stored in **inches** (allowed range **0.1** to **1000**; default **3**). Settings UI shows this in **`default_project_unit`**. |
| `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. |
| `hotkeys` | object | Remappable keyboard shortcuts: action id string keys to human-readable chord strings (for example `"mode.move": "G"`, `"mode.add_edge": "L"`, `"cmd.shape_cut": "Ctrl+Shift+C"`, `"edit.delete": "Shift+D"`). Missing keys merge to built-in defaults. See **Settings -> Keyboard shortcuts** and [usage.md#hotkeys](usage.md#hotkeys). |
| `hotkeys` | object | Remappable keyboard shortcuts: action id string keys to human-readable chord strings (for example `"mode.move": "G"`, `"mode.add_edge": "L"`, `"cmd.shape_cut": "Ctrl+Shift+C"`, `"edit.delete": "Shift+D"`). Missing keys merge to built-in defaults. See [Keyboard shortcuts](#keyboard-shortcuts) and [usage.md#hotkeys](usage.md#hotkeys). |

Each **`imgui_style_dark`** / **`imgui_style_light`** object may contain:

Expand Down
2 changes: 1 addition & 1 deletion docs/usage-sketch.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Common keyboard shortcuts (hotkeys) while working in 2D sketch mode or with sket
**Notes:**
- <kbd>Tab</kbd> / <kbd>Shift+Tab</kbd> work even when focus is in the 3D view (they are routed to the active sketch tool for precise entry).
- When an angle constraint is active, node snapping is typically relaxed or disabled to preserve the exact angle.
- Global hotkeys (mode switches like <kbd>G</kbd> Move, sketch tools, undo, view navigation, selection filters) are in the main [Hotkeys](usage.md#hotkeys) section. Remap sketch and modeling chords in **View -> Settings -> Keyboard shortcuts**.
- Global hotkeys (mode switches like <kbd>G</kbd> Move, sketch tools, undo, view navigation, selection filters) are in the main [Hotkeys](usage.md#hotkeys) section. Remap sketch and modeling chords in **View -> Settings -> Keyboard shortcuts** ([usage-settings.md](usage-settings.md#keyboard-shortcuts)).

### Move / rotate / polar axis constraints (when those options are active)

Expand Down
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ For more on 3D solids and the viewer, see [3D viewer (Open CASCADE)](usage-occt-

## Hotkeys

Mode, file, and edit chords in the **General Operations** and **Modeling Shortcuts** tables below are the **defaults**. Remap them in **View -> Settings -> Keyboard shortcuts** (persisted as **`gui.hotkeys`**). Toolbar tooltips for remappable modes show the current chord. Fixed keys (Esc, Enter, Tab / Shift+Tab, selection filter digits, view zoom/orbit/roll, and <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> redo) are reserved and cannot be assigned in Settings. **Delete** and **Backspace** always delete the selection even if **Delete** is remapped off <kbd>Shift</kbd>+<kbd>D</kbd>. <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> remains a fixed redo alias alongside the remappable <kbd>Ctrl</kbd>+<kbd>Y</kbd>.
Mode, file, and edit chords in the **General Operations** and **Modeling Shortcuts** tables below are the **defaults**. Remap them in **View -> Settings -> Keyboard shortcuts** — see [usage-settings.md -> Keyboard shortcuts](usage-settings.md#keyboard-shortcuts) for bindable keys, reserved chords, Reset/Defaults, and **`gui.hotkeys`** persistence. Toolbar tooltips for remappable modes show the current chord. **Delete** and **Backspace** always delete the selection even if **Delete** is remapped off <kbd>Shift</kbd>+<kbd>D</kbd>. <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd> remains a fixed redo alias alongside the remappable <kbd>Ctrl</kbd>+<kbd>Y</kbd>.

### General Operations

Expand Down
4 changes: 2 additions & 2 deletions src/doc/gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ When adding a `Mode` to [`mode.h`](../mode.h) (`EZY_MODE_LIST`), a toolbar butto
| 4 | `gui.hotkeys` entry in [`res/ezycad_settings.json`](../../res/ezycad_settings.json) |
| 5 | User docs: [usage.md](../../docs/usage.md#hotkeys) Modeling table; sketch tools also [usage-sketch.md](../../docs/usage-sketch.md#hotkeys); [usage-settings.md](../../docs/usage-settings.md) if labels change; `CHANGELOG.md` |

Pick a default that does not collide with existing `c_actions` chords or fixed keys (Esc, Enter, Tab, digits, unmodified X/Y/Z axis toggles). Also wire parent-mode / Options / doc URL maps as usual for new modes.
Pick a default that does not collide with existing `c_actions` chords or fixed keys (Esc, Enter, Tab, digits, unmodified X/Y/Z axis toggles — reserved via `is_reserved_chord`). Also wire parent-mode / Options / doc URL maps as usual for new modes.

`Occt_view::on_mode` also sets `AIS_ViewController::SetAllowHighlight(false)` for `Move` / `Rotate` / `Scale` (and `ClearDetected`) so idle mouse moves do not run dynamic `MoveTo` while transform preview leaves selection BVHs at the pre-transform pose; other modes restore highlight. Orbit/pan still receive `UpdateMousePosition` when buttons are held. When LMB finalizes an active transform (operands loaded), `on_mouse_button` skips `PressMouseButton` / `ReleaseMouseButton` for that click so AIS `SelectDetected` on release cannot replace the restored multi-selection with the single shape under the cursor.

Expand Down Expand Up @@ -163,7 +163,7 @@ Remappable chords live in `Gui_hotkeys` (`gui_hotkeys.h` / `.cpp`), owned by `GU
| Move-mode keys | `Mode::Move` | `on_key_move_mode_` (axis constraints X/Y/Z); hardcoded |
| Rotate-mode keys | `Mode::Rotate` | `on_key_rotate_mode_` (axis pick, Tab angle); hardcoded |

Default remappable chords include G/R/S/E/C/F/D shape tools; sketch tools N/L/A/Q/B/O/U/I/P and Shift variants; Shift+P polar, Shift+X cross-section; Ctrl+Shift+C/F/M booleans; Shift+D delete; Ctrl+N/O/S; Ctrl+Z / Ctrl+Y. Avoid binding unmodified X/Y/Z (axis toggles in Move/Rotate).
Default remappable chords include G/R/S/E/C/F/D shape tools; sketch tools N/L/A/Q/B/O/U/I/P and Shift variants; Shift+P polar, Shift+X cross-section; Ctrl+Shift+C/F/M booleans; Shift+D delete; Ctrl+N/O/S; Ctrl+Z / Ctrl+Y. Unmodified X/Y/Z are reserved for Move/Rotate axis toggles (`is_reserved_chord`); Shift+X remains free for cross-section. Remappable keys must pass `is_bindable_key` (letters, digits, Space, and named keys that round-trip in settings JSON); punctuation such as `,` / `.` and numpad keys are rejected. Settings **Keyboard shortcuts** has a `?` to `doc_urls::k_hotkeys` ([usage-settings.md#keyboard-shortcuts](../../docs/usage-settings.md#keyboard-shortcuts)).

See also [`src/doc/sketch.md`](sketch.md) and [`src/doc/shape.md`](shape.md) for per-mode mouse routing after `GUI` delegates to `Occt_view`.

Expand Down
1 change: 1 addition & 0 deletions src/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ inline constexpr const char* k_usage_settings_options = "https://ezycad.re
inline constexpr const char* k_occt_view = "https://ezycad.readthedocs.io/en/latest/usage-occt-view.html";
inline constexpr const char* k_startup_project = "https://ezycad.readthedocs.io/en/latest/usage-settings.html#startup-project";
inline constexpr const char* k_extrude_sketch_face = "https://ezycad.readthedocs.io/en/latest/usage.html#extrude-sketch-face-tool-e";
inline constexpr const char* k_hotkeys = "https://ezycad.readthedocs.io/en/latest/usage-settings.html#keyboard-shortcuts";
// clang-format on
} // namespace doc_urls

Expand Down
Loading
Loading