diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e18795..b01defa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- **Shape List Zoom to**: right-click a shape or group name (or the **M** button on a solid) and choose **Zoom to** to frame that solid, or all descendant solids of a group, in the 3D view while keeping the current camera orientation.
+
- **Shape copy / paste** (Ctrl+C / Ctrl+V): in-app clipboard for selected solids, or a Shape List group subtree when the selection matches that group's descendant solids. Paste deep-copies under the current group (new ids, uniquified names, same pose) with one undo step. Survives **New** so you can copy, start a blank project, and paste. Remappable as **`edit.copy`** / **`edit.paste`**. Does not use the OS clipboard; sketch geometry is not copied.
- **Align shafts** (J): pick two cylindrical faces (first moves, second is fixed), coaxially align, drag axial insert depth; with Options **Clock rotation** (default off), LMB or Shift+Tab rotates about the shared axis for tooth clocking (Enter during depth skips clocking). Tab edits depth or angle by phase. Options **Flip direction** reverses axis sense. Bakes like Move/Rotate (`Shape_geom_delta`); radius mismatch logs a warning but still places. Remappable as **`mode.cyl_align`**.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b725054..9e3493a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -623,7 +623,49 @@ add_definitions(-DGLM_FORCE_RADIANS)
# Link OpenGL libraries
target_link_libraries(${PROJECT_NAME} ${OPENGL_LIBRARIES} ${OPENGL_gl_LIBRARY})
-source_group("imgui" FILES ${imguiSrc})
+# IDE filters under EzyCad_lib: nest vendored sources under third_party (disk paths unchanged).
+# Use TREE + target_sources (headers) so the .vcxproj changes and VS reloads Solution Explorer.
+set(_ezy_tp_ide_files)
+foreach(f ${imguiSrc})
+ if(IS_ABSOLUTE "${f}")
+ list(APPEND _ezy_tp_ide_files "${f}")
+ else()
+ list(APPEND _ezy_tp_ide_files "${CMAKE_SOURCE_DIR}/${f}")
+ endif()
+endforeach()
+list(APPEND _ezy_tp_ide_files
+ "${CMAKE_SOURCE_DIR}/third_party/tinyfiledialogs/tinyfiledialogs.c"
+ "${CMAKE_SOURCE_DIR}/third_party/tinyfiledialogs/tinyfiledialogs.h")
+get_filename_component(_ezy_te_abs "${EZYCAD_IMGUI_TEXT_EDITOR_CPP}" ABSOLUTE)
+list(APPEND _ezy_tp_ide_files "${_ezy_te_abs}")
+get_filename_component(_ezy_te_dir "${_ezy_te_abs}" DIRECTORY)
+if(EXISTS "${_ezy_te_dir}/TextEditor.h")
+ list(APPEND _ezy_tp_ide_files "${_ezy_te_dir}/TextEditor.h")
+endif()
+# Vendored TextEditor lives under third_party/; FetchContent fallback is outside — group flat then.
+file(TO_CMAKE_PATH "${_ezy_te_abs}" _ezy_te_cmake)
+file(TO_CMAKE_PATH "${CMAKE_SOURCE_DIR}/third_party" _ezy_tp_root)
+string(FIND "${_ezy_te_cmake}" "${_ezy_tp_root}" _ezy_te_under_tp)
+if(_ezy_te_under_tp EQUAL 0)
+ source_group(TREE "${CMAKE_SOURCE_DIR}/third_party" PREFIX "third_party" FILES ${_ezy_tp_ide_files})
+else()
+ list(REMOVE_ITEM _ezy_tp_ide_files "${_ezy_te_abs}")
+ if(EXISTS "${_ezy_te_dir}/TextEditor.h")
+ list(REMOVE_ITEM _ezy_tp_ide_files "${_ezy_te_dir}/TextEditor.h")
+ endif()
+ source_group(TREE "${CMAKE_SOURCE_DIR}/third_party" PREFIX "third_party" FILES ${_ezy_tp_ide_files})
+ source_group("third_party" FILES "${_ezy_te_abs}")
+ if(EXISTS "${_ezy_te_dir}/TextEditor.h")
+ source_group("third_party" FILES "${_ezy_te_dir}/TextEditor.h")
+ endif()
+endif()
+set(_ezy_tp_headers
+ "${CMAKE_SOURCE_DIR}/third_party/tinyfiledialogs/tinyfiledialogs.h")
+if(EXISTS "${_ezy_te_dir}/TextEditor.h")
+ list(APPEND _ezy_tp_headers "${_ezy_te_dir}/TextEditor.h")
+endif()
+target_sources(${PROJECT_NAME}_lib PRIVATE ${_ezy_tp_headers})
+set_source_files_properties(${_ezy_tp_headers} PROPERTIES HEADER_FILE_ONLY TRUE)
set(SRC_SHP)
set(SRC_GUI)
diff --git a/agents/drafts/issues/active/gh-248-zoom-to-shaft-align-ide.md b/agents/drafts/issues/active/gh-248-zoom-to-shaft-align-ide.md
new file mode 100644
index 0000000..d9e0160
--- /dev/null
+++ b/agents/drafts/issues/active/gh-248-zoom-to-shaft-align-ide.md
@@ -0,0 +1,62 @@
+---
+github_issue: 248
+github_pr: 249
+status: active
+paired_draft: ../prs/active/gh-249-zoom-to-shaft-align-ide.md
+---
+
+# Shape List Zoom to, shaft-align mode rename, and third_party IDE filters
+
+**Suggested labels:** `enhancement`, `ui`
+
+---
+
+## Title (GitHub)
+
+Shape List Zoom to, shaft-align mode rename, and third_party IDE filters
+
+## Body (GitHub)
+
+### Summary
+
+Add Shape List **Zoom to**, rename Align shafts mode to `Mode::Shape_shaft_align`, nest vendored compile units under a `third_party` IDE filter in Visual Studio, and keep developer docs aligned with Options / Tab / Enter routing for Align shafts.
+
+### Problem
+
+- No quick way to frame a Shape List solid or group in the 3D view without changing camera orientation manually.
+- Mode name `Shape_cyl_align` no longer matches the user-facing **Align shafts** tool.
+- TextEditor / tinyfiledialogs / imgui sat in default or top-level VS filters instead of under `third_party`.
+- `gui.md` Options / keyboard dispatch needed explicit Align shafts coverage.
+
+### Scope
+
+- **Zoom to** on Shape List name and **M** context menus; `Occt_view::fit_shapes_in_view` (solid or group descendant solids; keeps camera orientation).
+- Rename `Mode::Shape_cyl_align` → `Mode::Shape_shaft_align` (hotkey id `mode.cyl_align` / `Shp_cyl_align` unchanged for now).
+- CMake `source_group(TREE ...)` so `EzyCad_lib` shows `third_party/{imgui,ImGuiColorTextEdit,tinyfiledialogs}`.
+- `src/doc/gui.md` / `shape.md`, `docs/usage.md`, `CHANGELOG.md`.
+
+### Acceptance criteria
+
+- [ ] Shape List solid/group **Zoom to** frames selection with current camera orientation
+- [ ] Empty group **Zoom to** disabled; solids with null geometry not zoomable via name menu
+- [ ] Mode enum / Options / hotkey dispatch use `Shape_shaft_align`
+- [ ] VS Solution Explorer (`EzyCad_lib`) shows vendored sources under `third_party` after CMake reconfigure + reload
+- [ ] User docs and CHANGELOG mention Zoom to; developer docs list Align shafts Options/key routing
+
+### Out of scope
+
+- Renaming `Shp_cyl_align` / `mode.cyl_align` / `Gui_action::Mode_cyl_align`
+- Changing Zoom to camera orientation (fit only)
+
+### Files touched
+
+- `src/gui.cpp`, `src/gui.h`, `src/gui_mode.cpp`, `src/gui_occt_view.cpp`, `src/gui_occt_view.h`, `src/mode.h`
+- `CMakeLists.txt`
+- `docs/usage.md`, `CHANGELOG.md`, `src/doc/gui.md`, `src/doc/shape.md`
+- `agents/drafts/issues/active/gh-248-zoom-to-shaft-align-ide.md` (this draft)
+
+### Related
+
+- Issue: https://github.com/trailcode/EzyCad/issues/248
+- Prior: Align shafts (#247 / #246)
+- Branch: `Trailcode/zoom-to`
diff --git a/agents/drafts/prs/active/gh-249-zoom-to-shaft-align-ide.md b/agents/drafts/prs/active/gh-249-zoom-to-shaft-align-ide.md
new file mode 100644
index 0000000..9db6d92
--- /dev/null
+++ b/agents/drafts/prs/active/gh-249-zoom-to-shaft-align-ide.md
@@ -0,0 +1,48 @@
+---
+github_issue: 248
+github_pr: 249
+status: active
+paired_draft: ../issues/active/gh-248-zoom-to-shaft-align-ide.md
+---
+
+# PR - Zoom to / shaft-align / third_party IDE
+
+## Title
+
+Shape List Zoom to, shaft-align mode rename, and third_party IDE filters
+
+## Summary
+
+- Shape List **Zoom to** (name and **M** menus) via `Occt_view::fit_shapes_in_view` — frames a solid or group descendant solids while keeping camera orientation.
+- Rename `Mode::Shape_cyl_align` → `Mode::Shape_shaft_align` (hotkey id `mode.cyl_align` / `Shp_cyl_align` unchanged).
+- Nest imgui / TextEditor / tinyfiledialogs under `third_party` IDE filters in `EzyCad_lib` (CMake `source_group`).
+- Sync `gui.md` Align shafts Options / Tab / Enter / dist-edit routing; `usage.md` + CHANGELOG for Zoom to.
+
+## Files Changed
+
+- `src/gui.cpp`, `src/gui.h`, `src/gui_mode.cpp`, `src/gui_occt_view.cpp`, `src/gui_occt_view.h`, `src/mode.h`
+- `CMakeLists.txt`
+- `docs/usage.md`, `CHANGELOG.md`, `src/doc/gui.md`, `src/doc/shape.md`
+- `agents/drafts/issues/active/gh-248-zoom-to-shaft-align-ide.md`
+- `agents/drafts/prs/active/gh-249-zoom-to-shaft-align-ide.md` (this draft)
+
+## Related
+
+- Issue: https://github.com/trailcode/EzyCad/issues/248
+- PR: https://github.com/trailcode/EzyCad/pull/249
+- Branch: `Trailcode/zoom-to`
+- Prior: Align shafts (#247 / #246)
+
+## Test Plan
+
+- [ ] Build Release `EzyCad`
+- [ ] Shape List solid: right-click name / **M** → **Zoom to** frames solid, orientation unchanged
+- [ ] Group with solids: **Zoom to** frames all descendants; empty group item disabled
+- [ ] Align shafts (**J**): Options Flip / Clock rotation still work after mode rename
+- [ ] Reconfigure CMake, reload `build/EzyCad.sln`: `EzyCad_lib` → `third_party/{imgui,ImGuiColorTextEdit,tinyfiledialogs}`
+- [ ] Spot-check `docs/usage.md` Shape List, `src/doc/gui.md` Options/keyboard, CHANGELOG
+
+## Notes
+
+- Closes #248
+- Out of scope: renaming `Shp_cyl_align` / `mode.cyl_align` / `Gui_action::Mode_cyl_align`
diff --git a/docs/usage.md b/docs/usage.md
index 37ceff2..3dd141e 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -123,10 +123,10 @@ Each row (left to right):
- **Visibility** - Checkbox to show or hide that node. Hiding a **group** hides its whole subtree in the 3D view.
- **Solid / wire** - Checkbox (solids only) to switch **shaded** or **wireframe**.
-- **M** - Solids only: material popup; right-click for **Shape info...** or **Delete**.
+- **M** - Solids only: material popup; right-click for **Zoom to**, **Shape info...**, or **Delete**.
- **Name** - Expandable tree row with an editable name. Click the row to select that solid (or all descendant solids for a group) and update the current group. **Ctrl+click** toggles multi-select. Drag to reparent (empty area below the list = document root).
- **Copy / Paste** - Ctrl+C / Ctrl+V (remappable) copy selected solids, or a whole group subtree when you clicked that group (all its descendant solids are selected). Paste inserts an independent deep copy under the current group, at the same pose. If you still have that copied group as the current group, paste places a sibling copy beside it (not nested inside it). This is an in-app clipboard (survives **New**; it does not use the system clipboard). Sketch edges are not copied.
-- **Right-click the name** - Solids: **Shape info...** / **Delete**. Groups: **Ungroup** (moves **all** direct children to the group's parent, then removes the group) / **Delete** (cascade-deletes the whole subtree).
+- **Right-click the name** - Solids: **Zoom to** / **Shape info...** / **Delete**. Groups: **Zoom to** (fits all descendant solids; disabled when the group has none) / **Ungroup** (moves **all** direct children to the group's parent, then removes the group) / **Delete** (cascade-deletes the whole subtree). **Zoom to** keeps the current camera orientation and frames the solid or group with a little padding.
Boolean results stay under the shared parent of their inputs when all inputs share one parent; otherwise they are placed at the document root. **File -> Import** STEP assemblies use **Import as** (default **Preserve hierarchy**) to keep product/assembly groups in the tree, import **Flat solids** at the root, or **Union shapes** into one solid.
diff --git a/src/doc/gui.md b/src/doc/gui.md
index 3f213a5..031274c 100644
--- a/src/doc/gui.md
+++ b/src/doc/gui.md
@@ -78,7 +78,7 @@ Constants and ranges live in `gui.h` (`k_gui_ui_verbosity_*`, dimension defaults
### Dist / angle edit popups
-Tab and Shift+Tab in the 3D view open numeric entry via `GUI::set_dist_edit` / `set_angle_edit` (sketch / extrude). Move and Rotate skip that path and handle Tab in their mode key handlers (`show_dist_edit` / `show_angle_edit`). The angle popup shows a `deg` suffix. While active (`is_dist_or_angle_edit_active()`), keys route to `on_key()` instead of ImGui text fields (`main` checks this).
+Tab and Shift+Tab in the 3D view open numeric entry via `GUI::set_dist_edit` / `set_angle_edit` (sketch / extrude). Move, Rotate, and Align shafts (`Shape_shaft_align`) skip that path and handle Tab in their mode key handlers (`show_dist_edit` / `show_angle_edit` / depth and twist edits). The angle popup shows a `deg` suffix. While active (`is_dist_or_angle_edit_active()`), keys route to `on_key()` instead of ImGui text fields (`main` checks this).
## Architecture
@@ -145,24 +145,24 @@ Overlay popups (`FloatEdit`, `AngleEdit`, `MessageStatus`, modals) keep `NoSaved
Remappable chords live in `Gui_hotkeys` (`gui_hotkeys.h` / `.cpp`), owned by `GUI::m_hotkeys`. Stable action ids (e.g. `mode.move`, `mode.add_edge`, `cmd.shape_cut`, `edit.undo`) map to `Key_chord { key, mods }`. Persistence: `gui.hotkeys` in `ezycad_settings.json` as human-readable strings (`"G"`, `"Shift+L"`, `"Ctrl+Shift+C"`); missing keys merge to built-in defaults. On load, `merge_from_json` drops reserved/invalid chords, then resolves duplicate chords: later actions reset to factory; if that factory chord is still held by an earlier remap, that earlier row is also restored to factory (defaults are unique, so `action_for` never keeps a silent collision). Settings **Keyboard shortcuts** captures the next `GLFW_PRESS` (Esc cancels; `set_chord` rejects conflicts and **reserved** fixed chords via `is_reserved_chord`). Per-row **Reset** calls `reset_action` (factory chord via `set_chord`, so duplicates are rejected with the same inline conflict message). Capture is cleared when Settings closes. Toolbar tooltips for remappable modes and boolean commands are rebuilt via `sync_toolbar_hotkey_tooltips_()`.
-| Input | Condition | Handler |
-| --------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
-| `+` / `-` / numpad +/- | No Ctrl/Alt | `Occt_view::zoom_view_wheel_notches` |
-| Shift + 4/6 / arrows / numpad 4/6 | No Ctrl/Alt | `Occt_view::roll_view_z_deg` |
-| Numpad 5 | No modifiers | `Occt_view::snap_view_to_nearest_standard_axis` |
-| Numpad 2/4/6/8 | No modifiers | `Occt_view::orbit_view_screen_step_deg` |
-| Hotkey capture active | Settings | `try_capture_hotkey_press_` (assign / Esc cancel / conflict message) |
-| `1`-`9` / numpad `1`-`9` | `Mode::Normal` only | `set_shp_selection_mode` (TopAbs enum index); fixed |
-| Esc | | cancel capture if listening; else `cancel_underlay_calib_`, `Occt_view::cancel`, hide dist/angle edit |
-| Tab | not Move/Rotate | `Occt_view::dimension_input`; Move/Rotate: `break` into mode handlers |
-| Shift+Tab | not Move/Rotate | `Occt_view::angle_input`; fixed |
-| Enter | not Rotate | hide edits, `Occt_view::on_enter`; Rotate: `break` into `on_key_rotate_mode_` (finalize) |
-| Delete / Backspace | | `Occt_view::delete_selected` (fixed aliases; remapping `edit.delete` does not remove these) |
-| Ctrl+Shift+Z | | `Occt_view::redo` (fixed second redo; remappable `edit.redo` defaults to Ctrl+Y) |
-| Remappable chord | `m_hotkeys` hit | `dispatch_hotkey_action_` (`Gui_action`: sketch/shape modes, booleans, delete, copy/paste, file, undo/redo) |
-| 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 |
-| Align-shafts keys | `Mode::Shape_cyl_align` | `on_key_cyl_align_mode_` (Tab depth, Shift+Tab clock/angle, Enter finalize); hardcoded |
+| Input | Condition | Handler |
+| --------------------------------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------- |
+| `+` / `-` / numpad +/- | No Ctrl/Alt | `Occt_view::zoom_view_wheel_notches` |
+| Shift + 4/6 / arrows / numpad 4/6 | No Ctrl/Alt | `Occt_view::roll_view_z_deg` |
+| Numpad 5 | No modifiers | `Occt_view::snap_view_to_nearest_standard_axis` |
+| Numpad 2/4/6/8 | No modifiers | `Occt_view::orbit_view_screen_step_deg` |
+| Hotkey capture active | Settings | `try_capture_hotkey_press_` (assign / Esc cancel / conflict message) |
+| `1`-`9` / numpad `1`-`9` | `Mode::Normal` only | `set_shp_selection_mode` (TopAbs enum index); fixed |
+| Esc | | cancel capture if listening; else `cancel_underlay_calib_`, `Occt_view::cancel`, hide dist/angle edit |
+| Tab | not Move/Rotate/Align shafts | `Occt_view::dimension_input`; those modes: `break` into mode handlers |
+| Shift+Tab | not Move/Rotate/Align shafts | `Occt_view::angle_input`; those modes: `break` into mode handlers (cyl-align: clock/angle) |
+| Enter | not Rotate/Align shafts | hide edits, `Occt_view::on_enter`; those modes: `break` into mode handlers (finalize) |
+| Delete / Backspace | | `Occt_view::delete_selected` (fixed aliases; remapping `edit.delete` does not remove these) |
+| Ctrl+Shift+Z | | `Occt_view::redo` (fixed second redo; remappable `edit.redo` defaults to Ctrl+Y) |
+| Remappable chord | `m_hotkeys` hit | `dispatch_hotkey_action_` (`Gui_action`: sketch/shape modes, booleans, delete, copy/paste, file, undo/redo) |
+| 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 |
+| Align-shafts keys | `Mode::Shape_shaft_align` | `on_key_cyl_align_mode_` (Tab depth, Shift+Tab clock/angle, Enter finalize); hardcoded |
Default remappable chords include G/R/S/J/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+C / Ctrl+V copy/paste (in-app shape clipboard); 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)).
@@ -172,15 +172,15 @@ See also [`src/doc/sketch.md`](sketch.md) and [`src/doc/shape.md`](shape.md) for
### Mouse move (`GUI::on_mouse_pos`)
-| `Mode` | Delegate |
-| --------------------------------------------------- | -------------------------------- |
-| `Move` | `shp_move().move_selected` |
-| `Rotate` | `shp_rotate().rotate_selected` |
-| `Scale` | `shp_scale().scale_selected` |
-| `Shape_cyl_align` | `drag_depth` / `drag_twist` |
-| `Shape_polar_duplicate` | `shp_polar_dup().move_point` |
-| Sketch tool modes (line, arc, rect, dim, axis, ...) | `curr_sketch().sketch_pt_move` |
-| `Sketch_face_extrude` | `sketch_face_extrude(..., true)` |
+| `Mode` | Delegate |
+| --------------------------------------------------- | ------------------------------------------- |
+| `Move` | `shp_move().move_selected` |
+| `Rotate` | `shp_rotate().rotate_selected` |
+| `Scale` | `shp_scale().scale_selected` |
+| `Shape_shaft_align` | `shp_cyl_align().drag_depth` / `drag_twist` |
+| `Shape_polar_duplicate` | `shp_polar_dup().move_point` |
+| Sketch tool modes (line, arc, rect, dim, axis, ...) | `curr_sketch().sketch_pt_move` |
+| `Sketch_face_extrude` | `sketch_face_extrude(..., true)` |
Always calls `m_view->on_mouse_move(screen_coords)` first.
@@ -210,7 +210,7 @@ Tests use `sketch_left_click` to simulate sketch LMB without ImGui mouse positio
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `Normal` | `options_normal_mode_` (selection filter, orthographic) |
| `Move` / `Rotate` / `Scale` | `options_*_mode_` (constraints, axis, material) |
-| `Shape_cyl_align` | Flip direction, Clock rotation; pick / depth / clock help |
+| `Shape_shaft_align` | `options_shape_shaft_align_mode_` (Flip direction, Clock rotation; pick / depth / clock help) |
| `Shape_chamfer` / `Shape_fillet` | mode + radius/distance |
| `Shape_polar_duplicate` | angle, count, rotate/combine, **Dup** button |
| `Shape_cross_section` | local XY/XZ/YZ, invert normal, hide back side, show section outline, bbox-ranged offset, Clip, Cross section sketch |
@@ -239,7 +239,7 @@ Shared sketch controls (snap, midpoint nodes, place-from-center) live in `option
Sketch List expand **Faces**: each face row supports **`E`** and right-click **Extrude** via `GUI::sketch_list_extrude_face_` (`set_mode(Sketch_face_extrude)` + `Occt_view::begin_sketch_face_extrude` / `Shp_extrude::begin_face_extrude`). Hovering a **Faces**, **Edges**, or **Nodes** row calls `Occt_view::set_sketch_list_hover_{face,edge,node}` (temporarily displays the AIS when hidden outside sketch modes; uses `Graphic3d_ZLayerId_Topmost` so solids do not occlude the highlight).
-**Shape List outliner:** `shape_list_` draws a tree of document shapes/groups via `shape_children(0)` and recursive `TreeNodeEx` rows. Fixed-width vis/disp/mat columns are on the left; the name column stretches on the right with tree indent (`IndentEnable` on name only). An empty pad row after the last item is a drag-drop target for document root (`reparent_shape(..., 0)`); it shows a "Move to root" hint while dragging. Groups support expand/collapse (`ui.shapeList.expanded`), drag-drop reparent (`EZY_SHAPE_ID` payload), Group / New group / Ungroup, and cascade delete. Clicking a group sets `Occt_view::current_group_id` (including empty groups) and selects descendant solids; clicking a solid selects it and sets current group to its parent. New primitives/extrudes/revolves parent under the current group. Ctrl+click multi-selects. Copy/paste (Ctrl+C/V) deep-copies the current group subtree when the selection matches that group's descendant solids. Hover uses `set_shape_list_hover` on leaf solids only. `ui.shapeList.currentGroupId` is persisted in `.ezy`.
+**Shape List outliner:** `shape_list_` draws a tree of document shapes/groups via `shape_children(0)` and recursive `TreeNodeEx` rows. Fixed-width vis/disp/mat columns are on the left; the name column stretches on the right with tree indent (`IndentEnable` on name only). An empty pad row after the last item is a drag-drop target for document root (`reparent_shape(..., 0)`); it shows a "Move to root" hint while dragging. Groups support expand/collapse (`ui.shapeList.expanded`), drag-drop reparent (`EZY_SHAPE_ID` payload), Group / New group / Ungroup, and cascade delete. Clicking a group sets `Occt_view::current_group_id` (including empty groups) and selects descendant solids; clicking a solid selects it and sets current group to its parent. New primitives/extrudes/revolves parent under the current group. Ctrl+click multi-selects. Copy/paste (Ctrl+C/V) deep-copies the current group subtree when the selection matches that group's descendant solids. Context menu **Zoom to** calls `Occt_view::fit_shapes_in_view` (solid or group descendant solids; keeps camera orientation). Hover uses `set_shape_list_hover` on leaf solids only. `ui.shapeList.currentGroupId` is persisted in `.ezy`.
**Sketch List UI in the project file:** `GUI::serialized_project_json_` writes `ui.sketchList` (scroll Y plus per-sketch `rows` keyed by sketch `id`: `expanded`, `dimensions`, `nodes`, `edges`, `faces`). `GUI::on_file` restores via `apply_sketch_list_ui_from_json_`. Subsection open state is app-owned (`Sketch_list_row_ui` + `SetNextItemOpen`), not ImGui ini storage.
diff --git a/src/doc/shape.md b/src/doc/shape.md
index a565a06..e264395 100644
--- a/src/doc/shape.md
+++ b/src/doc/shape.md
@@ -169,7 +169,7 @@ Protected helpers used by all operation classes:
| `Mode::Move` | `shp_move().move_selected` | `shp_move().finalize` | `shp_move().show_dist_edit` (`gui_mode`) | `shp_move().cancel` -> `Normal` |
| `Mode::Rotate` | `shp_rotate().rotate_selected` | `shp_rotate().finalize` | `shp_rotate().show_angle_edit` | `shp_rotate().cancel` -> `Normal` |
| `Mode::Scale` | `shp_scale().scale_selected` | `shp_scale().finalize` | -- | `shp_scale().cancel` -> `Normal` |
-| `Mode::Shape_cyl_align` | `drag_depth` / `drag_twist` | Pick faces; LMB depth->clock if Clock rotation on; LMB/Enter finalize | Tab depth; Shift+Tab clock/angle; Enter finalize | `shp_cyl_align().cancel` -> `Normal` |
+| `Mode::Shape_shaft_align` | `drag_depth` / `drag_twist` | Pick faces; LMB depth->clock if Clock rotation on; LMB/Enter finalize | Tab depth; Shift+Tab clock/angle; Enter finalize | `shp_cyl_align().cancel` -> `Normal` |
| `Mode::Sketch_face_extrude` | `sketch_face_extrude(..., true)` | Pick face; with preview: `on_left_click()` locks height->twist if Twist on, else `finalize` (`GUI` skips re-pick) | Tab: height (`dimension_input`); Shift+Tab: twist angle in twist phase | `m_shp_extrude.cancel` |
| `Mode::Shape_fillet` | -- | `shp_fillet().add_fillet(..., Fillet_mode)` | -- | -- |
| `Mode::Shape_chamfer` | -- | `shp_chamfer().add_chamfer(..., Chamfer_mode)` | -- | -- |
diff --git a/src/gui.cpp b/src/gui.cpp
index 2c5ccf9..fc6f8ae 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -184,7 +184,7 @@ void GUI::initialize_toolbar_()
{load_texture("res/icons/Assembly_AxialMove.png"), false, "Shape move", Mode::Move},
{load_texture("res/icons/Draft_Rotate.png"), false, "Shape rotate", Mode::Rotate},
{load_texture("res/icons/Part_Scale.png"), false, "Shape Scale", Mode::Scale},
- {load_texture("res/icons/Assembly_Move.png"), false, "Align shafts", Mode::Shape_cyl_align},
+ {load_texture("res/icons/Assembly_Move.png"), false, "Align shafts", Mode::Shape_shaft_align},
{load_texture("res/icons/Macro_FaceToSketch_48.png"), false, "Create a sketch from planar face", Mode::Sketch_from_planar_face},
{load_texture("res/icons/Sketcher_MirrorSketch.png"), false, "Operational axis", Mode::Sketch_operation_axis},
{load_texture("res/icons/Sketcher_CreatePoint.png"), false, "Add node", Mode::Sketch_add_node},
@@ -219,6 +219,7 @@ void GUI::sync_toolbar_hotkey_tooltips_()
{
if (b.data.index() != 0 || std::get(b.data) != mode)
continue;
+
const std::string chord = Gui_hotkeys::format_chord(m_hotkeys.chord_for(action));
b.tooltip = std::string(base) + " (" + chord + ")";
return;
@@ -230,6 +231,7 @@ void GUI::sync_toolbar_hotkey_tooltips_()
{
if (b.data.index() != 1 || std::get(b.data) != cmd)
continue;
+
const std::string chord = Gui_hotkeys::format_chord(m_hotkeys.chord_for(action));
b.tooltip = std::string(base) + " (" + chord + ")";
return;
@@ -240,7 +242,7 @@ void GUI::sync_toolbar_hotkey_tooltips_()
tip_mode(Mode::Move, "Shape move", Gui_action::Mode_move);
tip_mode(Mode::Rotate, "Shape rotate", Gui_action::Mode_rotate);
tip_mode(Mode::Scale, "Shape Scale", Gui_action::Mode_scale);
- tip_mode(Mode::Shape_cyl_align, "Align shafts", Gui_action::Mode_cyl_align);
+ tip_mode(Mode::Shape_shaft_align, "Align shafts", Gui_action::Mode_cyl_align);
tip_mode(Mode::Sketch_dim_anno, "Length dimension", Gui_action::Mode_dimension);
tip_mode(Mode::Sketch_face_extrude, "Extrude sketch face", Gui_action::Mode_extrude);
tip_mode(Mode::Shape_chamfer, "Chamfer", Gui_action::Mode_chamfer);
@@ -393,16 +395,16 @@ void GUI::menu_bar_()
const Project_unit cur = m_view->get_project_unit();
if (ImGui::MenuItem("Inches", nullptr, cur == Project_unit::Inch))
m_view->set_project_unit(Project_unit::Inch);
+
if (ImGui::MenuItem("Millimeters", nullptr, cur == Project_unit::Millimeter))
m_view->set_project_unit(Project_unit::Millimeter);
+
ImGui::EndMenu();
}
if (ui_show_feature(2))
- {
if (ImGui::MenuItem("Import"))
import_file_dialog_();
- }
if (ImGui::BeginMenu("Export"))
{
@@ -459,7 +461,6 @@ void GUI::menu_bar_()
m_view->redo();
add_menu_items_();
-
ImGui::EndMenu();
}
@@ -731,6 +732,7 @@ void GUI::ensure_about_assets_()
m_about_splash_size.x = std::get<1>(*dec);
m_about_splash_size.y = std::get<2>(*dec);
}
+
m_about_splash_gl = load_texture(p);
break;
}
@@ -2720,6 +2722,12 @@ void GUI::shape_list_()
if (ImGui::BeginPopupContextItem("mat_btn_ctx"))
{
+ if (ImGui::MenuItem("Zoom to"))
+ {
+ select_shape_row(shape);
+ m_view->fit_shapes_in_view(shape);
+ }
+
if (ImGui::MenuItem("Shape info..."))
open_shape_info_(shape);
@@ -2808,6 +2816,13 @@ void GUI::shape_list_()
row_hovered |= ImGui::IsItemHovered();
if (ImGui::BeginPopupContextItem("shape_name_ctx"))
{
+ const bool can_zoom = is_group ? !m_view->shape_descendant_solids(shape->get_id()).empty() : !shape->Shape().IsNull();
+ if (ImGui::MenuItem("Zoom to", nullptr, false, can_zoom))
+ {
+ select_shape_row(shape);
+ m_view->fit_shapes_in_view(shape);
+ }
+
if (!is_group && ImGui::MenuItem("Shape info..."))
open_shape_info_(shape);
@@ -3848,7 +3863,7 @@ void GUI::on_mouse_pos(const ScreenCoords& screen_coords)
break;
- case Mode::Shape_cyl_align:
+ case Mode::Shape_shaft_align:
if (m_view->shp_cyl_align().is_twist_phase())
{
if (Status s = m_view->shp_cyl_align().drag_twist(screen_coords); !s.is_ok())
@@ -3894,7 +3909,7 @@ void GUI::on_left_click_(const ScreenCoords& screen_coords)
case Mode::Move: m_view->shp_move().finalize(); break;
case Mode::Rotate: m_view->shp_rotate().finalize(); break;
case Mode::Scale: m_view->shp_scale().finalize(); break;
- case Mode::Shape_cyl_align:
+ case Mode::Shape_shaft_align:
if (m_view->shp_cyl_align().is_dragging())
m_view->shp_cyl_align().on_left_click();
else if (Status s = m_view->shp_cyl_align().pick(screen_coords); !s.is_ok())
diff --git a/src/gui.h b/src/gui.h
index 4bad3f6..31a596a 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -456,7 +456,7 @@ class GUI
void options_shape_fillet_mode_();
void options_shape_polar_duplicate_mode_();
void options_shape_cross_section_mode_();
- void options_shape_cyl_align_mode_();
+ void options_shape_shaft_align_mode_();
void options_sketch_from_planer_face_mode_();
void options_sketch_operation_axis_mode_();
void options_sketch_face_extrude_mode_();
diff --git a/src/gui_mode.cpp b/src/gui_mode.cpp
index d77e8e9..8e9b5ed 100644
--- a/src/gui_mode.cpp
+++ b/src/gui_mode.cpp
@@ -46,7 +46,7 @@ std::string GUI::get_doc_url_for_mode(Mode mode)
{Mode::Move, "https://ezycad.readthedocs.io/en/latest/usage.html#shape-move-tool-g"},
{Mode::Rotate, "https://ezycad.readthedocs.io/en/latest/usage.html#shape-rotate-tool-r"},
{Mode::Scale, "https://ezycad.readthedocs.io/en/latest/usage.html#shape-scale-tool-s"},
- {Mode::Shape_cyl_align, "https://ezycad.readthedocs.io/en/latest/usage.html#align-shafts-tool-j"},
+ {Mode::Shape_shaft_align, "https://ezycad.readthedocs.io/en/latest/usage.html#align-shafts-tool-j"},
{Mode::Sketch_inspection_mode, "https://ezycad.readthedocs.io/en/latest/usage-sketch.html#sketch-origin"},
{Mode::Sketch_from_planar_face, "https://ezycad.readthedocs.io/en/latest/usage-sketch.html#create-sketch-from-planar-face-tool"},
{Mode::Sketch_face_extrude, "https://ezycad.readthedocs.io/en/latest/usage.html#extrude-sketch-face-tool-e"},
@@ -125,7 +125,7 @@ Mode GUI::parent_mode_of(Mode mode)
{Mode::Move, Mode::Normal},
{Mode::Scale, Mode::Normal},
{Mode::Rotate, Mode::Normal},
- {Mode::Shape_cyl_align, Mode::Normal},
+ {Mode::Shape_shaft_align, Mode::Normal},
{Mode::Sketch_inspection_mode, Mode::Normal},
{Mode::Sketch_from_planar_face, Mode::Normal},
{Mode::Sketch_face_extrude, Mode::Normal},
@@ -306,7 +306,7 @@ void GUI::on_key(int key, int scancode, int action, int mods)
{
// Move / Rotate / Align shafts handle Tab in their mode key handlers (distance / angle / depth / twist).
const Mode mode = get_mode();
- if (mode == Mode::Move || mode == Mode::Rotate || mode == Mode::Shape_cyl_align)
+ if (mode == Mode::Move || mode == Mode::Rotate || mode == Mode::Shape_shaft_align)
break;
bool shift_pressed = (mods & GLFW_MOD_SHIFT) != 0;
@@ -319,7 +319,7 @@ void GUI::on_key(int key, int scancode, int action, int mods)
case GLFW_KEY_ENTER:
// Rotate / Align shafts finalize on Enter in their mode key handlers.
- if (get_mode() == Mode::Rotate || get_mode() == Mode::Shape_cyl_align)
+ if (get_mode() == Mode::Rotate || get_mode() == Mode::Shape_shaft_align)
break;
hide_sketch_origin_set_edit(true);
hide_dist_edit();
@@ -361,7 +361,7 @@ void GUI::on_key(int key, int scancode, int action, int mods)
on_key_rotate_mode_(key);
break;
- case Mode::Shape_cyl_align:
+ case Mode::Shape_shaft_align:
on_key_cyl_align_mode_(key, mods);
break;
@@ -378,7 +378,7 @@ void GUI::dispatch_hotkey_action_(Gui_action action)
case Gui_action::Mode_move: set_mode(Mode::Move); break;
case Gui_action::Mode_rotate: set_mode(Mode::Rotate); break;
case Gui_action::Mode_scale: set_mode(Mode::Scale); break;
- case Gui_action::Mode_cyl_align: set_mode(Mode::Shape_cyl_align); break;
+ case Gui_action::Mode_cyl_align: set_mode(Mode::Shape_shaft_align); break;
case Gui_action::Mode_extrude: set_mode(Mode::Sketch_face_extrude); break;
case Gui_action::Mode_chamfer: set_mode(Mode::Shape_chamfer); break;
case Gui_action::Mode_fillet: set_mode(Mode::Shape_fillet); break;
@@ -505,7 +505,7 @@ void GUI::options_()
case Mode::Move: options_move_mode_(); break;
case Mode::Rotate: options_rotate_mode_(); break;
case Mode::Scale: options_scale_mode_(); break;
- case Mode::Shape_cyl_align: options_shape_cyl_align_mode_(); break;
+ case Mode::Shape_shaft_align: options_shape_shaft_align_mode_(); break;
case Mode::Shape_chamfer: options_shape_chamfer_mode_(); break;
case Mode::Shape_fillet: options_shape_fillet_mode_(); break;
case Mode::Shape_polar_duplicate: options_shape_polar_duplicate_mode_(); break;
@@ -633,9 +633,9 @@ void GUI::options_scale_mode_()
options_orthographic_projection_();
}
-void GUI::options_shape_cyl_align_mode_()
+void GUI::options_shape_shaft_align_mode_()
{
- EZY_ASSERT(get_mode() == Mode::Shape_cyl_align);
+ EZY_ASSERT(get_mode() == Mode::Shape_shaft_align);
ImGui::TextUnformatted(current_mode_description_());
options_doc_help_button_();
diff --git a/src/gui_occt_view.cpp b/src/gui_occt_view.cpp
index 5731bb5..4dc8c0c 100644
--- a/src/gui_occt_view.cpp
+++ b/src/gui_occt_view.cpp
@@ -583,7 +583,7 @@ void Occt_view::cancel(Set_parent_mode set_parent_mode)
operation_canceled = true;
break;
- case Mode::Shape_cyl_align:
+ case Mode::Shape_shaft_align:
shp_cyl_align().cancel();
operation_canceled = true;
break;
@@ -1685,6 +1685,47 @@ bool Occt_view::fit_face_in_view(const TopoDS_Face& face)
return true;
}
+bool Occt_view::fit_shapes_in_view(const Shp_ptr& shp)
+{
+ if (is_headless() || m_view.IsNull() || shp.IsNull())
+ return false;
+
+ std::vector solids;
+ if (shp->is_group())
+ solids = shape_descendant_solids(shp->get_id());
+ else
+ solids.push_back(shp);
+
+ Bnd_Box bbox;
+ for (const Shp_ptr& s : solids)
+ {
+ if (s.IsNull())
+ continue;
+
+ const TopoDS_Shape& geom = s->Shape();
+ if (geom.IsNull())
+ continue;
+
+ Bnd_Box local;
+ BRepBndLib::Add(geom, local);
+ if (local.IsVoid())
+ continue;
+
+ const gp_Trsf& tr = s->LocalTransformation();
+ if (tr.Form() != gp_Identity)
+ local = local.Transformed(tr);
+
+ bbox.Add(local);
+ }
+
+ if (bbox.IsVoid())
+ return false;
+
+ m_view->FitAll(bbox, 0.1); // 10% padding; keep current camera orientation
+ m_view->Redraw();
+ return true;
+}
+
// Dimension related
void Occt_view::refresh_sketch_annotations(const Sketch_annotation_refresh& refresh)
{
@@ -2623,7 +2664,7 @@ void Occt_view::on_mouse_button(int theButton, int theAction, int theMods)
const bool finalize_transform = (get_mode() == Mode::Move && shp_move().has_operation_shps()) ||
(get_mode() == Mode::Rotate && shp_rotate().has_operation_shps()) ||
(get_mode() == Mode::Scale && shp_scale().has_operation_shps()) ||
- (get_mode() == Mode::Shape_cyl_align && shp_cyl_align().is_dragging());
+ (get_mode() == Mode::Shape_shaft_align && shp_cyl_align().is_dragging());
if (finalize_transform)
{
m_transform_finalize_lmb_skipped_view_controller = true;
@@ -3312,7 +3353,7 @@ void Occt_view::on_mode()
case Mode::Move: set_shp_selection_mode(TopAbs_SHAPE); break;
case Mode::Rotate: set_shp_selection_mode(TopAbs_SHAPE); break;
case Mode::Scale: set_shp_selection_mode(TopAbs_SHAPE); break;
- case Mode::Shape_cyl_align: set_shp_selection_mode(TopAbs_FACE); break;
+ case Mode::Shape_shaft_align: set_shp_selection_mode(TopAbs_FACE); break;
case Mode::Shape_cross_section: set_shp_selection_mode(TopAbs_COMPOUND); break;
default:
if(m_modes_selection_mode_map.count(get_mode()))
@@ -3351,7 +3392,7 @@ void Occt_view::on_mode()
}
}
- if (mode == Mode::Shape_cyl_align)
+ if (mode == Mode::Shape_shaft_align)
shp_cyl_align().begin();
if (mode == Mode::Shape_cross_section && !enter_selection.empty())
@@ -4393,7 +4434,7 @@ Mode mode_for_history_restore_(Mode mode)
case Mode::Move:
case Mode::Rotate:
case Mode::Scale:
- case Mode::Shape_cyl_align:
+ case Mode::Shape_shaft_align:
return GUI::parent_mode_of(mode);
default:
return mode;
diff --git a/src/gui_occt_view.h b/src/gui_occt_view.h
index 01d6323..671f096 100644
--- a/src/gui_occt_view.h
+++ b/src/gui_occt_view.h
@@ -287,6 +287,8 @@ class Occt_view : protected AIS_ViewController
void on_enter(const ScreenCoords& screen_coords); // For manual dimension distance keyboard input.
bool fit_face_in_view(const TopoDS_Face& face);
+ /// Fit the 3D view to a solid, or to all descendant solids of a group. Keeps camera orientation.
+ bool fit_shapes_in_view(const Shp_ptr& shp);
// Dimension related
void dimension_input(const ScreenCoords& screen_coords);
diff --git a/src/mode.h b/src/mode.h
index 7779c6e..058b645 100644
--- a/src/mode.h
+++ b/src/mode.h
@@ -30,7 +30,7 @@
X(Sketch_add_slot) \
X(Sketch_dim_anno) \
X(Shape_cross_section) \
- X(Shape_cyl_align)
+ X(Shape_shaft_align)
enum class Mode
{
diff --git a/src/shp_cyl_align.h b/src/shp_cyl_align.h
index 6b38d32..0f90bc2 100644
--- a/src/shp_cyl_align.h
+++ b/src/shp_cyl_align.h
@@ -28,14 +28,14 @@ class Shp_cyl_align : private Shp_operation_base
[[nodiscard]] Status drag_depth(const ScreenCoords& screen_coords);
[[nodiscard]] Status drag_twist(const ScreenCoords& screen_coords);
/// Depth + Clock rotation on: enter clock phase. Depth + off: finalize. Clock phase: finalize.
- void on_left_click();
+ void on_left_click();
/// Depth -> clock rotation (requires Options Clock rotation), then open angle edit.
- void begin_twist_input(const ScreenCoords& screen_coords);
- void show_depth_edit(const ScreenCoords& screen_coords);
- void show_twist_edit(const ScreenCoords& screen_coords);
- void finalize();
- void cancel();
- void reset();
+ void begin_twist_input(const ScreenCoords& screen_coords);
+ void show_depth_edit(const ScreenCoords& screen_coords);
+ void show_twist_edit(const ScreenCoords& screen_coords);
+ void finalize();
+ void cancel();
+ void reset();
Cyl_align_options& get_opts();
/// Apply Options Clock rotation toggle (exits clock phase if turned off).
diff --git a/src/shp_extrude.cpp b/src/shp_extrude.cpp
index e817e40..3352cb0 100644
--- a/src/shp_extrude.cpp
+++ b/src/shp_extrude.cpp
@@ -24,12 +24,12 @@
namespace
{
-size_t count_shape_edges_(const TopoDS_Shape& shape);
-gp_Pnt centroid_of_verts_(const std::vector& verts);
-TopoDS_Wire transform_wire_(const TopoDS_Wire& wire, const gp_Trsf& trsf);
-gp_Trsf section_trsf_(const gp_Ax1& axis, double height_along_axis, double twist_rad);
-TopoDS_Shape loft_twisted_wire_(const TopoDS_Wire& wire, const gp_Ax1& axis, double h0, double h1, double ang0,
- double ang1, int n_seg);
+size_t count_shape_edges_(const TopoDS_Shape& shape);
+gp_Pnt centroid_of_verts_(const std::vector& verts);
+TopoDS_Wire transform_wire_(const TopoDS_Wire& wire, const gp_Trsf& trsf);
+gp_Trsf section_trsf_(const gp_Ax1& axis, double height_along_axis, double twist_rad);
+TopoDS_Shape loft_twisted_wire_(const TopoDS_Wire& wire, const gp_Ax1& axis, double h0, double h1, double ang0, double ang1,
+ int n_seg);
std::vector face_hole_wires_(const TopoDS_Face& face, const TopoDS_Wire& outer_wire);
} // namespace
diff --git a/src/shp_info.cpp b/src/shp_info.cpp
index 36ab6d0..c7fc082 100644
--- a/src/shp_info.cpp
+++ b/src/shp_info.cpp
@@ -83,7 +83,8 @@ std::vector collect(const TopoDS_Shape& shape, const Display_meta* display
add_line_(lines, "BBox X", fmt_double_(xmin) + " .. " + fmt_double_(xmax));
add_line_(lines, "BBox Y", fmt_double_(ymin) + " .. " + fmt_double_(ymax));
add_line_(lines, "BBox Z", fmt_double_(zmin) + " .. " + fmt_double_(zmax));
- add_line_(lines, "BBox size", fmt_double_(xmax - xmin) + " x " + fmt_double_(ymax - ymin) + " x " + fmt_double_(zmax - zmin));
+ add_line_(lines, "BBox size",
+ fmt_double_(xmax - xmin) + " x " + fmt_double_(ymax - ymin) + " x " + fmt_double_(zmax - zmin));
}
GProp_GProps vol_props;
diff --git a/src/skt_display.cpp b/src/skt_display.cpp
index 0d860a3..2763852 100644
--- a/src/skt_display.cpp
+++ b/src/skt_display.cpp
@@ -25,13 +25,13 @@ constexpr float k_background_edge_rgba[4] = {0.3f, 0.3f, 0.3f, 0.3f};
constexpr float k_background_face_rgba[4] = {0.3f, 0.3f, 0.3f, 0.2f};
constexpr float k_edge_highlight_line_width = 2.0f;
-Quantity_Color rgb_from_rgba_(const float* rgba);
-float transparency_from_rgba_(const float* rgba);
-void apply_rgba_style_(AIS_Shape& shp, const float* rgba, float line_width);
-Prs3d_Drawer_ptr make_edge_hilight_drawer_(const float* rgba, float line_width);
-Prs3d_Drawer_ptr make_face_hilight_drawer_(const float* rgba);
-void apply_edge_hilight_(AIS_Shape& shp, const GUI& gui);
-void apply_face_hilight_(AIS_Shape& shp, const GUI& gui);
+Quantity_Color rgb_from_rgba_(const float* rgba);
+float transparency_from_rgba_(const float* rgba);
+void apply_rgba_style_(AIS_Shape& shp, const float* rgba, float line_width);
+Prs3d_Drawer_ptr make_edge_hilight_drawer_(const float* rgba, float line_width);
+Prs3d_Drawer_ptr make_face_hilight_drawer_(const float* rgba);
+void apply_edge_hilight_(AIS_Shape& shp, const GUI& gui);
+void apply_face_hilight_(AIS_Shape& shp, const GUI& gui);
} // namespace
void Sketch::update_edge_style_(const AIS_Shape_ptr& shp)
diff --git a/src/skt_nodes.cpp b/src/skt_nodes.cpp
index 4a7bd7f..efb9ada 100644
--- a/src/skt_nodes.cpp
+++ b/src/skt_nodes.cpp
@@ -26,9 +26,8 @@ bool s_annotate_all_coaxial_nodes = true;
Quantity_Color snap_guide_qc_(const glm::vec3& c);
void prepare_snap_ais_(AIS_InteractiveContext& ctx, const AIS_Shape_ptr& ais);
-void update_snap_ais_shape_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais, const TopoDS_Shape& shape,
- const glm::vec3& color);
-void clear_snap_ais_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais);
+void update_snap_ais_shape_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais, const TopoDS_Shape& shape, const glm::vec3& color);
+void clear_snap_ais_(AIS_InteractiveContext& ctx, AIS_Shape_ptr& ais);
} // namespace
class Sketch_nodes::Impl
diff --git a/src/skt_tools.cpp b/src/skt_tools.cpp
index 9fa2c68..0b57f93 100644
--- a/src/skt_tools.cpp
+++ b/src/skt_tools.cpp
@@ -172,7 +172,8 @@ bool Sketch_tools::complete_edge_from_center_(const ScreenCoords& screen_coords)
std::optional span;
if (m_sketch.m_dims.entered_edge_len().has_value())
- span = symmetric_edge_from_center_(center, m_sketch.m_dims.entered_edge_len()->dir, m_sketch.m_dims.entered_edge_len()->len);
+ span =
+ symmetric_edge_from_center_(center, m_sketch.m_dims.entered_edge_len()->dir, m_sketch.m_dims.entered_edge_len()->len);
else if (m_sketch.m_dims.entered_edge_angle().has_value())
{
@@ -302,7 +303,7 @@ void Sketch_tools::move_line_string_pt_(const ScreenCoords& screen_coords)
}
else if (m_sketch.m_dims.entered_edge_len().has_value())
span = symmetric_edge_from_center_(center, m_sketch.m_dims.entered_edge_len()->dir,
- m_sketch.m_dims.entered_edge_len()->len);
+ m_sketch.m_dims.entered_edge_len()->len);
else
span = symmetric_edge_from_center_and_hint_(center, pt_b);
diff --git a/src/utl_cad_file_info.cpp b/src/utl_cad_file_info.cpp
index 56e0ff1..a6d30fe 100644
--- a/src/utl_cad_file_info.cpp
+++ b/src/utl_cad_file_info.cpp
@@ -37,7 +37,7 @@ namespace utl_cad_file_info
namespace
{
std::vector collect_step_(const std::string& file_path, const std::string& file_bytes,
- const Atomic_progress_indicator_ptr& progress);
+ const Atomic_progress_indicator_ptr& progress);
void add_line_(std::vector& out, const char* label, const std::string& value);
@@ -86,24 +86,24 @@ std::string xcaf_product_name_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_La
void append_named_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, std::vector& out);
void append_tree_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, int parent_index,
- std::vector& out);
+ std::vector& out);
Status read_step_named_bodies_xcaf_(const std::string& file_bytes, std::vector& out,
- const Message_ProgressRange& progress);
+ const Message_ProgressRange& progress);
Status read_step_named_bodies_plain_(const std::string& file_bytes, std::vector& out,
- const Message_ProgressRange& progress);
+ const Message_ProgressRange& progress);
bool collect_step_has_status_error_(const std::vector& lines);
std::vector collect_step_xcaf_(const std::string& file_path, const std::string& file_bytes,
- const Atomic_progress_indicator_ptr& progress);
+ const Atomic_progress_indicator_ptr& progress);
std::vector collect_step_plain_(const std::string& file_path, const std::string& file_bytes,
- const Atomic_progress_indicator_ptr& progress);
+ const Atomic_progress_indicator_ptr& progress);
Status read_step_named_tree_xcaf_(const std::string& file_bytes, std::vector& out,
- const Message_ProgressRange& progress);
+ const Message_ProgressRange& progress);
} // namespace
Format detect(const std::string& file_path, const std::string& file_bytes)
@@ -568,7 +568,7 @@ void append_named_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Lab
}
void append_tree_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Label& lab, int parent_index,
- std::vector& out)
+ std::vector& out)
{
const std::string name = xcaf_product_name_(shapes, lab);
@@ -623,7 +623,7 @@ void append_tree_from_label_(const XCAFDoc_ShapeTool_ptr& shapes, const TDF_Labe
}
Status read_step_named_bodies_xcaf_(const std::string& file_bytes, std::vector& out,
- const Message_ProgressRange& progress)
+ const Message_ProgressRange& progress)
{
Interface_Static::SetCVal("xstep.cascade.unit", "MM");
@@ -668,7 +668,7 @@ Status read_step_named_bodies_xcaf_(const std::string& file_bytes, std::vector& out,
- const Message_ProgressRange& progress)
+ const Message_ProgressRange& progress)
{
Interface_Static::SetCVal("xstep.cascade.unit", "MM");
@@ -708,7 +708,7 @@ bool collect_step_has_status_error_(const std::vector& lines)
}
std::vector collect_step_xcaf_(const std::string& file_path, const std::string& file_bytes,
- const Atomic_progress_indicator_ptr& progress)
+ const Atomic_progress_indicator_ptr& progress)
{
std::vector lines;
append_common_(lines, file_path, file_bytes, Format::Step);
@@ -800,7 +800,7 @@ std::vector collect_step_xcaf_(const std::string& file_path, const std::st
}
std::vector collect_step_plain_(const std::string& file_path, const std::string& file_bytes,
- const Atomic_progress_indicator_ptr& progress)
+ const Atomic_progress_indicator_ptr& progress)
{
std::vector lines;
append_common_(lines, file_path, file_bytes, Format::Step);
@@ -840,7 +840,7 @@ std::vector collect_step_plain_(const std::string& file_path, const std::s
}
std::vector collect_step_(const std::string& file_path, const std::string& file_bytes,
- const Atomic_progress_indicator_ptr& progress)
+ const Atomic_progress_indicator_ptr& progress)
{
// Single Transfer pass (XCAF preferred). Avoids the old double-parse for Named bodies.
std::vector xcaf = collect_step_xcaf_(file_path, file_bytes, progress);
@@ -854,7 +854,7 @@ std::vector collect_step_(const std::string& file_path, const std::string&
}
Status read_step_named_tree_xcaf_(const std::string& file_bytes, std::vector& out,
- const Message_ProgressRange& progress)
+ const Message_ProgressRange& progress)
{
Interface_Static::SetCVal("xstep.cascade.unit", "MM");
diff --git a/src/utl_geom.cpp b/src/utl_geom.cpp
index 538de95..92f8229 100644
--- a/src/utl_geom.cpp
+++ b/src/utl_geom.cpp
@@ -474,11 +474,7 @@ std::optional cylinder_from_face(const TopoDS_Face& face)
return info;
}
-gp_Trsf cyl_align_trsf(const gp_Ax1& moving_axis,
- const gp_Ax1& fixed_axis,
- bool flip,
- double axial_offset,
- double twist_rad)
+gp_Trsf cyl_align_trsf(const gp_Ax1& moving_axis, const gp_Ax1& fixed_axis, bool flip, double axial_offset, double twist_rad)
{
const gp_Dir from_dir = moving_axis.Direction();
const gp_Dir to_dir = flip ? fixed_axis.Direction().Reversed() : fixed_axis.Direction();
@@ -640,11 +636,11 @@ namespace
{
constexpr double k_dim_text_height_base = 16.0;
-Prs3d_DimensionAspect_ptr clone_dimension_aspect_(const Handle(PrsDim_Dimension) & dim);
-void arrow_style_preset_(const int arrow_style, double& angle_deg, bool& arrows_3d);
-Prs3d_DimensionArrowOrientation arrow_orientation_from_index_(const int idx);
-void apply_dimension_label_text_aspect_(const Prs3d_TextAspect_ptr& text, const Quantity_Color& col,
- const Length_dimension_style& style);
+Prs3d_DimensionAspect_ptr clone_dimension_aspect_(const Handle(PrsDim_Dimension) & dim);
+void arrow_style_preset_(const int arrow_style, double& angle_deg, bool& arrows_3d);
+Prs3d_DimensionArrowOrientation arrow_orientation_from_index_(const int idx);
+void apply_dimension_label_text_aspect_(const Prs3d_TextAspect_ptr& text, const Quantity_Color& col,
+ const Length_dimension_style& style);
} // namespace
double length_dimension_auto_flyout(const double edge_len)
@@ -1522,9 +1518,8 @@ std::optional snap_foot_to_open_segment_interior_if_close(const gp_Pnt
namespace
{
Geom_TrimmedCurve_ptr edge_trimmed_curve_(const TopoDS_Edge& edge);
-bool on_segment_for_inclusion_(const gp_Pnt2d& p, const gp_Pnt2d& a, const gp_Pnt2d& b,
- Segment_inclusion inclusion);
-bool on_open_arc_parameter_(double u, double u_first, double u_last);
+bool on_segment_for_inclusion_(const gp_Pnt2d& p, const gp_Pnt2d& a, const gp_Pnt2d& b, Segment_inclusion inclusion);
+bool on_open_arc_parameter_(double u, double u_first, double u_last);
} // namespace
bool point_on_open_arc_interior_2d(const gp_Pnt2d& p, const TopoDS_Edge& arc_edge, const gp_Pln& pln)
diff --git a/src/utl_geom.h b/src/utl_geom.h
index 76c80d0..a676fd5 100644
--- a/src/utl_geom.h
+++ b/src/utl_geom.h
@@ -108,11 +108,8 @@ std::optional cylinder_from_face(const TopoDS_Face& face);
/// \a axial_offset then slides along the fixed direction. \a flip reverses the
/// moving direction sense (180 deg about a perpendicular). \a twist_rad is an
/// extra rotation about the fixed axis after coaxial placement (clock rotation).
-gp_Trsf cyl_align_trsf(const gp_Ax1& moving_axis,
- const gp_Ax1& fixed_axis,
- bool flip,
- double axial_offset,
- double twist_rad = 0.0);
+gp_Trsf cyl_align_trsf(const gp_Ax1& moving_axis, const gp_Ax1& fixed_axis, bool flip, double axial_offset,
+ double twist_rad = 0.0);
bool planes_equal(const gp_Pln& plane1, const gp_Pln& plane2);
diff --git a/src/utl_io.cpp b/src/utl_io.cpp
index db8cdb7..84f5730 100644
--- a/src/utl_io.cpp
+++ b/src/utl_io.cpp
@@ -24,11 +24,11 @@ struct Zip_entry
std::string data;
};
-uint32_t crc32_bytes_(const uint8_t* data, std::size_t len);
-void write_u16_(std::vector& out, uint16_t v);
-void write_u32_(std::vector& out, uint32_t v);
-bool read_u16_(const uint8_t* p, std::size_t avail, std::size_t& off, uint16_t& v);
-bool read_u32_(const uint8_t* p, std::size_t avail, std::size_t& off, uint32_t& v);
+uint32_t crc32_bytes_(const uint8_t* data, std::size_t len);
+void write_u16_(std::vector& out, uint16_t v);
+void write_u32_(std::vector& out, uint32_t v);
+bool read_u16_(const uint8_t* p, std::size_t avail, std::size_t& off, uint16_t& v);
+bool read_u32_(const uint8_t* p, std::size_t avail, std::size_t& off, uint32_t& v);
std::vector zip_write_stored_(const std::vector& entries);
bool zip_read_stored_(const std::string& bytes, std::vector& out_entries);
void collect_underlay_asset_ids_(const nlohmann::json& j, std::vector& out);
@@ -330,8 +330,9 @@ bool zip_read_stored_(const std::string& bytes, std::vector& out_entr
std::size_t off = eocd + 4;
uint16_t disk_num, disk_with_cd, num_entries_cd, num_entries_total;
uint32_t cd_size, cd_offset;
- if (!read_u16_(data, n, off, disk_num) || !read_u16_(data, n, off, disk_with_cd) || !read_u16_(data, n, off, num_entries_cd) ||
- !read_u16_(data, n, off, num_entries_total) || !read_u32_(data, n, off, cd_size) || !read_u32_(data, n, off, cd_offset))
+ if (!read_u16_(data, n, off, disk_num) || !read_u16_(data, n, off, disk_with_cd) ||
+ !read_u16_(data, n, off, num_entries_cd) || !read_u16_(data, n, off, num_entries_total) ||
+ !read_u32_(data, n, off, cd_size) || !read_u32_(data, n, off, cd_offset))
return false;
if (num_entries_cd != num_entries_total || cd_offset + cd_size > n)
diff --git a/src/utl_ply_io.cpp b/src/utl_ply_io.cpp
index bc83334..b9c8b79 100644
--- a/src/utl_ply_io.cpp
+++ b/src/utl_ply_io.cpp
@@ -57,16 +57,15 @@ struct ElementDesc
std::vector lists;
};
-void trim_inplace_(std::string& s);
-bool iequals_(const std::string& a, const std::string& b);
+void trim_inplace_(std::string& s);
+bool iequals_(const std::string& a, const std::string& b);
ScalarType scalar_from_token_(const std::string& t);
-int size_of_scalar_(ScalarType t);
-bool read_scalar_bin_at_(const unsigned char* base, size_t off, const unsigned char* endbuf, ScalarType t, double& out);
+int size_of_scalar_(ScalarType t);
+bool read_scalar_bin_at_(const unsigned char* base, size_t off, const unsigned char* endbuf, ScalarType t, double& out);
std::uint32_t read_list_count_bin_(const unsigned char*& p, const unsigned char* end, ScalarType ct);
-bool read_face_indices_bin_(const unsigned char*& p, const unsigned char* end, ScalarType vt, std::uint32_t n,
- std::vector& idx_out);
-bool append_triangle_(TopoDS_Compound& comp, BRep_Builder& bb, const gp_Pnt& p0, const gp_Pnt& p1, const gp_Pnt& p2,
- int& ntri);
+bool read_face_indices_bin_(const unsigned char*& p, const unsigned char* end, ScalarType vt, std::uint32_t n,
+ std::vector& idx_out);
+bool append_triangle_(TopoDS_Compound& comp, BRep_Builder& bb, const gp_Pnt& p0, const gp_Pnt& p1, const gp_Pnt& p2, int& ntri);
} // namespace
Status import_ply_shape(const std::string& file_bytes, TopoDS_Shape& out_shape)