diff --git a/CHANGELOG.md b/CHANGELOG.md
index f9c7341..dd36333 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Fixed
+
+- WASM (OCCT 7.9.3): stop forcing a near-white AIS `SetColor` after every material apply. That OCCT 8 GLES workaround had been running on all Emscripten builds and made steel, gold, and other presets look identical (glass still showed transparency).
+- WASM: clear AIS `OwnColor` when applying a Shape List material so presets are not stuck on a forced color. Prefer the **OCCT 7.9.3** wasm kit — OCCT 8.x GLES still has known shading regressions (`docs/building-occt.md`).
+
+### Cross-section
+
+- Added a temporary **Shape cross-section** preview tool for selected solids. Entering the tool with solids already selected updates the preview immediately; changing the selection while in the tool also updates it. Options choose a shared local **XY**, **XZ**, or **YZ** plane (first-selected axes, selection-bbox center), **Invert normal** (flips the annotation arrow and positive-offset direction while keeping the plane in place), **Hide back side** (on by default; display-only AIS clip opposite the yellow arrow), a bounding-box **Offset** slider (Ctrl+click to type; auto-updates preview) in project units, and **Clip** (half-space cut: keeps the positive-normal side, deletes the originals, adds clipped replacements; undoable). A translucent yellow plane and normal arrow annotate the cut. Solids the plane misses are skipped; the preview clears only if nothing in the selection is cut.
+- Desktop preview sections multiple selected solids in parallel (worker pool); WASM remains single-threaded for the per-solid pool. Bounding-box culling skips solids that cannot meet the plane. Offset/plane dragging updates the yellow plane immediately and runs section wires asynchronously (cancel + latest-pending) so the Options slider stays responsive.
+- Shapes now carry a bbox-centered local frame that follows baked move/rotate/scale transforms and persists in project JSON and shape undo records.
+
### Shape List
- **Shape List** is a hierarchical outliner: organizational **groups**, expand/collapse, drag-and-drop reparent, **Group** / **New group** / **Ungroup**, cascade delete, and Ctrl+click multi-select. Click a group (including empty) to set the **current group**; new primitives/extrudes/revolves are added there. Drop on empty space below the list to move a node to the document root. Group visibility hides the subtree. **Hide all** no longer clears per-shape visibility flags. Boolean results inherit a shared parent when inputs share one. STEP import preserves XCAF assembly groups (unless **Union shapes**).
diff --git a/agents/conventions/occt-wasm-dual-version.md b/agents/conventions/occt-wasm-dual-version.md
index cb15bb7..4bc6262 100644
--- a/agents/conventions/occt-wasm-dual-version.md
+++ b/agents/conventions/occt-wasm-dual-version.md
@@ -10,6 +10,8 @@
| WASM (Emscripten) | **7.9.3** (`V7_9_3`) recommended | `scripts/build-occt-793-wasm.ps1` |
| WASM (compare) | 8.0.0.p1 (`V8_0_0_p1`) experimental | Shading regressions; not for demos/release |
+Do **not** apply OCCT 8 GLES workarounds unconditionally under `__EMSCRIPTEN__`. Example: `Occt_view::refresh_shape_shading_` must not force a fixed AIS `SetColor` on 7.9.3 (washes out material presets). Gate with `OCC_VERSION_HEX >= 0x080000`, and when a color is required on OCCT 8 prefer the material's own color.
+
Details: [docs/building-occt.md](../../docs/building-occt.md), [workflows/local-dev.md](../workflows/local-dev.md).
## Shared `src/` must compile on both
diff --git a/agents/drafts/issues/active/gh-218-cross-section-tool.md b/agents/drafts/issues/active/gh-218-cross-section-tool.md
new file mode 100644
index 0000000..2d0d7ca
--- /dev/null
+++ b/agents/drafts/issues/active/gh-218-cross-section-tool.md
@@ -0,0 +1,38 @@
+---
+github_issue: 218
+github_pr: 219
+status: active
+paired_draft: ../prs/active/gh-219-cross-section-tool.md
+---
+
+# Add temporary shape cross-section preview tool
+
+**Suggested labels:** `enhancement`, `ui`, `feature-request`
+
+---
+
+## Title (GitHub)
+
+Add temporary shape cross-section preview tool
+
+## Body (GitHub)
+
+See https://github.com/trailcode/EzyCad/issues/218
+
+### Summary
+
+Interactive **Shape cross-section** tool: shared local plane cut of selected solids with temporary AIS preview; Invert normal, Hide back side, Clip; async wires + desktop parallel pool (no sketch commit in v0).
+
+### Acceptance criteria
+
+- [x] Auto-preview on enter / selection / plane / offset / Invert
+- [x] Hide back side (default on); Clip undoable half-space
+- [x] Bold empty-selection prompt; leave mode or clear selection removes preview
+- [x] Focused tests `Shp_cross_section.*` / `Shp_test.Cross_section*`
+- [x] Docs + CHANGELOG updated
+
+### Related
+
+- PR: https://github.com/trailcode/EzyCad/pull/219
+- Plan: `agents/plans/cross-section-tool.md`
+- Related tracking: #220
diff --git a/agents/drafts/issues/active/gh-220-wasm-alt-drag-multiselect.md b/agents/drafts/issues/active/gh-220-wasm-alt-drag-multiselect.md
new file mode 100644
index 0000000..d49da5d
--- /dev/null
+++ b/agents/drafts/issues/active/gh-220-wasm-alt-drag-multiselect.md
@@ -0,0 +1,38 @@
+---
+github_issue: 220
+status: active
+---
+
+# WASM: Alt + LMB drag does not multi-select shapes
+
+**Suggested labels:** `bug`
+
+**Opened:** https://github.com/trailcode/EzyCad/issues/220
+
+---
+
+## Title (GitHub)
+
+WASM: Alt + LMB drag does not multi-select shapes
+
+## Body (GitHub)
+
+See https://github.com/trailcode/EzyCad/issues/220
+
+### Summary
+
+Desktop: **Alt + LMB drag** rubber-band multi-selects shapes via OCCT `AIS_ViewController`. WASM: same gesture does not select multiple shapes.
+
+### Acceptance criteria
+
+- [ ] WASM Alt + LMB drag multi-selects like desktop
+- [ ] Desktop unchanged
+- [ ] Docs note Alt+drag (and any Web caveats)
+- [ ] Plan updated: `agents/plans/wasm-alt-drag-multiselect.md`
+
+### Related
+
+- Issue: https://github.com/trailcode/EzyCad/issues/220
+- Plan: `agents/plans/wasm-alt-drag-multiselect.md` (tracking on PR #219; bug not fixed there)
+- Related: #93 / `gh-93-emscripten-web-hotkeys-followup.md`
+- PR that added tracking: https://github.com/trailcode/EzyCad/pull/219
diff --git a/agents/drafts/prs/active/gh-219-cross-section-tool.md b/agents/drafts/prs/active/gh-219-cross-section-tool.md
new file mode 100644
index 0000000..6ad9b80
--- /dev/null
+++ b/agents/drafts/prs/active/gh-219-cross-section-tool.md
@@ -0,0 +1,32 @@
+---
+github_issue: 218
+github_pr: 219
+status: active
+paired_draft: ../issues/active/gh-218-cross-section-tool.md
+---
+
+# PR - Trailcode/cross-section
+
+## Title
+
+Add temporary shape cross-section preview tool
+
+## Summary
+
+- Temporary shape cross-section preview (local XY/XZ/YZ + offset, Invert normal, Hide back side, Clip).
+- Async section wires + desktop parallel pool; WASM serial (see `wasm-multithreading.md`).
+- Shape local frames for plane orientation / persistence.
+
+## Related
+
+- Issue: https://github.com/trailcode/EzyCad/issues/218
+- PR: https://github.com/trailcode/EzyCad/pull/219
+- Branch: `Trailcode/cross-section`
+- Plan: `agents/plans/cross-section-tool.md`
+
+## Test Plan
+
+- [ ] Desktop Release build
+- [ ] `EzyCad_tests --gtest_filter=Shp_cross_section.*:Shp_test.Cross_section*`
+- [ ] Manual enter / selection / plane / offset / Hide back / Clip / Esc
+- [ ] Docs sync checklist
diff --git a/agents/plans/README.md b/agents/plans/README.md
index 44d20a5..8a87285 100644
--- a/agents/plans/README.md
+++ b/agents/plans/README.md
@@ -20,3 +20,5 @@ sketch-mode-shape-faint (parallel UX: ghost/wire shapes while sketching)
| [sketch-from-shape-section.md](sketch-from-shape-section.md) | "sketch from shape", section → editable sketch (end-state; deferred) |
| [sketch-mode-shape-faint.md](sketch-mode-shape-faint.md) | faint/ghost/wireframe shapes in sketch mode, alpha blending solids |
| [shape-list-hierarchy-phase3.md](shape-list-hierarchy-phase3.md) | parent transform inheritance, Parts/planes, Boolean history (#214) |
+| [wasm-multithreading.md](wasm-multithreading.md) | WASM/Emscripten pthreads, SharedArrayBuffer, parallel OCCT on web |
+| [wasm-alt-drag-multiselect.md](wasm-alt-drag-multiselect.md) | WASM Alt+LMB drag rectangle multi-select broken (#220) |
diff --git a/agents/plans/cross-section-tool.md b/agents/plans/cross-section-tool.md
index 2284778..7be0d49 100644
--- a/agents/plans/cross-section-tool.md
+++ b/agents/plans/cross-section-tool.md
@@ -1,5 +1,5 @@
---
-status: planning
+status: implemented-v0
topic: cross-section-tool
depends_on: shp-origin-orientation
blocks:
@@ -32,12 +32,12 @@ Fail closed on empty section (status message). No requirement to create a `Sketc
## Experiment checklist
-- [ ] Box mid-plane → rectangle-like wires.
-- [ ] Cylinder mid-plane → circle (or dense curve).
-- [ ] Offset along normal; miss solid → clear failure.
-- [ ] Multi-face / boolean solids: junk edges? duplicates?
-- [ ] Curve types returned (`GeomAbs_*`) — informs sketch import strategy.
-- [ ] WASM vs desktop parity ([occt-wasm-dual-version](../conventions/occt-wasm-dual-version.md)).
+- [x] Box mid-plane -> four line edges (focused unit test).
+- [x] Cylinder mid-plane -> circular edge (focused unit test).
+- [x] Offset along normal; miss solid -> clear failure (focused unit test).
+- [x] Multi-face / boolean solids: junk edges? duplicates? (fused two-box mid-plane returns **8** line edges, not a clean 4-edge rectangle — sketch import will need weld/dedup).
+- [x] Curve types returned (`GeomAbs_*`) -> preview status counts lines, circles, ellipses, B-splines, and other curves.
+- [x] WASM vs desktop parity ([occt-wasm-dual-version](../conventions/occt-wasm-dual-version.md)).
## Likely touch points
@@ -58,3 +58,13 @@ Enough evidence to either:
Import into `Sketch_edges`; undoable new sketch; script binding (unless useful for tests).
**Related UX:** [sketch-mode-shape-faint.md](sketch-mode-shape-faint.md) — keep the solid visible but faint under section preview.
+
+## Implemented v0 findings
+
+- `Shp` now has a persisted `gp_Ax3` local frame. It defaults to a world-aligned frame at the shape bounding-box center and follows baked move/rotate/scale transforms.
+- `Shp_cross_section` computes one shared cutting plane for the selection with `BRepAlgoAPI_Section` (`Approximation(false)` for exact curve types): orientation from the first selected solid's local axes, origin at the selection bbox center, plus offset. Section edges aggregate into a temporary topmost cyan AIS wire preview, with one translucent yellow plane outline and positive-normal arrow. None of this temporary geometry enters the document or undo history.
+- Preview updates fail closed only when the shared plane misses every selected solid, or a hard OCCT failure occurs. Solids the plane does not intersect are skipped and counted in the status message; the yellow plane annotation remains.
+- The first curve evidence supports line and circle handling. Ellipse/B-spline/other counts are exposed for further manual experiments before sketch import rules are chosen.
+- Boolean solids are not yet sketch-ready: a fused two-box mid-plane yields 8 line edges (likely unmerged/overlapping segments) instead of one 4-edge outer loop.
+- Desktop Release build and focused section tests pass with OCCT 8.0.0. The Emscripten Release build also passes with OCCT 7.9.3.
+- Desktop multi-solid preview sections solids on a `std::thread` worker pool (`for_each_index_` / `section_shapes_on_plane_`); WASM remains single-threaded for that pool. Interactive Options use `request_preview` + `poll`: plane AIS updates immediately; section wires run async with cancel + latest-pending (desktop `std::async`; WASM one solid per poll). WASM pthread enablement is tracked in [wasm-multithreading.md](wasm-multithreading.md).
diff --git a/agents/plans/wasm-alt-drag-multiselect.md b/agents/plans/wasm-alt-drag-multiselect.md
new file mode 100644
index 0000000..4dffebe
--- /dev/null
+++ b/agents/plans/wasm-alt-drag-multiselect.md
@@ -0,0 +1,73 @@
+---
+status: planned
+topic: wasm-alt-drag-multiselect
+depends_on: []
+blocks: []
+github_issue: 220
+---
+
+# WASM Alt + LMB drag multi-select
+
+**Load only when** the prompt is about WASM/Emscripten Alt+drag, rectangle / rubber-band selection, or multi-select failing on web. Skip otherwise ([token-lean](../conventions/token-lean.md)). Index: [plans/README.md](README.md).
+
+**Issue:** https://github.com/trailcode/EzyCad/issues/220
+
+## Context
+
+On desktop, holding **Alt** and **left-dragging** in the 3D view activates OCCT `AIS_ViewController` rectangle selection (`SelectRectangle` / rubber band) and multi-selects shapes under the box.
+
+On the WASM build the same gesture does **not** multi-select.
+
+EzyCad already maps modifiers into OCCT flags:
+
+- `Occt_view::key_flags_from_glfw_` — `GLFW_MOD_ALT` → `Aspect_VKeyFlags_ALT`
+- Mouse press/release: `PressMouseButton` / `ReleaseMouseButton` with `theMods`
+- Mouse move: `UpdateMousePosition(..., key_flags_from_glfw_window_(), ...)` which polls `glfwGetKey(... LEFT/RIGHT_ALT ...)`
+
+Related input/hotkey tracking: [#93](https://github.com/trailcode/EzyCad/issues/93), draft `agents/drafts/issues/active/gh-93-emscripten-web-hotkeys-followup.md`. OCCT desktop vs wasm kit: [occt-wasm-dual-version](../conventions/occt-wasm-dual-version.md).
+
+## Goal
+
+Make **Alt + LMB drag** multi-select on WASM match desktop, without changing desktop gesture mapping or selection schemes.
+
+## Likely causes (investigate in order)
+
+1. **Alt not present on pointer events under Emscripten/GLFW** — browser menu focus, `preventDefault`, or incomplete `mods` on `glfwSetMouseButtonCallback`.
+2. **`glfwGetKey(ALT)` false during drag** — rubber-band gesture needs Alt on move updates (`key_flags_from_glfw_window_`), not only on button press.
+3. **ImGui / focus** — canvas loses keyboard focus when Alt is pressed; Alt never reaches GLFW.
+4. **OCCT 7.9.3 wasm kit** — confirm default `MouseGestureMap` still binds Alt+LMB to select-rectangle (unlikely difference, but verify if input looks correct).
+
+## Approach
+
+### Phase 0 — reproduce and instrument
+
+- [ ] Confirm desktop Alt+LMB rubber band still works.
+- [ ] On WASM, log (temporary) `mods` on LMB press/release and Alt from `key_flags_from_glfw_window_` during drag.
+- [ ] Note browser (Chrome/Firefox/Edge) and whether the OS/browser steals Alt.
+
+### Phase 1 — fix input path
+
+- [ ] Ensure Alt is forwarded for press, move, and release while the gesture is active (Emscripten/GLFW and/or synthetic modifier from `event.altKey` if GLFW is incomplete).
+- [ ] Avoid focusing browser chrome on Alt when the canvas has focus (as far as the platform allows).
+- [ ] Keep `GUI::on_mouse_button` / sketch `mods == 0` click paths unchanged for unmodified LMB.
+
+### Phase 2 — docs and parity
+
+- [ ] Document Alt+drag multi-select in `docs/usage.md` and/or `docs/usage-occt-view.md`; call out any remaining Web limitation.
+- [ ] Manual check: Ctrl+click multi-select still works on WASM if it already does; Alt+drag matches desktop.
+- [ ] Close #220 when done; update this plan status.
+
+## Out of scope
+
+- WASM pthreads / parallel OCCT ([wasm-multithreading.md](wasm-multithreading.md)).
+- Broader #93 hotkey parity beyond what this gesture needs.
+- Changing default OCCT mouse gesture map on desktop.
+
+## Related code
+
+| Area | Path |
+| ---------------------------- | ----------------------------------------- |
+| Modifier → OCCT flags | `src/gui_occt_view.cpp` (`key_flags_*`) |
+| Mouse → view controller | `src/gui_occt_view.cpp` (`on_mouse_*`) |
+| GLFW → GUI | `src/main.cpp`, `src/gui.cpp` |
+| Draft | `agents/drafts/issues/active/gh-220-*.md` |
diff --git a/agents/plans/wasm-multithreading.md b/agents/plans/wasm-multithreading.md
new file mode 100644
index 0000000..f88ca8c
--- /dev/null
+++ b/agents/plans/wasm-multithreading.md
@@ -0,0 +1,110 @@
+---
+status: planned
+topic: wasm-multithreading
+depends_on: []
+blocks: []
+---
+
+# WASM multithreading (Emscripten pthreads)
+
+**Load only when** the prompt is about WASM/Emscripten threads, pthreads, SharedArrayBuffer, parallel OCCT on the web, or enabling the cross-section (or similar) worker pool under `__EMSCRIPTEN__`. Skip otherwise ([token-lean](../conventions/token-lean.md)). Index: [plans/README.md](README.md).
+
+## Context (what already exists)
+
+Desktop cross-section preview already parallelizes per-solid `BRepAlgoAPI_Section` via a small worker pool in `shp_cross_section.cpp` (`for_each_index_` / `section_shapes_on_plane_`). WASM is intentionally serial:
+
+```cpp
+#ifdef __EMSCRIPTEN__
+ // single-threaded loop
+#else
+ // std::thread pool sized to hardware_concurrency
+#endif
+```
+
+AIS / viewer updates stay on the main thread after results are collected. BBox cull skips obvious plane misses on both targets.
+
+Related: [cross-section-tool.md](cross-section-tool.md), [occt-wasm-dual-version](../conventions/occt-wasm-dual-version.md) (desktop OCCT 8 vs wasm 7.9.3).
+
+## Why WASM needs its own plan
+
+Emscripten does not get free `std::thread` parallelism. Real threads require:
+
+1. Compile/link with **`-pthread`** (app + preferably OCCT wasm kit).
+2. Browser **SharedArrayBuffer**, which needs **cross-origin isolation** (`Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` or equivalent) on the hosting page and assets.
+3. Larger binary, pthread pool sizing (`PTHREAD_POOL_SIZE` / `PROXY_TO_PTHREAD` choices), and careful main-thread vs worker ownership of OpenGL/AIS.
+
+Until that stack exists, keep the `#ifdef __EMSCRIPTEN__` serial path. Do not pretend `SetRunParallel(true)` alone enables multi-core on current single-threaded wasm builds.
+
+## Goal
+
+Enable the **same parallel map pattern** used by cross-section preview on WASM when isolation + pthread builds are available, without regressing:
+
+- Non-isolated hosts (fallback to serial).
+- Desktop behavior.
+- OCCT 7.9.3 wasm kit compatibility ([occt-wasm-dual-version](../conventions/occt-wasm-dual-version.md)).
+
+Cross-section is the **first consumer / proof**; later ops (multi-solid Clip half-space, other embarrassingly parallel BREP maps) should reuse one helper.
+
+## Approach (phased)
+
+### Phase 0 — document + gate (no behavior change)
+
+- [ ] Record current desktop pool API shape (`for_each_index_` or a shared `utl_parallel.h` helper).
+- [ ] Keep WASM serial by default.
+- [ ] Note hosting constraints in [docs/building-occt.md](../../docs/building-occt.md) / wasm run notes when work starts.
+
+### Phase 1 — Emscripten pthread build option
+
+- [ ] CMake/Emscripten option (e.g. `EZYCAD_WASM_PTHREADS=ON`) adding `-pthread` to compile and link.
+- [ ] Set sensible defaults: `PTHREAD_POOL_SIZE` (start small, e.g. 4), memory growth still allowed.
+- [ ] Rebuild or document whether the **OCCT 7.9.3 wasm kit** must also be built with pthreads (likely yes for allocator / internal parallel safety).
+- [ ] CI or local smoke: Release wasm with option off (current) and on (new).
+
+### Phase 2 — Hosting / COOP+COEP
+
+- [ ] Serve demo/pages with COOP/COEP (or document required headers for GitHub Pages / custom host).
+- [ ] Runtime detect: if `crossOriginIsolated` is false, **force serial** even in a pthread build (avoid SAB failures).
+- [ ] Shell HTML / deploy notes updated so agents and maintainers know why threads are off.
+
+### Phase 3 — Wire shared parallel helper on WASM
+
+- [ ] Lift `for_each_index_` (or equivalent) to a shared utility used by `shp_cross_section` and future callers.
+- [ ] Under `__EMSCRIPTEN__` + pthread + `crossOriginIsolated`: use the same worker-pool logic as desktop.
+- [ ] Otherwise: serial loop (today’s behavior).
+- [ ] Still: **no AIS/OCCT viewer calls from workers**; only BREP compute, then main-thread Display.
+
+### Phase 4 — Hardening
+
+- [ ] Stress test: many selected solids in cross-section preview (Offset drag) on Chrome/Firefox/Safari as available.
+- [ ] Watch for OCCT allocator / intermittent section crashes under concurrent `BRepAlgoAPI_Section` (known historical risk); fall back to serial if unstable on 7.9.3.
+- [ ] Optional: cancel/stale-token so an old preview does not finish after a newer one (helps UI more than threads alone).
+- [ ] Binary size / startup cost note in CHANGELOG when enabling by default.
+
+## Non-goals (for this plan)
+
+- Web Worker with a second full OCCT instance (heavier architecture; revisit only if pthreads prove insufficient).
+- Making all OCCT booleans internally `SetRunParallel` on wasm without measuring.
+- Requiring pthreads for every wasm deploy (isolated hosting may stay optional).
+
+## Success
+
+- With pthread build + cross-origin isolation, multi-solid cross-section preview uses multiple cores on wasm similarly to desktop.
+- Without isolation or with pthreads off, behavior matches today’s single-threaded wasm (no crash, no SAB error).
+- One reusable parallel-for helper; cross-section remains the reference caller.
+
+## Likely touch points
+
+| Area | Notes |
+| ---------------------------- | --------------------------------------------------------------------- |
+| `src/shp_cross_section.cpp` | First consumer; drop hard serial-only `#ifdef` once helper exists |
+| New `utl_parallel.*` (maybe) | Shared `for_each_index` / pool; desktop + wasm gated |
+| `CMakeLists.txt` | `EZYCAD_WASM_PTHREADS`, link flags, pool size |
+| `scripts/build-occt-*-wasm*` | Rebuild OCCT kit with `-pthread` if required |
+| `web/EzyCad.html` / host | COOP/COEP or meta documentation |
+| Docs | `building-occt.md`, maybe short wasm run section |
+
+## Open questions
+
+1. Must the OCCT wasm kit itself be pthread-linked, or is app-only `-pthread` enough for independent `BRepAlgoAPI_Section` instances?
+2. Default ON for demo builds, or opt-in forever for smaller non-isolated deploys?
+3. Safari / iOS SharedArrayBuffer support matrix for our supported browsers?
diff --git a/docs/usage-settings.md b/docs/usage-settings.md
index 8877bf0..9a127b4 100644
--- a/docs/usage-settings.md
+++ b/docs/usage-settings.md
@@ -104,7 +104,7 @@ 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).
+For other non-sketch Options content, see the matching tool section in the usage guide. In particular, **Shape cross-section** provides local **XY/XZ/YZ**, **Invert normal**, **Hide back side**, a bbox-ranged **Offset** slider (Ctrl+click to type), **Clip** (half-space cut, replaces selection), and automatic preview updates when the selection changes; see [Shape cross-section tool](usage.md#shape-cross-section-tool).
### Sketch tools
@@ -223,7 +223,7 @@ If saved layout text has no `[Docking]` section (older installs), a default dock
| `view_roll_step_deg` | number | Degrees per **NumPad 8**/**2**/**4**/**6** orbit and **Shift+NumPad 4**/**6** roll (allowed range **0.1** to **180** in code; default **45**). |
| `view_zoom_scroll_scale` | number | Multiplier for `UpdateZoom` scroll delta from wheel and keyboard zoom (allowed range **0.25** to **64** in code; default **4**). With **Shift** held, the effective step is multiplied by **0.1** (Blender-style finer zoom). |
| `default_project_unit` | string | Default **File -> New** project unit: `"inch"` or `"millimeter"` (default **`inch`**). Edited under **Settings -> New project defaults**. |
-| `default_2d_view_width` | number | Horizontal sketch-plane span for **File -> New** / projects with no saved camera, stored in **inches** (allowed range **0.1** to **1000**; default **3**). Settings UI shows this in **`default_project_unit`**. |
+| `default_2d_view_width` | number | Horizontal sketch-plane span for **File -> New** / projects with no saved camera, stored in **inches** (allowed range **0.1** to **1000**; default **3**). Settings UI shows this in **`default_project_unit`**. |
| `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. |
@@ -244,20 +244,20 @@ Each **`imgui_style_dark`** / **`imgui_style_light`** object may contain:
Each **`settings_headers`** object may contain (default: only nested Sketch **Appearance** / **Dimensions** expanded):
-| Key | Type | Meaning |
-| ------------------- | ------- | ---------------------------------------- |
-| `view_nav` | boolean | **3D view navigation** section expanded. |
+| Key | Type | Meaning |
+| ------------------- | ------- | ------------------------------------------ |
+| `view_nav` | boolean | **3D view navigation** section expanded. |
| `new_project` | boolean | **New project defaults** section expanded. |
-| `ui` | boolean | **UI** section expanded. |
-| `view_presentation` | boolean | **View presentation** section expanded. |
-| `grid` | boolean | **3D view grid** section expanded. |
-| `sketch` | boolean | **Sketch** section expanded. |
-| `sketch_appearance` | boolean | Nested **Appearance** under Sketch. |
-| `sketch_dimensions` | boolean | Nested **Dimensions** under Sketch. |
-| `sketch_nodes` | boolean | Nested **Nodes** under Sketch. |
-| `sketch_snap` | boolean | Nested **Snap** under Sketch. |
-| `sketch_underlay` | boolean | Nested **Underlay** under Sketch. |
-| `startup` | boolean | **Startup project** section expanded. |
+| `ui` | boolean | **UI** section expanded. |
+| `view_presentation` | boolean | **View presentation** section expanded. |
+| `grid` | boolean | **3D view grid** section expanded. |
+| `sketch` | boolean | **Sketch** section expanded. |
+| `sketch_appearance` | boolean | Nested **Appearance** under Sketch. |
+| `sketch_dimensions` | boolean | Nested **Dimensions** under Sketch. |
+| `sketch_nodes` | boolean | Nested **Nodes** under Sketch. |
+| `sketch_snap` | boolean | Nested **Snap** under Sketch. |
+| `sketch_underlay` | boolean | Nested **Underlay** under Sketch. |
+| `startup` | boolean | **Startup project** section expanded. |
Scripting API **`ezy.occt_view_settings_json()`** returns a JSON string with **`occt_view`** plus selected **`gui`** keys (including dimension and snap keys above, **`gui.permanent_node_anno_scale`**, **`gui.inspection_orthographic`**, **`gui.view_roll_step_deg`**, **`gui.view_zoom_scroll_scale`**, **`gui.default_project_unit`**, **`gui.default_2d_view_width`**, **`gui.default_2d_view_height`** when saved). See [scripting.md](scripting.md).
diff --git a/docs/usage.md b/docs/usage.md
index 79258b0..dce4527 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -251,10 +251,10 @@ The typical modeling workflow in EzyCad follows these steps:
**File -> Import** opens an **Import** window for STEP or PLY. Review metadata, optionally enable **Union shapes** (STEP), then click **Import into project**. The window closes after a successful import.
-| Format | What the Import dialog shows |
-| -------------------------- | -------------------------------------------------------------- |
-| **STEP** (`.step`, `.stp`) | Roots/shapes, import bodies, named bodies, topology, bbox |
-| **PLY** (`.ply`) | Encoding, vertex and face counts from the header |
+| Format | What the Import dialog shows |
+| -------------------------- | --------------------------------------------------------- |
+| **STEP** (`.step`, `.stp`) | Roots/shapes, import bodies, named bodies, topology, bbox |
+| **PLY** (`.ply`) | Encoding, vertex and face counts from the header |
**How to use:**
1. Choose **File -> Import**
@@ -290,10 +290,10 @@ In addition to creating 3D shapes from sketches, EzyCad supports importing exist
**Units:** Sketch and display lengths follow **File -> Project units** (**Inches** or **Millimeters**). Internal model coordinates stay inch-scaled (`model = inches * dimension_scale`, default **100**). Changing project units only remaps the UI; it does not rewrite geometry. Import scaling:
-| Format | File units | Into EzyCad |
-| -------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------ |
-| **STEP** (`.step`, `.stp`) | Declared in the file (often mm); OCCT reads them as mm, then converted | Scaled so lengths match the inch-scaled model |
-| **PLY** (`.ply`) | No standard unit metadata | Vertex coords are treated as **inches** |
+| Format | File units | Into EzyCad |
+| -------------------------- | ---------------------------------------------------------------------- | --------------------------------------------- |
+| **STEP** (`.step`, `.stp`) | Declared in the file (often mm); OCCT reads them as mm, then converted | Scaled so lengths match the inch-scaled model |
+| **PLY** (`.ply`) | No standard unit metadata | Vertex coords are treated as **inches** |
**PLY import notes:**
- Supported: **ASCII** PLY and **binary little-endian** PLY.
@@ -324,10 +324,10 @@ Use **File -> Export** to save the current model for other CAD tools, CAM, or 3D
**Units:** After you choose a format, an **Export units** dialog asks for **Inches** or **Millimeters** (defaults: millimeters for STEP/IGES, inches for STL/PLY).
-| Format | How units are applied |
-| ------------------- | --------------------------------------------------------------------------- |
-| **STEP** / **IGES** | Geometry scaled to the chosen unit; that unit is written into the CAD file |
-| **STL** / **PLY** | Geometry scaled to the chosen unit; files have **no** unit metadata |
+| Format | How units are applied |
+| ------------------- | -------------------------------------------------------------------------- |
+| **STEP** / **IGES** | Geometry scaled to the chosen unit; that unit is written into the CAD file |
+| **STL** / **PLY** | Geometry scaled to the chosen unit; files have **no** unit metadata |
**Mesh exports (STL and PLY):** Surfaces are **tessellated** with a fixed linear deflection (same idea as typical STL export). Very complex B-rep models produce large mesh files.
@@ -370,6 +370,7 @@ More detail: [Sketch snapping](usage-sketch.md#sketch-snapping) in the sketch gu
-  [Rotate objects (R)](#shape-rotate-tool-r)
-  [Scale elements (S)](#shape-scale-tool)
-  [Polar duplicate](#shape-polar-duplicate-tool)
+ -  [Preview a local-plane cross-section](#shape-cross-section-tool)
#### Shape Move Tool (G)
@@ -490,7 +491,7 @@ The extrude tool allows you to create 3D solid shapes by extruding 2D sketch fac
| | |
| -------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Orthographic camera** | Extrude mode forces **orthographic** projection (same as other sketch tools) so extrusion height is easier to judge without perspective foreshortening |
-| **Direct face selection** | Click directly on a sketch face to select it for extrusion, or use **`E`** / right-click **Extrude** on a face in the [Sketch List](#sketch-list) |
+| **Direct face selection** | Click directly on a sketch face to select it for extrusion, or use **`E`** / right-click **Extrude** on a face in the [Sketch List](#sketch-list) |
| **Automatic view adjustment** | The view automatically rotates if the face plane is parallel to the view plane (within 5 degrees), providing better visibility for the extrusion operation |
| **Real-time preview** | See the extruded shape update in real-time as you move the mouse |
| **Interactive distance control** | Drag the mouse to adjust extrusion distance, or use the distance input dialog (Tab key) for precise control |
@@ -594,6 +595,23 @@ The polar duplicate tool allows you to create multiple copies of selected shapes
- Creating symmetric designs with rotational symmetry
- Duplicating features around a center point
+#### Shape Cross-section Tool
+
+
+
+Use the cross-section tool to inspect where a plane cuts one or more selected solids. The result is a temporary cyan wire preview. A translucent yellow rectangle outlines the cutting plane, with an arrow showing its positive normal direction. These annotations do not modify the solids or create a sketch.
+
+1. Select one or more solids.
+2. Click **Shape cross-section** in the toolbar. If solids were already selected, the preview updates immediately.
+3. In **Options**, choose **Local XY**, **Local XZ**, or **Local YZ**. Use **Invert normal** to flip the yellow arrow (and the positive-offset direction); the cut stays in place. **Hide back side** is on by default and preview-clips the selected solids so geometry on the negative-normal side of the plane is not drawn (opposite the yellow arrow); turn it off to show the full solids.
+4. Drag **Offset** (or Ctrl+click to type) along that plane's local normal. The slider range follows the selected solids' bounding box in the current project unit. The yellow plane updates immediately while you drag; cyan section wires catch up asynchronously so the control stays responsive.
+5. Click **Clip** to keep the positive-normal half of each selected solid as a new shape and delete the originals (undoable). Leave the tool (or clear the selection) to remove the preview and any hide-back display clip.
+6. Changing the selection or **Section plane** (or dragging **Offset**) updates the preview automatically. If nothing is selected, Options shows a bold prompt to select one or more shapes.
+
+Each solid has a local frame used for orientation. New solids start with a world-aligned frame at the center of their bounding box; moving, rotating, or scaling a solid updates that frame. With multiple selected solids, all of them share one cutting plane: orientation from the first selected solid's local axes, and origin at the selection bounding-box center. Offset moves that shared plane along its normal.
+
+If any selected item is not a solid, or Open CASCADE cannot compute a section for a solid, that solid is skipped. If the plane misses some solids but still intersects others, the preview continues for the solids it cuts and the status notes how many were missed. Only when the plane misses the entire selection is the preview cleared. Section edges may be lines, circles, ellipses, B-splines, or other OCCT curves. The status message reports the curve counts.
+
### Other Feature Operations
- Create chamfers (C)
diff --git a/src/doc/gui.md b/src/doc/gui.md
index ee4cffe..7dc5bc7 100644
--- a/src/doc/gui.md
+++ b/src/doc/gui.md
@@ -124,24 +124,24 @@ Overlay popups (`FloatEdit`, `AngleEdit`, `MessageStatus`, modals) keep `NoSaved
### Keyboard (`GUI::on_key` in `gui_mode.cpp`)
-| 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` |
+| 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` |
| Ctrl+N/O/S | | `new_project_` / `open_file_dialog_` / `save_file_dialog_` (save failures: `show_error_dialog` / `error_modal_dialog_`) |
-| Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y | | `Occt_view::undo` / `redo` |
-| `1`-`9` / numpad `1`-`9` | `Mode::Normal` only | `set_shp_selection_mode` (TopAbs enum index) |
-| Esc | | `cancel_underlay_calib_`, `Occt_view::cancel`, hide dist/angle edit |
-| Tab | | `Occt_view::dimension_input` |
-| Shift+Tab | | `Occt_view::angle_input` |
-| Enter | | hide edits, `Occt_view::on_enter` |
-| D | | `Mode::Sketch_dim_anno` |
-| Shift+D / Delete / Backspace | | `Occt_view::delete_selected` |
-| G / R / E / S / C / F | | Move / Rotate / Extrude / Scale / Chamfer / Fillet modes |
-| Move-mode keys | `Mode::Move` | `on_key_move_mode_` (axis constraints X/Y/Z) |
-| Rotate-mode keys | `Mode::Rotate` | `on_key_rotate_mode_` (axis pick, Tab angle) |
+| Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y | | `Occt_view::undo` / `redo` |
+| `1`-`9` / numpad `1`-`9` | `Mode::Normal` only | `set_shp_selection_mode` (TopAbs enum index) |
+| Esc | | `cancel_underlay_calib_`, `Occt_view::cancel`, hide dist/angle edit |
+| Tab | | `Occt_view::dimension_input` |
+| Shift+Tab | | `Occt_view::angle_input` |
+| Enter | | hide edits, `Occt_view::on_enter` |
+| D | | `Mode::Sketch_dim_anno` |
+| Shift+D / Delete / Backspace | | `Occt_view::delete_selected` |
+| G / R / E / S / C / F | | Move / Rotate / Extrude / Scale / Chamfer / Fillet modes |
+| Move-mode keys | `Mode::Move` | `on_key_move_mode_` (axis constraints X/Y/Z) |
+| Rotate-mode keys | `Mode::Rotate` | `on_key_rotate_mode_` (axis pick, Tab angle) |
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`.
@@ -186,6 +186,7 @@ Tests use `sketch_left_click` to simulate sketch LMB without ImGui mouse positio
| `Move` / `Rotate` / `Scale` | `options_*_mode_` (constraints, axis, material) |
| `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, bbox-ranged offset, Clip (replace) |
| `Sketch_inspection_mode` | `options_sketch_common_` |
| Each sketch tool mode | Matching `options_sketch_*_mode_` |
| `Sketch_operation_axis` | Mirror / Revolve / Clear axis |
@@ -195,17 +196,17 @@ Shared sketch controls (snap, midpoint nodes, place-from-center) live in `option
## ImGui frame order (`render_gui`)
-| Order | Function | Purpose |
-| ----- | -------------------------------------------- | ------------------------------------- |
-| 1 | `flush_view_events` | Sync camera before UI uses projection |
-| 2 | `menu_bar_`, `toolbar_` | File / View / mode tools |
-| 3 | `dist_edit_`, `angle_edit_` | Floating numeric entry |
-| 4 | `sketch_list_`, `sketch_properties_dialog_` | Sketch List + underlay/properties |
-| 5 | `shape_list_`, `shape_info_dialog_`, `file_inspector_dialog_` | Shape List + info + Import dialog |
-| 6 | `options_` | Mode-specific Options pane |
-| 7 | `message_status_window_`, `about_dialog_` | Status + About |
-| 8 | `add_*_dialog_` | Primitive / sketch creation popups |
-| 9 | `log_window_`, consoles, `settings_`, `dbg_` | Log, Lua/Python, Settings |
+| Order | Function | Purpose |
+| ----- | ------------------------------------------------------------- | ------------------------------------- |
+| 1 | `flush_view_events` | Sync camera before UI uses projection |
+| 2 | `menu_bar_`, `toolbar_` | File / View / mode tools |
+| 3 | `dist_edit_`, `angle_edit_` | Floating numeric entry |
+| 4 | `sketch_list_`, `sketch_properties_dialog_` | Sketch List + underlay/properties |
+| 5 | `shape_list_`, `shape_info_dialog_`, `file_inspector_dialog_` | Shape List + info + Import dialog |
+| 6 | `options_` | Mode-specific Options pane |
+| 7 | `message_status_window_`, `about_dialog_` | Status + About |
+| 8 | `add_*_dialog_` | Primitive / sketch creation popups |
+| 9 | `log_window_`, consoles, `settings_`, `dbg_` | Log, Lua/Python, Settings |
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).
@@ -234,6 +235,8 @@ Toolbar buttons hold `std::variant`. `Command` (`Shape_cut`, `Sha
Mode buttons call `set_mode`. Active state tracks `m_mode`.
+The cross-section toolbar button enters `Mode::Shape_cross_section`. Entering the mode snapshots any selected solids first (selection-mode and sketch-faint redisplay Erase AIS selection), restores them after that sync, and calls `Shp_cross_section::preview` (blocking) with the snapshot. While the mode is active, Options updates the yellow plane annotation immediately on plane/offset/hide-back changes (`request_preview`), then `poll`s a background section job (desktop `std::async`; WASM chunks one solid per frame). At most one running job plus one pending (latest only); moving the slider cancels/coalesces work so the UI stays responsive. **Hide back side** attaches a temporary per-shape `Graphic3d_ClipPlane` for display only. **Clip** runs a half-space `BRepAlgoAPI_Common`, deletes the input solids, and adds clipped replacements (`Shape_replace_delta`). Temporary AIS and jobs are cleared when the mode is left, when the selection becomes empty, after a successful **Clip**, or on `clear()`.
+
## Typical developer usage
### Drive sketch input from tests
@@ -276,4 +279,4 @@ Occt_view* view = gui.get_view();
| [`src/doc/shape.md`](shape.md) | Shape operations invoked from toolbar, Options, mouse |
| [`scr_lua_console.cpp`](../scr_lua_console.cpp) / [`scr_python_console.cpp`](../scr_python_console.cpp) | Script consoles embedded in `render_gui` |
| [`utl_settings.cpp`](../utl_settings.cpp) | User settings file path and I/O helpers |
-| [`utl_cad_file_info.h`](../utl_cad_file_info.h) | CAD/mesh file metadata for **File -> Import** |
+| [`utl_cad_file_info.h`](../utl_cad_file_info.h) | CAD/mesh file metadata for **File -> Import** |
diff --git a/src/doc/shape.md b/src/doc/shape.md
index 2ccbd47..957eb10 100644
--- a/src/doc/shape.md
+++ b/src/doc/shape.md
@@ -23,6 +23,7 @@ Typical uses:
- Extrude a sketch face into a solid; revolve sketch geometry (revolve lives in `skt_operations.cpp`, returns `Shp_rslt`).
- Boolean fuse/cut/common on selected shapes.
- Interactive move, rotate, and scale with preview transforms.
+- Preview cross-sections on a shape-local XY, XZ, or YZ plane (optional hide-back preview; **Clip** commits a half-space cut).
- Fillet/chamfer by shape, face, wire, or edge pick mode.
- Polar duplicate selected shapes about an arm on the current sketch plane.
@@ -31,6 +32,7 @@ Typical uses:
### Lifetime and ownership
- Shapes are stored in `Occt_view::m_shps` (`std::list`). Access via `get_shapes()` or internal `add_shp_()`.
+- Each solid stores a `gp_Ax3` local frame. New geometry defaults to a world-aligned frame at its bounding-box center. Baked move/rotate/scale transforms update the frame; project JSON and shape undo records preserve it.
- `Shp_ptr` is `opencascade::handle`. New shapes are allocated with `new Shp(ctx(), topo_shape)` then registered through `Occt_view::add_shp_()`. Groups use `Shp::create_group` (empty compound, never displayed).
- Hierarchy: `parent_id` (0 = root) + `sibling_order`. Organizational groups only (no transform inheritance). Helpers: `shape_children`, `shape_descendant_solids`, `group_shapes`, `ungroup_shape`, `reparent_shape`, `would_reparent_create_cycle`.
- **Current group** (`Occt_view::current_group_id`, 0 = root): Shape List click sets it; empty groups are valid. Primitives / extrude / revolve / PLY / unioned STEP import call `add_shp_(..., use_current_group=true)` so new solids land under that group. Booleans keep `assign_result_parent_`.
@@ -90,6 +92,7 @@ Occt_view
|
+-- std::list m_shps document shapes
+-- Shp_move / Shp_rotate / Shp_scale interactive transforms
+ +-- Shp_cross_section temporary local-plane section preview
+-- Shp_extrude sketch face extrude session
+-- Shp_fuse / Shp_cut / Shp_common booleans
+-- Shp_fillet / Shp_chamfer edge modifiers (replace in place)
@@ -116,7 +119,7 @@ class Shp : public AIS_Shape {
`set_visible` stores the user preference. `Occt_view::sync_sketch_shape_faint_style` applies effective visibility (own flag, ancestor groups, Hide all overlay, sketch faint/hide) via `apply_context_shown` so Hide all does not stomp per-shape flags. `update_display_()` re-binds selection after mode changes.
-`.ezy` `shapes[]` entries include `id`, `name`, `parentId`, `order`, `visible`, and either `isGroup: true` or `material` + `geom`. Undo uses `Shape_rec` (same fields) plus `Shape_tree_delta` for reparent/group/ungroup.
+`.ezy` `shapes[]` entries include `id`, `name`, `parentId`, `order`, `visible`, and either `isGroup: true` or `material` + `geom` + `frame`. Undo uses `Shape_rec` (including the local frame) plus `Shape_tree_delta` for reparent/group/ungroup.
## `Shp_operation_base`
@@ -148,6 +151,7 @@ Protected helpers used by all operation classes:
| `shp_fillet.h` | `Shp_fillet` | `add_fillet(..., Fillet_mode)` -- `BRepFilletAPI_MakeFillet`; modes: Shape, Face, Wire, Edge (`mode.h`). |
| `shp_chamfer.h` | `Shp_chamfer` | `add_chamfer(..., Chamfer_mode)` -- diagonal distance converted to setback (`dist/sqrt(2)`). |
| `shp_polar_dup.h` | `Shp_polar_dup` | Arm on sketch plane; `dup()` copies selection at polar steps; options: rotate copies, combine into one solid. |
+| `shp_cross_section.h` | `Shp_cross_section` | Shared cutting-plane preview: immediate yellow plane AIS; cyan section wires via async job (desktop `std::async` + per-solid pool; WASM one-solid-per-`poll` chunks); running+latest-pending cancel/coalesce; optional hide-back AIS clip; **Clip** half-space-commons and replaces inputs. |
| `shp_info.h` | `namespace shp_info` | `collect(TopoDS_Shape, Display_meta*)` -> labeled lines for Shape info dialog. |
## Input routing (from UI / `Occt_view`)
@@ -163,6 +167,7 @@ Protected helpers used by all operation classes:
| `Mode::Shape_fillet` | -- | `shp_fillet().add_fillet(..., Fillet_mode)` | -- | -- |
| `Mode::Shape_chamfer` | -- | `shp_chamfer().add_chamfer(..., Chamfer_mode)` | -- | -- |
| `Mode::Shape_polar_duplicate` | `shp_polar_dup().move_point` | `shp_polar_dup().add_point` | -- | `shp_polar_dup().reset` on mode change |
+| `Mode::Shape_cross_section` | -- | -- | Auto-preview on enter / selection / Options; Clip replaces solids | Preview cleared; Clip commits |
| Fuse / cut / common (toolbar) | -- | `selected_fuse` / `selected_cut` / `selected_common` (one-shot) | -- | -- |
| Primitives (menu / script) | -- | `Occt_view::add_box`, `add_sphere`, ... | -- | -- |
| Revolve (sketch Options) | -- | `Occt_view::revolve_selected` -> `add_shp_` | -- | -- |
diff --git a/src/doc_delta.cpp b/src/doc_delta.cpp
index 0256df4..0a13314 100644
--- a/src/doc_delta.cpp
+++ b/src/doc_delta.cpp
@@ -19,10 +19,7 @@ void Sketch_struct_delta::add_sketch_(Occt_view& view, const nlohmann::json& ske
view.undo_insert_sketch(sketch_json, make_current);
}
-void Sketch_struct_delta::remove_sketch_by_id_(Occt_view& view, size_t sketch_id) const
-{
- view.undo_remove_sketch(sketch_id);
-}
+void Sketch_struct_delta::remove_sketch_by_id_(Occt_view& view, size_t sketch_id) const { view.undo_remove_sketch(sketch_id); }
void Sketch_struct_delta::apply_forward(Occt_view& view)
{
diff --git a/src/gui.cpp b/src/gui.cpp
index cee6adb..218cec7 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -196,6 +196,7 @@ void GUI::initialize_toolbar_()
{load_texture("res/icons/PartDesign_Chamfer.png"), false, "Chamfer (c)", Mode::Shape_chamfer},
{load_texture("res/icons/PartDesign_Fillet.png"), false, "Fillet (f)", Mode::Shape_fillet},
{load_texture("res/icons/Draft_PolarArray.png"), false, "Shape polar duplicate", Mode::Shape_polar_duplicate},
+ {load_texture("res/icons/Curves_ExtractSubshape.png"), false, "Shape cross-section", Mode::Shape_cross_section},
{load_texture("res/icons/Part_Cut.png"), false, "Shape cut", Command::Shape_cut},
{load_texture("res/icons/Part_Fuse.png"), false, "Shape fuse", Command::Shape_fuse},
{load_texture("res/icons/Part_Common.png"), false, "Shape common", Command::Shape_common},
@@ -1121,9 +1122,8 @@ const std::vector& GUI::occt_material_combo_labels_()
}
void GUI::sketch_list_inspector_(const Sketch::sptr& sketch, int index, Sketch_list_row_ui& row_ui,
- Sketch::sptr& hover_dim_sketch, size_t& hover_dim_index,
- Sketch::sptr& hover_face_sketch, size_t& hover_face_index,
- Sketch::sptr& hover_edge_sketch, size_t& hover_edge_index,
+ Sketch::sptr& hover_dim_sketch, size_t& hover_dim_index, Sketch::sptr& hover_face_sketch,
+ size_t& hover_face_index, Sketch::sptr& hover_edge_sketch, size_t& hover_edge_index,
Sketch::sptr& hover_node_sketch, size_t& hover_node_index)
{
ImGui::Indent();
@@ -1492,10 +1492,9 @@ void GUI::sketch_list_()
sketch_list_hover = sketch;
if (expanded && ui_show_sketch_list_expand())
- sketch_list_inspector_(sketch, index, row_ui, sketch_list_measurement_hover_sketch,
- sketch_list_measurement_hover_index, sketch_list_hover_face_sketch,
- sketch_list_hover_face_index, sketch_list_hover_edge_sketch, sketch_list_hover_edge_index,
- sketch_list_hover_node_sketch, sketch_list_hover_node_index);
+ sketch_list_inspector_(sketch, index, row_ui, sketch_list_measurement_hover_sketch, sketch_list_measurement_hover_index,
+ sketch_list_hover_face_sketch, sketch_list_hover_face_index, sketch_list_hover_edge_sketch,
+ sketch_list_hover_edge_index, sketch_list_hover_node_sketch, sketch_list_hover_node_index);
++index;
}
@@ -2137,8 +2136,8 @@ void GUI::cancel_underlay_calib_()
void GUI::begin_underlay_undo_(Sketch& sk)
{
- m_underlay_undo_before = sk.underlay().to_json(m_view->asset_store());
- m_underlay_undo_sketch_id = sk.get_id();
+ m_underlay_undo_before = sk.underlay().to_json(m_view->asset_store());
+ m_underlay_undo_sketch_id = sk.get_id();
}
void GUI::commit_underlay_undo_(Sketch& sk)
@@ -2572,6 +2571,9 @@ void GUI::shape_list_()
if (is_group || i < 0 || i >= nmat)
return;
+ // OwnColor (e.g. from older wasm SetColor workarounds) overrides material presets.
+ if (shape->HasColor())
+ shape->UnsetColor();
shape->SetMaterial(Graphic3d_MaterialAspect(static_cast(i)));
m_view->refresh_shape_shading_(shape);
m_view->ctx().Redisplay(shape, true);
@@ -2764,15 +2766,15 @@ void GUI::shape_list_()
if (ImGui::BeginTable("##shape_outliner", 4, table_flags, ImVec2(0.f, 0.f)))
{
// Fixed actions on the left; stretch name on the right (tree indent on name only).
- ImGui::TableSetupColumn("vis", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize |
- ImGuiTableColumnFlags_IndentDisable,
- check_col_w);
- ImGui::TableSetupColumn("disp", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize |
- ImGuiTableColumnFlags_IndentDisable,
- check_col_w);
- ImGui::TableSetupColumn("mat", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize |
- ImGuiTableColumnFlags_IndentDisable,
- mat_col_w);
+ ImGui::TableSetupColumn(
+ "vis", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable,
+ check_col_w);
+ ImGui::TableSetupColumn(
+ "disp", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable,
+ check_col_w);
+ ImGui::TableSetupColumn(
+ "mat", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable,
+ mat_col_w);
ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_IndentEnable, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
@@ -2788,15 +2790,14 @@ void GUI::shape_list_()
{
const float min_pad_h = ImGui::GetFrameHeight();
const ImGuiWindow* inner = ImGui::GetCurrentWindow();
- const float visible_remain =
- inner->InnerClipRect.Max.y - ImGui::GetCursorScreenPos().y - ImGui::GetStyle().CellPadding.y;
- const float pad_h = std::max(min_pad_h, visible_remain);
+ const float visible_remain = inner->InnerClipRect.Max.y - ImGui::GetCursorScreenPos().y - ImGui::GetStyle().CellPadding.y;
+ const float pad_h = std::max(min_pad_h, visible_remain);
ImGui::TableNextRow(ImGuiTableRowFlags_None, pad_h);
ImGui::TableSetColumnIndex(0);
const ImGuiPayload* active_payload = ImGui::GetDragDropPayload();
- const bool dragging_shape =
- active_payload != nullptr && active_payload->IsDataType("EZY_SHAPE_ID") && active_payload->DataSize == sizeof(Shape_id);
+ const bool dragging_shape = active_payload != nullptr && active_payload->IsDataType("EZY_SHAPE_ID") &&
+ active_payload->DataSize == sizeof(Shape_id);
if (dragging_shape)
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, ImGui::GetColorU32(ImGuiCol_Header, 0.20f));
@@ -2808,8 +2809,7 @@ void GUI::shape_list_()
bool root_drop_hovered = false;
if (ImGui::BeginDragDropTarget())
{
- const ImGuiPayload* payload =
- ImGui::AcceptDragDropPayload("EZY_SHAPE_ID", ImGuiDragDropFlags_AcceptBeforeDelivery);
+ const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("EZY_SHAPE_ID", ImGuiDragDropFlags_AcceptBeforeDelivery);
if (payload != nullptr)
{
root_drop_hovered = payload->Preview;
@@ -2832,9 +2832,9 @@ void GUI::shape_list_()
const char* hint = "Move to root";
const ImVec2 ts = ImGui::CalcTextSize(hint);
- ImGui::GetWindowDrawList()->AddText(ImVec2(rmin.x + (rmax.x - rmin.x - ts.x) * 0.5f,
- rmin.y + (rmax.y - rmin.y - ts.y) * 0.5f),
- ImGui::GetColorU32(ImGuiCol_TextDisabled), hint);
+ ImGui::GetWindowDrawList()->AddText(
+ ImVec2(rmin.x + (rmax.x - rmin.x - ts.x) * 0.5f, rmin.y + (rmax.y - rmin.y - ts.y) * 0.5f),
+ ImGui::GetColorU32(ImGuiCol_TextDisabled), hint);
}
}
@@ -3471,7 +3471,7 @@ void GUI::apply_shape_list_ui_from_json_(const nlohmann::json& j)
try
{
- const Shape_id id = static_cast(std::stoull(it.key()));
+ const Shape_id id = static_cast(std::stoull(it.key()));
m_shape_list_expanded[id] = it.value().get();
}
catch (...)
@@ -3548,7 +3548,7 @@ void GUI::apply_sketch_list_ui_from_json_(const nlohmann::json& j)
if (!row.is_object() || key.empty())
continue;
- char* end = nullptr;
+ char* end = nullptr;
const unsigned long long parsed = std::strtoull(key.c_str(), &end, 10);
if (!end || *end != '\0')
continue;
@@ -3574,8 +3574,8 @@ std::string GUI::serialized_project_json_() const
std::string project_json = m_view->to_json();
json j = json::parse(project_json);
j["mode"] = static_cast(get_mode());
- j["ui"]["sketchList"] = sketch_list_ui_to_json_();
- j["ui"]["shapeList"] = shape_list_ui_to_json_();
+ j["ui"]["sketchList"] = sketch_list_ui_to_json_();
+ j["ui"]["shapeList"] = shape_list_ui_to_json_();
return j.dump(2);
}
@@ -4057,8 +4057,8 @@ void GUI::save_file_dialog_()
{
auto describe_save_failure = [&](const char* stage) -> std::string
{
- const int err = errno;
- const std::string name = std::filesystem::path(file).filename().string();
+ const int err = errno;
+ const std::string name = std::filesystem::path(file).filename().string();
std::ostringstream msg;
msg << "Could not save \"" << name << "\".\n\n";
msg << "Path:\n" << file << "\n\n";
@@ -4271,8 +4271,7 @@ void GUI::import_file_dialog_async()
var contentsPtr = _malloc(length);
HEAPU8.set(contents, contentsPtr);
// File -> Import opens the Import dialog (metadata, then Import into project).
- Module.ccall('on_inspector_file_selected', null, [ 'string', 'number', 'number' ],
- [ fileName, contentsPtr, length ]);
+ Module.ccall('on_inspector_file_selected', null, [ 'string', 'number', 'number' ], [ fileName, contentsPtr, length ]);
_free(contentsPtr);
};
reader.readAsArrayBuffer(file);
@@ -4322,10 +4321,14 @@ void GUI::save_file_dialog_async(const char* title, const std::string& default_f
try
{
var data = HEAPU8.subarray($0, $0 + $1);
- var blob = new Blob([data], {type : 'application/octet-stream'});
- var url = URL.createObjectURL(blob);
- var a = document.createElement('a');
- a.href = url;
+ var blob = new Blob([data],
+ {
+ type:
+ 'application/octet-stream'
+ });
+ var url = URL.createObjectURL(blob);
+ var a = document.createElement('a');
+ a.href = url;
a.download = UTF8ToString($2);
document.body.appendChild(a);
a.click();
diff --git a/src/gui.h b/src/gui.h
index 48fc544..07a73ee 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -105,8 +105,7 @@ inline constexpr float k_gui_sketch_face_selection_color_default[4] = {0.799043f
/// Dynamic (hover) highlight RGBA for sketch faces (`gui.sketch_face_highlight_color`).
inline constexpr float k_gui_sketch_face_highlight_color_default[4] = {0.822967f, 0.0f, 1.0f, 1.0f};
/// 3D shape selection (AIS SelectionStyle) RGBA (`gui.shape_selection_color`).
-inline constexpr float k_gui_shape_selection_color_default[4] = {
- 0.754312f, 0.072938f, 0.846890f, 1.0f};
+inline constexpr float k_gui_shape_selection_color_default[4] = {0.754312f, 0.072938f, 0.846890f, 1.0f};
/// `gui.sketch_shape_faint_style`: 0 = Off (hide shapes in sketch mode), 1 = Ghost, 2 = Wire.
inline constexpr int k_gui_sketch_shape_faint_style_min = 0;
inline constexpr int k_gui_sketch_shape_faint_style_max = 2;
@@ -285,32 +284,32 @@ class GUI
float sketch_shape_faint_opacity() const { return m_sketch_shape_faint_opacity; }
/// Master on/off for faint shapes in all sketch modes (`gui.sketch_shape_faint_enabled`).
bool sketch_shape_faint_enabled() const { return m_sketch_shape_faint_enabled; }
- bool get_add_mid_pt_line_edges() const { return m_add_mid_pt_line_edges; }
- bool get_add_mid_pt_rect_edges() const { return m_add_mid_pt_rect_edges; }
- bool get_add_mid_pt_slot_edges() const { return m_add_mid_pt_slot_edges; }
- bool get_edge_from_center() const { return m_edge_from_center; }
- bool get_hide_all_shapes() const { return m_hide_all_shapes; }
- void set_hide_all_shapes(bool hide) { m_hide_all_shapes = hide; }
+ bool get_add_mid_pt_line_edges() const { return m_add_mid_pt_line_edges; }
+ bool get_add_mid_pt_rect_edges() const { return m_add_mid_pt_rect_edges; }
+ bool get_add_mid_pt_slot_edges() const { return m_add_mid_pt_slot_edges; }
+ bool get_edge_from_center() const { return m_edge_from_center; }
+ 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 toggle for non-sketch modes (forces ortho in sketch modes); persisted as
/// `gui.inspection_orthographic`.
- bool inspection_orthographic() const { return m_inspection_orthographic; }
- void set_inspection_orthographic(bool v) { m_inspection_orthographic = v; }
+ bool inspection_orthographic() const { return m_inspection_orthographic; }
+ void set_inspection_orthographic(bool v) { m_inspection_orthographic = v; }
/// Default top-view framing width/height in inches (`gui.default_2d_view_width` / `_height`).
double default_2d_view_width() const { return m_default_2d_view_width; }
double default_2d_view_height() const { return m_default_2d_view_height; }
/// Unit applied by File -> New (`gui.default_project_unit`).
Project_unit default_project_unit() const { return m_default_project_unit; }
- bool get_dark_mode() const { return m_dark_mode; }
- ImVec4 get_clear_color() const;
- void set_mode(Mode mode); // gui_mode.cpp
- void set_parent_mode(); // gui_mode.cpp
- void set_dist_edit(float dist, std::function&& callback,
- const std::optional screen_coords = std::nullopt);
- void hide_dist_edit(bool apply = true);
- void set_angle_edit(float angle, std::function&& callback,
- const std::optional screen_coords = std::nullopt);
- void hide_angle_edit(bool apply = true);
- void hide_sketch_origin_set_edit(bool apply = true);
+ bool get_dark_mode() const { return m_dark_mode; }
+ ImVec4 get_clear_color() const;
+ void set_mode(Mode mode); // gui_mode.cpp
+ void set_parent_mode(); // gui_mode.cpp
+ void set_dist_edit(float dist, std::function&& callback,
+ const std::optional screen_coords = std::nullopt);
+ void hide_dist_edit(bool apply = true);
+ void set_angle_edit(float angle, std::function&& callback,
+ const std::optional screen_coords = std::nullopt);
+ void hide_angle_edit(bool apply = true);
+ void hide_sketch_origin_set_edit(bool apply = true);
/// True when dist or angle edit is visible; Tab should be routed to on_key() instead of ImGui.
bool is_dist_or_angle_edit_active() const;
bool is_sketch_origin_set_edit_active() const;
@@ -364,9 +363,9 @@ class GUI
void note_saved_project_filename(const std::string& filename);
#endif
- void on_file(const std::string& file_path, const std::string& file_bytes, bool announce_load = true);
+ void on_file(const std::string& file_path, const std::string& file_bytes, bool announce_load = true);
[[nodiscard]] bool on_import_file(const std::string& file_path, const std::string& file_data, bool union_shapes = false);
- void on_inspector_file(const std::string& file_path, const std::string& file_data);
+ void on_inspector_file(const std::string& file_path, const std::string& file_data);
/// Emscripten `on_sketch_underlay_selected` routes here (must be public for C callback).
void on_sketch_underlay_file(const std::string& file_path, const std::string& file_bytes);
@@ -410,14 +409,14 @@ class GUI
[[nodiscard]] nlohmann::json sketch_list_ui_to_json_() const;
[[nodiscard]] nlohmann::json shape_list_ui_to_json_() const;
void apply_shape_list_ui_from_json_(const nlohmann::json& j);
- void sketch_properties_dialog_();
- void sketch_origin_panel_settings_(const std::shared_ptr& sk);
- void shape_list_();
- void shape_info_dialog_();
- void open_shape_info_(const Shp_ptr& shape);
- void file_inspector_dialog_();
- void open_file_inspector_(const std::string& file_path, const std::string& file_bytes);
- void close_file_inspector_();
+ void sketch_properties_dialog_();
+ void sketch_origin_panel_settings_(const std::shared_ptr& sk);
+ void shape_list_();
+ void shape_info_dialog_();
+ void open_shape_info_(const Shp_ptr& shape);
+ void file_inspector_dialog_();
+ void open_file_inspector_(const std::string& file_path, const std::string& file_bytes);
+ void close_file_inspector_();
// Mode + Options panel (gui_mode.cpp)
void options_();
@@ -429,6 +428,7 @@ class GUI
void options_shape_chamfer_mode_();
void options_shape_fillet_mode_();
void options_shape_polar_duplicate_mode_();
+ void options_shape_cross_section_mode_();
void options_sketch_from_planer_face_mode_();
void options_sketch_operation_axis_mode_();
void options_sketch_face_extrude_mode_();
@@ -612,18 +612,18 @@ class GUI
int m_sketch_shape_faint_style = k_gui_sketch_shape_faint_style_default;
float m_sketch_shape_faint_opacity = k_gui_sketch_shape_faint_opacity_default;
bool m_sketch_shape_faint_enabled = k_gui_sketch_shape_faint_enabled_default;
- bool m_add_mid_pt_line_edges = false;
- bool m_add_mid_pt_rect_edges = true;
- bool m_add_mid_pt_slot_edges = false;
- bool m_edge_from_center = false;
+ bool m_add_mid_pt_line_edges = false;
+ bool m_add_mid_pt_rect_edges = true;
+ bool m_add_mid_pt_slot_edges = false;
+ bool m_edge_from_center = false;
/// Degrees per numpad orbit (8/2/4/6) and Blender-style roll (Shift+NumPad 4/6); persisted in `gui.view_roll_step_deg`.
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;
+ double m_view_zoom_scroll_scale = k_gui_view_zoom_scroll_scale_default;
/// Sketch-plane framing for New Project / default camera (`gui.default_2d_view_width` / `_height`, inches).
- double m_default_2d_view_width = k_gui_default_2d_view_size_default;
- double m_default_2d_view_height = k_gui_default_2d_view_size_default;
- Project_unit m_default_project_unit = Project_unit::Inch;
+ double m_default_2d_view_width = k_gui_default_2d_view_size_default;
+ double m_default_2d_view_height = k_gui_default_2d_view_size_default;
+ Project_unit m_default_project_unit = Project_unit::Inch;
bool m_inspection_orthographic = false;
std::vector m_toolbar_buttons;
@@ -668,52 +668,52 @@ class GUI
std::unordered_map m_sketch_list_ui;
/// Shape List group expand state (project `ui.shapeList.expanded`); missing id defaults to open.
std::unordered_map m_shape_list_expanded;
- float m_sketch_list_scroll_y{0.f};
- bool m_sketch_list_scroll_restore{false};
-
- bool m_show_sketch_list{true};
- bool m_show_shape_list{true};
- bool m_show_options{true};
- bool m_show_settings_dialog{false};
- bool m_open_about_popup{false};
- bool m_about_popup_open{false};
- bool m_shape_info_open{false};
- Shp_ptr m_shape_info_shp;
- std::vector m_shape_info_lines;
- bool m_file_inspector_open{false};
- bool m_file_inspector_union{false};
- std::string m_file_inspector_path;
- std::string m_file_inspector_bytes;
- utl_cad_file_info::Format m_file_inspector_fmt{utl_cad_file_info::Format::Unknown};
+ float m_sketch_list_scroll_y{0.f};
+ bool m_sketch_list_scroll_restore{false};
+
+ bool m_show_sketch_list{true};
+ bool m_show_shape_list{true};
+ bool m_show_options{true};
+ bool m_show_settings_dialog{false};
+ bool m_open_about_popup{false};
+ bool m_about_popup_open{false};
+ bool m_shape_info_open{false};
+ Shp_ptr m_shape_info_shp;
+ std::vector m_shape_info_lines;
+ bool m_file_inspector_open{false};
+ bool m_file_inspector_union{false};
+ std::string m_file_inspector_path;
+ std::string m_file_inspector_bytes;
+ utl_cad_file_info::Format m_file_inspector_fmt{utl_cad_file_info::Format::Unknown};
std::vector m_file_inspector_lines;
- std::string m_about_markdown;
- uint32_t m_about_splash_gl{0};
- glm::ivec2 m_about_splash_size{512, 512};
- bool m_about_assets_loaded{false};
- bool m_open_add_box_popup{false};
- glm::dvec3 m_add_box_origin{0.0, 0.0, 0.0};
- glm::dvec3 m_add_box_size{1.0, 1.0, 1.0};
- bool m_open_add_pyramid_popup{false};
- glm::dvec3 m_add_pyramid_origin{0.0, 0.0, 0.0};
- double m_add_pyramid_side{1};
- bool m_open_add_sphere_popup{false};
- glm::dvec3 m_add_sphere_origin{0.0, 0.0, 0.0};
- double m_add_sphere_radius{1};
- bool m_open_add_cylinder_popup{false};
- glm::dvec3 m_add_cylinder_origin{0.0, 0.0, 0.0};
- double m_add_cylinder_radius{1}, m_add_cylinder_height{1};
- bool m_open_add_cone_popup{false};
- glm::dvec3 m_add_cone_origin{0.0, 0.0, 0.0};
- double m_add_cone_R1{1}, m_add_cone_R2{0}, m_add_cone_height{1};
- bool m_open_add_torus_popup{false};
- glm::dvec3 m_add_torus_origin{0.0, 0.0, 0.0};
- double m_add_torus_R1{1}, m_add_torus_R2{0.5};
- bool m_open_add_sketch_popup{false};
- int m_new_sketch_plane{0}; // 0=XY, 1=XZ, 2=YZ
- double m_new_sketch_offset{};
- bool m_hide_all_shapes{false};
- int m_ui_verbosity{k_gui_ui_verbosity_default};
- bool m_dark_mode{false};
+ std::string m_about_markdown;
+ uint32_t m_about_splash_gl{0};
+ glm::ivec2 m_about_splash_size{512, 512};
+ bool m_about_assets_loaded{false};
+ bool m_open_add_box_popup{false};
+ glm::dvec3 m_add_box_origin{0.0, 0.0, 0.0};
+ glm::dvec3 m_add_box_size{1.0, 1.0, 1.0};
+ bool m_open_add_pyramid_popup{false};
+ glm::dvec3 m_add_pyramid_origin{0.0, 0.0, 0.0};
+ double m_add_pyramid_side{1};
+ bool m_open_add_sphere_popup{false};
+ glm::dvec3 m_add_sphere_origin{0.0, 0.0, 0.0};
+ double m_add_sphere_radius{1};
+ bool m_open_add_cylinder_popup{false};
+ glm::dvec3 m_add_cylinder_origin{0.0, 0.0, 0.0};
+ double m_add_cylinder_radius{1}, m_add_cylinder_height{1};
+ bool m_open_add_cone_popup{false};
+ glm::dvec3 m_add_cone_origin{0.0, 0.0, 0.0};
+ double m_add_cone_R1{1}, m_add_cone_R2{0}, m_add_cone_height{1};
+ bool m_open_add_torus_popup{false};
+ glm::dvec3 m_add_torus_origin{0.0, 0.0, 0.0};
+ double m_add_torus_R1{1}, m_add_torus_R2{0.5};
+ bool m_open_add_sketch_popup{false};
+ int m_new_sketch_plane{0}; // 0=XY, 1=XZ, 2=YZ
+ double m_new_sketch_offset{};
+ bool m_hide_all_shapes{false};
+ int m_ui_verbosity{k_gui_ui_verbosity_default};
+ bool m_dark_mode{false};
#ifndef NDEBUG
bool m_show_dbg{false};
#endif
@@ -774,9 +774,8 @@ class GUI
/// Shape List hover highlight in the OCCT view (0-1, persisted in ezycad_settings.json).
glm::vec4 m_elm_list_hover_color{0.402064f, 0.102557f, 0.474576f, 1.f};
/// AIS SelectionStyle for selected 3D shapes (0-1 RGBA; Settings -> View presentation).
- float m_shape_selection_color[4] = {
- k_gui_shape_selection_color_default[0], k_gui_shape_selection_color_default[1],
- k_gui_shape_selection_color_default[2], k_gui_shape_selection_color_default[3]};
+ float m_shape_selection_color[4] = {k_gui_shape_selection_color_default[0], k_gui_shape_selection_color_default[1],
+ k_gui_shape_selection_color_default[2], k_gui_shape_selection_color_default[3]};
std::unique_ptr m_lua_console;
bool m_show_python_console{false};
diff --git a/src/gui_mode.cpp b/src/gui_mode.cpp
index b3b9497..a49235a 100644
--- a/src/gui_mode.cpp
+++ b/src/gui_mode.cpp
@@ -2,11 +2,13 @@
#include
+#include
#include
#include
#include
#include
#include