From e1e81864c48006222ccbe50fd1c52900071ccdd9 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 18:24:31 -0600 Subject: [PATCH 1/8] WIP --- src/sketch.md | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 src/sketch.md diff --git a/src/sketch.md b/src/sketch.md new file mode 100644 index 0000000..1cd411c --- /dev/null +++ b/src/sketch.md @@ -0,0 +1,223 @@ +# Sketch module + +Developer reference for the 2D sketch subsystem. The public C++ entry point is [`sketch.h`](sketch.h). + +For end-user sketching workflows (tools, hotkeys, snapping), see [`docs/usage-sketch.md`](../docs/usage-sketch.md). + +## Purpose + +`Sketch` is a 2D planar profile embedded in EzyCad's 3D OCCT viewer. Each sketch lives on a fixed `gp_Pln`, owns nodes (2D vertices), edges (line segments and circle arcs), derived closed faces, length dimensions, and optional image underlay. + +The class is a **coordinator**: it holds shared state (plane, viewer context, visibility, operation axis) and delegates geometry, topology, drawing tools, dimensions, and display to focused sub-modules introduced in the sketch refactor (issue #163). + +Typical uses: + +- Interactive creation and editing via sketch tools (line, arc, rectangle, slot, add-node, dimension, operation axis). +- Face extraction for extrusion and revolve into 3D solids. +- Mirror selected edges about an operation axis. +- JSON save/load and undo/redo through stable sketch and node identity. + +## Requirements and invariants + +### Lifetime and ownership + +- A `Sketch` is owned by `Occt_view` (`Sketch_list m_sketches`, current sketch `m_cur_sketch`). Use `Sketch_ptr` (`std::shared_ptr`) when sharing. +- The constructor **`Occt_view& view` must outlive the sketch**. The sketch holds references to `m_view` and `m_ctx` (the OCCT interactive context). +- Each sketch receives a stable **`m_id`** from `Occt_view::allocate_sketch_id()`. Display names (`m_name`) may duplicate; undo deltas and file I/O key off `get_id()`. +- Node indices are **never compacted**. Deleted nodes become tombstones (`Node::deleted`) so undo/redo and JSON round-trips stay stable. + +### Plane and coordinates + +- All sketch geometry is stored as **2D plane coordinates** (`gp_Pnt2d`) in `Sketch_nodes`. Convert to 3D with `Sketch::to_3d_()` or node helpers that take `gp_Pln`. +- Screen input arrives as `ScreenCoords` and is projected onto `m_pln` inside `Sketch_nodes::snap()` and tool handlers. + +### Transient vs committed state + +- **Committed** geometry: `Sketch_edges` (persistent edge list), `Sketch_topo` (face cache), `Sketch_dims` (length dimensions), `Sketch_node_marks` (permanent "+" markers). +- **Transient** draw state: `Sketch_tools` (`m_tmp_edges`, `m_tmp_node_idxs`, rubber-band previews). Cleared by `cancel_elm()` or committed by `finalize_elm()`. +- Do not mix transient edges into the persistent list; tools finalize through `Sketch_edges` and `Sketch_topo`. + +### Operation axis + +- Mirror and revolve require an optional **`m_operation_axis`** edge (two nodes). While the axis is active in mirror/revolve mode, sketch snap and permanent node markers are suppressed (`operation_axis_suppresses_sketch_snap_()`). +- Clear with `clear_operation_axis()` before leaving operation-axis workflows. + +### Global sketch options (static) + +| API | Effect | +| --- | --- | +| `Sketch::set_add_mid_pt_edges(bool)` | When true, linear edge tools create midpoint snap nodes on new segments. Default off in the app; many unit tests enable it in `SetUp`. | +| `Sketch::set_edge_from_center(bool)` | When true, rectangle/circle/slot tools place from center instead of corner. | + +Snap distance, guide mode, and guide colors live on `Sketch_nodes` (static setters). + +## Architecture + +``` +Sketch (coordinator: sketch.cpp, sketch.h) + | + +-- Sketch_nodes vertices, snapping, outside-sketch snap points + +-- Sketch_node_marks permanent "+" markers for user-placed nodes + +-- Sketch_edges persistent edge list; add, split, remove, pick + +-- Sketch_topo planar graph -> closed faces, edge splitting + +-- Sketch_dims length dimensions, typed distance/angle input + +-- Sketch_tools interactive drawing session (tmp state) + +-- Sketch_underlay raster image on the sketch plane + +Supporting (not owned sub-objects): + sketch_edge.* Sketch_edge type, linear/arc predicates + sketch_ais.* Sketch_AIS_edge, Sketch_AIS_node_mark, Sketch_face_shp + sketch_display.cpp visibility, edge styling, list hover, set_current + sketch_operations.cpp operation axis, mirror, revolve + sketch_json.* JSON serialization + sketch_delta.* undo/redo (Sketch_op_recorder, Sketch_delta) +``` + +`Sketch` grants `friend` access to `Sketch_json`, `Sketch_delta`, `Sketch_topo`, `Sketch_edges`, `Sketch_dims`, `Sketch_node_marks`, and `Sketch_tools` for coordinated mutations without exposing internals publicly. + +## Public API overview + +### Construction + +```cpp +Sketch(const std::string& name, Occt_view& view, const gp_Pln& pln); +Sketch(const std::string& name, Occt_view& view, const gp_Pln& pln, const TopoDS_Wire& outer_wire); +``` + +The wire overload creates a sketch **from a planar face**; `m_originating_face` is displayed and its vertices contribute to snap targets. + +### Input routing (from UI / Occt_view) + +The viewer forwards pointer and keyboard events to the **current** sketch: + +| Method | When | +| --- | --- | +| `add_sketch_pt(screen_coords)` | Click / left-button down for the active sketch tool | +| `sketch_pt_move(screen_coords)` | Mouse move (rubber band, snap guides) | +| `dimension_input(screen_coords)` | Tab: precise length while drawing | +| `angle_input(screen_coords)` | Shift+Tab: angle constraint while drawing | +| `on_enter()` | Enter: confirm typed numeric input | +| `finalize_elm()` | Complete multi-step tool (e.g. right-click finish) | +| `cancel_elm()` | Esc: discard transient state; returns whether an operation was canceled | +| `on_mode()` | Called when `Mode` changes; resets transient state and refreshes axis/mark display | + +`get_mode()` reads `Occt_view::get_mode()`; tool behavior in `Sketch_tools` branches on the active `Mode`. + +### Visibility and display + +| Method | Purpose | +| --- | --- | +| `set_visible` / `is_visible` | Show or hide the whole sketch in the viewer | +| `set_show_faces` / `set_show_edges` / `set_show_dims` | Layer toggles for faces, edges, dimensions | +| `set_edge_style(Full \| Background \| Hidden)` | Current vs background sketch appearance | +| `set_current()` | Make this sketch current in `Occt_view` | +| `refresh_annotations(Sketch_annotation_refresh)` | Rebuild length dimensions and/or permanent node marks after settings changes | +| `append_list_hover_ais(out)` | AIS objects to highlight when the Sketch List row is hovered | + +### Geometry queries and inspector + +| Method | Purpose | +| --- | --- | +| `has_edges`, `edge_count`, `face_count`, `length_dimension_count` | Counts for UI and mode selection after undo | +| `inspector_*_labels()` | Human-readable labels for Sketch List / inspector panels | +| `get_plane()`, `get_nodes()`, `underlay()` | Access plane, nodes, and image underlay | + +### Operations + +| Method | Purpose | +| --- | --- | +| `mirror_selected_edges()` | Mirror selected edges about `m_operation_axis` (requires axis + selection) | +| `revolve_selected(angle)` | Revolve selected edges/faces into a 3D solid | +| `clear_operation_axis` / `has_operation_axis` | Operation-axis lifecycle | +| `remove_edge`, `remove_permanent_node_mark` | Delete committed geometry from selection | +| `toggle_edge_dim_anno`, `try_remove_length_dimension` | Dimension tool and deletion | + +### Dimensions (delegated to `Sketch_dims`) + +Per-dimension visibility, flyout offset, name, and OCCT handle accessors are exposed on `Sketch` for the Sketch List and viewer highlight paths. + +## Typical developer usage + +### Create a sketch and add geometry (tests / scripts) + +```cpp +Occt_view& view = ...; +gp_Pln pln = ...; +auto sketch = std::make_shared("Test", view, pln); +view.set_curr_sketch(sketch); + +// Simulate two clicks for a line (screen coords are plane units in tests) +sketch->add_sketch_pt(ScreenCoords(dvec2(0.0, 0.0))); +sketch->add_sketch_pt(ScreenCoords(dvec2(5.0, 0.0))); +``` + +For closed profiles and face tests, add edges that intersect so `Sketch_topo::update_faces()` splits segments and builds faces. See `tests/sketch_tests.cpp`. + +### Record undoable edits + +Wrap mutations that should appear on the undo stack in `Sketch_op_recorder`: + +```cpp +Sketch_op_recorder rec(view, *sketch); +sketch->add_edge_(pt_a, pt_b, rec); // private; tools/json use this path +rec.commit(); +``` + +Interactive tools create recorders internally on finalize. JSON load and delta apply/revert bypass live recorders. + +### Serialize + +```cpp +nlohmann::json j = Sketch_json::to_json(*sketch, assets); +auto loaded = Sketch_json::from_json(view, j); +``` + +`from_json` restores nodes by index, edges, dimensions, operation axis, and underlay metadata. + +### Read edges without exposing the list type + +```cpp +sketch->m_edges.for_each_linear([](const Sketch_edge_linear& e) { ... }); +sketch->m_edges.for_each_arc([](const Sketch_edge_arc& e) { ... }); +``` + +Prefer these visitors in JSON/delta/topo code over iterating `std::list` directly. + +## Module responsibilities (detail) + +| File | Responsibility | +| --- | --- | +| `sketch.h` / `sketch.cpp` | Coordinator: input routing, edge shape updates, snap aggregation, inspector labels, static options | +| `sketch_edge.h` | `Sketch_edge` struct; `sketch_edge_is_linear` / `sketch_edge_is_arc` | +| `sketch_edges.h` | Persistent `std::list`; add linear/arc edges; split at intersections; pick and selection | +| `sketch_topo.h` | Planar face extraction from edge graph; automatic splitting at interior nodes and arc crossings | +| `sketch_nodes.h` | Node storage, snap, snap guides, outside-sketch snap points | +| `sketch_node_marks.h` | AIS "+" markers for permanent nodes only | +| `sketch_dims.h` | Length dimensions between node pairs; Tab/Shift+Tab input; dimension-tool pick state | +| `sketch_tools.h` | Mode-specific click/move/finalize/cancel for all sketch creation tools | +| `sketch_underlay.h` | Calibrated raster underlay on the sketch plane | +| `sketch_ais.h` | OCCT AIS wrappers tied back to owning `Sketch` | +| `sketch_display.cpp` | Visibility, edge/face styling, `set_current`, list hover | +| `sketch_operations.cpp` | Operation axis, mirror, revolve | +| `sketch_json.h` | `.ezy` / project JSON for sketches | +| `sketch_delta.h` | Undo/redo deltas keyed by `sketch_id` | + +## Edge and face model + +- A **linear edge** has `node_idx_a`, `node_idx_b`, optional `node_idx_mid` (midpoint snap node when the option is on). +- An **arc edge** has start, end, and arc midpoint nodes (`node_idx_arc_pt` for the curve point used when snapping). +- **Faces** are derived, not stored as user entities. `Sketch_topo::update_faces()` rebuilds `Sketch_face_shp` objects after edge changes. Faces drive extrusion/revolve and face selection. +- New linear or arc edges that cross existing edges trigger **automatic splitting** so T-junctions and divided regions produce valid topology (see user doc on automatic splitting). + +## Testing + +- GTest suite: `tests/sketch_tests.cpp`, filter `Sketch_test.*`. +- Build target: `EzyCad_tests` (see [`agents/workflows/local-dev.md`](../agents/workflows/local-dev.md)). +- Tests construct `Occt_view`, create `Sketch` on a plane, and drive geometry through `add_sketch_pt` or private helpers via test fixtures. + +## Related code outside `src/sketch*` + +- `occt_view.h` / `occt_view.cpp` -- sketch list, current sketch, input forwarding, sketch creation from planar faces. +- `mode.h` -- `Mode` enum selects the active sketch tool. +- `gui.cpp` -- toolbar and Sketch List UI wired to `Sketch` methods. +- `shp.h` -- `Shp_rslt` from `revolve_selected`. From 7fa7d850c5d7993eaa78eac2e8670ddb1c223d59 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 18:32:38 -0600 Subject: [PATCH 2/8] WIP --- CMakeLists.txt | 16 ++++++++++++++++ agents.md | 1 + agents/README.md | 1 + agents/conventions/token-lean.md | 11 +++++++++++ src/{ => doc}/sketch.md | 6 +++--- 5 files changed, 32 insertions(+), 3 deletions(-) rename src/{ => doc}/sketch.md (96%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ed22fe..c6cb7d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -820,6 +820,22 @@ if(EXISTS ${CHANGELOG_FILE}) set_source_files_properties(${CHANGELOG_FILE} PROPERTIES HEADER_FILE_ONLY TRUE) endif() +# src/doc: developer markdown for src modules (non-compiled; nested folders under src\doc in the IDE) +file(GLOB_RECURSE SRC_DOC_FILES CONFIGURE_DEPENDS + LIST_DIRECTORIES false + "${CMAKE_SOURCE_DIR}/src/doc/*") +if(SRC_DOC_FILES) + source_group(TREE "${CMAKE_SOURCE_DIR}/src/doc" PREFIX "src\\doc" FILES ${SRC_DOC_FILES}) + set(SRC_DOC_FILES_REL) + foreach(f ${SRC_DOC_FILES}) + file(RELATIVE_PATH f_rel "${CMAKE_SOURCE_DIR}" "${f}") + list(APPEND SRC_DOC_FILES_REL "${f_rel}") + endforeach() + target_sources(${PROJECT_NAME}_lib PRIVATE ${SRC_DOC_FILES_REL}) + target_sources(${PROJECT_NAME} PRIVATE ${SRC_DOC_FILES_REL}) + set_source_files_properties(${SRC_DOC_FILES_REL} PROPERTIES HEADER_FILE_ONLY TRUE) +endif() + # Add .clang-format and .gitignore to EzyCad and EzyCad_lib so they appear in VS and other IDEs (at project root) set(CLANG_FORMAT_FILE ${CMAKE_SOURCE_DIR}/.clang-format) if(EXISTS ${CLANG_FORMAT_FILE}) diff --git a/agents.md b/agents.md index 01aa085..aa28948 100644 --- a/agents.md +++ b/agents.md @@ -12,6 +12,7 @@ Pointer for AI coding assistants. Details live in [agents/README.md](agents/READ ## When needed - UI/settings/docs changes: [agents/conventions/user-docs-sync.md](agents/conventions/user-docs-sync.md) +- Sketch subsystem: [src/doc/sketch.md](src/doc/sketch.md) (read; update when API or architecture changes) - Build/test: [agents/workflows/local-dev.md](agents/workflows/local-dev.md) or root README - Release (maintainers only): [agents/workflows/release.md](agents/workflows/release.md) - Open work context: one file under [agents/drafts/](agents/drafts/) if relevant diff --git a/agents/README.md b/agents/README.md index 8ef5dd9..a3a32cb 100644 --- a/agents/README.md +++ b/agents/README.md @@ -11,6 +11,7 @@ Root markers: [AGENTS.md](../AGENTS.md) / [agents.md](../agents.md). | ASCII / `src/` edits | [conventions/ascii-source.md](conventions/ascii-source.md) | | C++ style (full) | [docs/ezycad_code_style.md](../docs/ezycad_code_style.md) | | User docs when UI changes | [conventions/user-docs-sync.md](conventions/user-docs-sync.md) | +| Sketch module (dev doc) | [src/doc/sketch.md](../src/doc/sketch.md) — read when editing sketch code; update if API/architecture changes | | Build / test / wasm | [workflows/local-dev.md](workflows/local-dev.md) | | Release | [workflows/release.md](workflows/release.md) | | Issue/PR drafts | [drafts/](drafts/) — [github-drafts.md](conventions/github-drafts.md) | diff --git a/agents/conventions/token-lean.md b/agents/conventions/token-lean.md index eba3d01..f6e89de 100644 --- a/agents/conventions/token-lean.md +++ b/agents/conventions/token-lean.md @@ -16,6 +16,7 @@ Goal: give assistants **only what they need** for the task at hand. Full style g | --- | --- | | Build / test | [workflows/local-dev.md](../workflows/local-dev.md) — or root [README.md](../../README.md#building-instructions) | | User-visible UI/settings | [user-docs-sync.md](user-docs-sync.md) + target `docs/usage-*.md` only | +| Sketch subsystem (`src/sketch*`, `tests/sketch_tests.cpp`, sketch behavior in `occt_view` / `gui`) | [src/doc/sketch.md](../../src/doc/sketch.md) — read before editing; update when API, invariants, module layout, or workflows change | | Docs build | [workflows/docs-build.md](../workflows/docs-build.md) | | Release | [workflows/release.md](../workflows/release.md) | | Specific issue/PR | One file under `drafts/issues/active/` or `drafts/prs/active/` | @@ -28,6 +29,16 @@ Goal: give assistants **only what they need** for the task at hand. Full style g - Prefer **GitHub issue/PR links** over pasting long draft bodies into rules. - Drop scratch files (`*-pr-body.txt`, duplicate templates). +## Developer docs in `src/doc/` + +Module notes live under `src/doc/` (IDE folder `src\doc`; see root `CMakeLists.txt`). They are **not** user guides — do not duplicate `docs/usage-*.md`. + +| Module doc | When | +| --- | --- | +| [src/doc/sketch.md](../../src/doc/sketch.md) | Editing `src/sketch*`, `tests/sketch_tests.cpp`, or sketch-facing behavior in `occt_view` / `gui` / `mode` | + +**On sketch changes:** read `sketch.md` first for context. Update it in the same branch when you change public `Sketch` API, coordinator/sub-module boundaries, invariants (IDs, undo, transient vs committed state), or developer usage patterns. Skip updates for internal-only refactors with no doc impact. + ## Cursor / IDE rules Keep personal rules minimal: point at `AGENTS.md` and `agents/conventions/ascii-source.md`. Avoid copying whole `local-dev.md` or `release.md` into global rules. diff --git a/src/sketch.md b/src/doc/sketch.md similarity index 96% rename from src/sketch.md rename to src/doc/sketch.md index 1cd411c..944c21e 100644 --- a/src/sketch.md +++ b/src/doc/sketch.md @@ -1,8 +1,8 @@ # Sketch module -Developer reference for the 2D sketch subsystem. The public C++ entry point is [`sketch.h`](sketch.h). +Developer reference for the 2D sketch subsystem. The public C++ entry point is [`sketch.h`](../sketch.h). -For end-user sketching workflows (tools, hotkeys, snapping), see [`docs/usage-sketch.md`](../docs/usage-sketch.md). +Maintainers: update this file when sketch API, module boundaries, or invariants change (see [agents/conventions/token-lean.md](../../agents/conventions/token-lean.md#developer-docs-in-srcdoc)). User-facing sketch guides live in [`docs/usage-sketch.md`](../../docs/usage-sketch.md). ## Purpose @@ -212,7 +212,7 @@ Prefer these visitors in JSON/delta/topo code over iterating `std::list Date: Sun, 5 Jul 2026 18:37:51 -0600 Subject: [PATCH 3/8] Refactor, rename files. --- src/gui.cpp | 2 +- src/gui.h | 2 +- src/gui_add.cpp | 2 +- src/gui_mode.cpp | 2 +- src/{occt_glfw_win.cpp => gui_occt_glfw_win.cpp} | 2 +- src/{occt_glfw_win.h => gui_occt_glfw_win.h} | 0 src/{occt_view.cpp => gui_occt_view.cpp} | 2 +- src/{occt_view.h => gui_occt_view.h} | 4 ++-- src/{occt_view.inl => gui_occt_view.inl} | 0 src/gui_settings.cpp | 2 +- src/scr_lua_console.cpp | 2 +- src/scr_python_console.cpp | 2 +- src/shp_chamfer.cpp | 2 +- src/shp_common.cpp | 2 +- src/shp_cut.cpp | 2 +- src/shp_extrude.cpp | 2 +- src/shp_fillet.cpp | 2 +- src/shp_fuse.cpp | 2 +- src/shp_move.cpp | 2 +- src/shp_operation.cpp | 2 +- src/shp_polar_dup.cpp | 2 +- src/shp_rotate.cpp | 2 +- src/shp_scale.cpp | 2 +- src/sketch.cpp | 2 +- src/sketch_delta.cpp | 2 +- src/sketch_dims.cpp | 2 +- src/sketch_display.cpp | 2 +- src/sketch_edges.cpp | 2 +- src/sketch_json.cpp | 2 +- src/sketch_node_marks.cpp | 2 +- src/sketch_nodes.cpp | 2 +- src/sketch_tools.cpp | 2 +- src/sketch_topo.cpp | 2 +- src/sketch_underlay.cpp | 2 +- tests/sketch_tests.cpp | 2 +- 35 files changed, 34 insertions(+), 34 deletions(-) rename src/{occt_glfw_win.cpp => gui_occt_glfw_win.cpp} (99%) rename src/{occt_glfw_win.h => gui_occt_glfw_win.h} (100%) rename src/{occt_view.cpp => gui_occt_view.cpp} (99%) rename src/{occt_view.h => gui_occt_view.h} (99%) rename src/{occt_view.inl => gui_occt_view.inl} (100%) diff --git a/src/gui.cpp b/src/gui.cpp index 8d09104..a7a72d8 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -25,7 +25,7 @@ #include "imgui.h" #include "utl_log.h" #include "scr_lua_console.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "ezy_io.h" #include "scr_python_console.h" #include "shp_info.h" diff --git a/src/gui.h b/src/gui.h index c3cf8e7..8bde731 100644 --- a/src/gui.h +++ b/src/gui.h @@ -21,7 +21,7 @@ #include "imgui_markdown.h" #include "utl_log.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "shp_info.h" #include "utl_types.h" diff --git a/src/gui_add.cpp b/src/gui_add.cpp index 9c47b46..91be986 100644 --- a/src/gui_add.cpp +++ b/src/gui_add.cpp @@ -1,6 +1,6 @@ #include "gui.h" #include "utl_geom.h" -#include "occt_view.h" +#include "gui_occt_view.h" void GUI::add_box_dialog_() { diff --git a/src/gui_mode.cpp b/src/gui_mode.cpp index 6b5b3a0..d42c060 100644 --- a/src/gui_mode.cpp +++ b/src/gui_mode.cpp @@ -16,7 +16,7 @@ #include "imgui.h" #include "imgui_internal.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "utl_occt.h" diff --git a/src/occt_glfw_win.cpp b/src/gui_occt_glfw_win.cpp similarity index 99% rename from src/occt_glfw_win.cpp rename to src/gui_occt_glfw_win.cpp index 24f01b5..b37d96b 100644 --- a/src/occt_glfw_win.cpp +++ b/src/gui_occt_glfw_win.cpp @@ -19,7 +19,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE -#include "occt_glfw_win.h" +#include "gui_occt_glfw_win.h" #if defined(__APPLE__) #undef Handle // avoid name collisions in macOS headers diff --git a/src/occt_glfw_win.h b/src/gui_occt_glfw_win.h similarity index 100% rename from src/occt_glfw_win.h rename to src/gui_occt_glfw_win.h diff --git a/src/occt_view.cpp b/src/gui_occt_view.cpp similarity index 99% rename from src/occt_view.cpp rename to src/gui_occt_view.cpp index 17a8572..bb0cc15 100644 --- a/src/occt_view.cpp +++ b/src/gui_occt_view.cpp @@ -1,4 +1,4 @@ -#include "occt_view.h" +#include "gui_occt_view.h" #include #include diff --git a/src/occt_view.h b/src/gui_occt_view.h similarity index 99% rename from src/occt_view.h rename to src/gui_occt_view.h index 63b59b7..f9fca4c 100644 --- a/src/occt_view.h +++ b/src/gui_occt_view.h @@ -14,7 +14,7 @@ #include #include -#include "occt_glfw_win.h" +#include "gui_occt_glfw_win.h" #include "shp_chamfer.h" #include "shp_common.h" #include "shp_cut.h" @@ -410,4 +410,4 @@ void show(AIS_InteractiveContext& ctx, Shp_ptr_t& shp, const T& obj, bool redraw template void show(AIS_InteractiveContext& ctx, Sketch& owner, Shp_ptr_t& shp, const T& obj, bool redraw = true); -#include "occt_view.inl" \ No newline at end of file +#include "gui_occt_view.inl" diff --git a/src/occt_view.inl b/src/gui_occt_view.inl similarity index 100% rename from src/occt_view.inl rename to src/gui_occt_view.inl diff --git a/src/gui_settings.cpp b/src/gui_settings.cpp index 004f896..a1129c4 100644 --- a/src/gui_settings.cpp +++ b/src/gui_settings.cpp @@ -8,7 +8,7 @@ #include "utl_dbg.h" #include "gui.h" #include "imgui.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl_settings.h" #include "sketch.h" #include "sketch_nodes.h" diff --git a/src/scr_lua_console.cpp b/src/scr_lua_console.cpp index 86d0e2e..51db2a0 100644 --- a/src/scr_lua_console.cpp +++ b/src/scr_lua_console.cpp @@ -3,7 +3,7 @@ #include "gui.h" #include "imgui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "shp.h" #include "sketch.h" diff --git a/src/scr_python_console.cpp b/src/scr_python_console.cpp index 864c1be..6a3dc79 100644 --- a/src/scr_python_console.cpp +++ b/src/scr_python_console.cpp @@ -10,7 +10,7 @@ #include "gui.h" #include "imgui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "shp.h" #include "sketch.h" diff --git a/src/shp_chamfer.cpp b/src/shp_chamfer.cpp index e5e76d3..5fb0f71 100644 --- a/src/shp_chamfer.cpp +++ b/src/shp_chamfer.cpp @@ -8,7 +8,7 @@ #include #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" Shp_chamfer::Shp_chamfer(Occt_view& view) : Shp_operation_base(view) diff --git a/src/shp_common.cpp b/src/shp_common.cpp index 8d38b81..b8c2d35 100644 --- a/src/shp_common.cpp +++ b/src/shp_common.cpp @@ -2,7 +2,7 @@ #include -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" Shp_common::Shp_common(Occt_view& view) diff --git a/src/shp_cut.cpp b/src/shp_cut.cpp index cbf633a..ece1cae 100644 --- a/src/shp_cut.cpp +++ b/src/shp_cut.cpp @@ -3,7 +3,7 @@ #include #include -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" Shp_cut::Shp_cut(Occt_view& view) diff --git a/src/shp_extrude.cpp b/src/shp_extrude.cpp index 36b6449..2cb6fc0 100644 --- a/src/shp_extrude.cpp +++ b/src/shp_extrude.cpp @@ -8,7 +8,7 @@ #include "utl_dbg.h" #include "utl_geom.h" #include "gui.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "utl.h" diff --git a/src/shp_fillet.cpp b/src/shp_fillet.cpp index 76ea06f..63bbfbc 100644 --- a/src/shp_fillet.cpp +++ b/src/shp_fillet.cpp @@ -7,7 +7,7 @@ #include #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" Shp_fillet::Shp_fillet(Occt_view& view) : Shp_operation_base(view) diff --git a/src/shp_fuse.cpp b/src/shp_fuse.cpp index 1c320c6..08efa69 100644 --- a/src/shp_fuse.cpp +++ b/src/shp_fuse.cpp @@ -2,7 +2,7 @@ #include -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" Shp_fuse::Shp_fuse(Occt_view& view) diff --git a/src/shp_move.cpp b/src/shp_move.cpp index fe247c2..3b8d963 100644 --- a/src/shp_move.cpp +++ b/src/shp_move.cpp @@ -2,7 +2,7 @@ #include "utl_geom.h" #include "gui.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" Shp_move::Shp_move(Occt_view& view) diff --git a/src/shp_operation.cpp b/src/shp_operation.cpp index a119c67..4f6e4c9 100644 --- a/src/shp_operation.cpp +++ b/src/shp_operation.cpp @@ -2,7 +2,7 @@ #include -#include "occt_view.h" +#include "gui_occt_view.h" Shp_operation_base::Shp_operation_base(Occt_view& view) : m_view(view) diff --git a/src/shp_polar_dup.cpp b/src/shp_polar_dup.cpp index 55c847b..125fc81 100644 --- a/src/shp_polar_dup.cpp +++ b/src/shp_polar_dup.cpp @@ -10,7 +10,7 @@ #include "utl_geom.h" #include "gui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_nodes.h" diff --git a/src/shp_rotate.cpp b/src/shp_rotate.cpp index d131e88..f6cb634 100644 --- a/src/shp_rotate.cpp +++ b/src/shp_rotate.cpp @@ -7,7 +7,7 @@ #include "utl_geom.h" #include "gui.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" Shp_rotate::Shp_rotate(Occt_view& view) diff --git a/src/shp_scale.cpp b/src/shp_scale.cpp index 9c3ecf7..7a42108 100644 --- a/src/shp_scale.cpp +++ b/src/shp_scale.cpp @@ -3,7 +3,7 @@ #include "utl_geom.h" #include "gui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" Shp_scale::Shp_scale(Occt_view& view) diff --git a/src/sketch.cpp b/src/sketch.cpp index 32aa7a9..c0d4625 100644 --- a/src/sketch.cpp +++ b/src/sketch.cpp @@ -13,7 +13,7 @@ #include "utl_geom.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch_delta.h" #include "utl.h" diff --git a/src/sketch_delta.cpp b/src/sketch_delta.cpp index baff87d..4352f9c 100644 --- a/src/sketch_delta.cpp +++ b/src/sketch_delta.cpp @@ -8,7 +8,7 @@ #include "utl_dbg.h" #include "utl_geom.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_nodes.h" #include "sketch_edge.h" diff --git a/src/sketch_dims.cpp b/src/sketch_dims.cpp index 48f43a4..84999e3 100644 --- a/src/sketch_dims.cpp +++ b/src/sketch_dims.cpp @@ -10,7 +10,7 @@ #include "gui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_delta.h" #include "sketch_edge.h" diff --git a/src/sketch_display.cpp b/src/sketch_display.cpp index 545572b..5028482 100644 --- a/src/sketch_display.cpp +++ b/src/sketch_display.cpp @@ -4,7 +4,7 @@ #include #include -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl.h" void Sketch::update_edge_style_(AIS_Shape_ptr& shp) diff --git a/src/sketch_edges.cpp b/src/sketch_edges.cpp index bc8e48a..7395ef2 100644 --- a/src/sketch_edges.cpp +++ b/src/sketch_edges.cpp @@ -9,7 +9,7 @@ #include #include -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_delta.h" #include "sketch_edge.h" diff --git a/src/sketch_json.cpp b/src/sketch_json.cpp index 938f852..8a22088 100644 --- a/src/sketch_json.cpp +++ b/src/sketch_json.cpp @@ -18,7 +18,7 @@ #include "sketch_nodes.h" #include "sketch_underlay.h" #include "ezy_asset_store.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "utl_json.h" using json = nlohmann::json; diff --git a/src/sketch_node_marks.cpp b/src/sketch_node_marks.cpp index dcde5d9..b75b2c8 100644 --- a/src/sketch_node_marks.cpp +++ b/src/sketch_node_marks.cpp @@ -5,7 +5,7 @@ #include "gui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_nodes.h" #include "sketch_topo.h" diff --git a/src/sketch_nodes.cpp b/src/sketch_nodes.cpp index d18612c..938ee69 100644 --- a/src/sketch_nodes.cpp +++ b/src/sketch_nodes.cpp @@ -13,7 +13,7 @@ #include "utl_dbg.h" #include "utl_geom.h" #include "imgui.h" -#include "occt_view.h" +#include "gui_occt_view.h" namespace { diff --git a/src/sketch_tools.cpp b/src/sketch_tools.cpp index a5a42b3..c3b128d 100644 --- a/src/sketch_tools.cpp +++ b/src/sketch_tools.cpp @@ -12,7 +12,7 @@ #include "gui.h" #include "mode.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_delta.h" #include "sketch_edge.h" diff --git a/src/sketch_topo.cpp b/src/sketch_topo.cpp index bbe16ae..5ee8abe 100644 --- a/src/sketch_topo.cpp +++ b/src/sketch_topo.cpp @@ -19,7 +19,7 @@ #include -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_delta.h" #include "sketch_edge.h" diff --git a/src/sketch_underlay.cpp b/src/sketch_underlay.cpp index 5d66eec..5c8fa25 100644 --- a/src/sketch_underlay.cpp +++ b/src/sketch_underlay.cpp @@ -24,7 +24,7 @@ #include "utl_geom.h" #include "ezy_asset_store.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch_nodes.h" #include "utl.h" diff --git a/tests/sketch_tests.cpp b/tests/sketch_tests.cpp index e6c6029..15c6191 100644 --- a/tests/sketch_tests.cpp +++ b/tests/sketch_tests.cpp @@ -13,7 +13,7 @@ #include "utl_geom.h" #include "gui.h" -#include "occt_view.h" +#include "gui_occt_view.h" #include "sketch.h" #include "sketch_delta.h" #include "sketch_json.h" From 8d6bf930acd54d7cba2ff739744a98d45eb53708 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 18:46:01 -0600 Subject: [PATCH 4/8] AI doc update. --- agents.md | 2 + agents/README.md | 1 + agents/conventions/token-lean.md | 4 + src/doc/shape.md | 232 +++++++++++++++++++++++++++++++ src/doc/sketch.md | 65 +++++---- 5 files changed, 278 insertions(+), 26 deletions(-) create mode 100644 src/doc/shape.md diff --git a/agents.md b/agents.md index aa28948..998af54 100644 --- a/agents.md +++ b/agents.md @@ -13,6 +13,8 @@ Pointer for AI coding assistants. Details live in [agents/README.md](agents/READ - UI/settings/docs changes: [agents/conventions/user-docs-sync.md](agents/conventions/user-docs-sync.md) - Sketch subsystem: [src/doc/sketch.md](src/doc/sketch.md) (read; update when API or architecture changes) +- Shape module: [src/doc/shape.md](src/doc/shape.md) (read; update when API or operation patterns change) +- Shape module: [src/doc/shape.md](src/doc/shape.md) (read; update when API or operation patterns change) - Build/test: [agents/workflows/local-dev.md](agents/workflows/local-dev.md) or root README - Release (maintainers only): [agents/workflows/release.md](agents/workflows/release.md) - Open work context: one file under [agents/drafts/](agents/drafts/) if relevant diff --git a/agents/README.md b/agents/README.md index a3a32cb..30e128b 100644 --- a/agents/README.md +++ b/agents/README.md @@ -12,6 +12,7 @@ Root markers: [AGENTS.md](../AGENTS.md) / [agents.md](../agents.md). | C++ style (full) | [docs/ezycad_code_style.md](../docs/ezycad_code_style.md) | | User docs when UI changes | [conventions/user-docs-sync.md](conventions/user-docs-sync.md) | | Sketch module (dev doc) | [src/doc/sketch.md](../src/doc/sketch.md) — read when editing sketch code; update if API/architecture changes | +| Shape module (dev doc) | [src/doc/shape.md](../src/doc/shape.md) — read when editing `shp_*` code; update if API/operations change | | Build / test / wasm | [workflows/local-dev.md](workflows/local-dev.md) | | Release | [workflows/release.md](workflows/release.md) | | Issue/PR drafts | [drafts/](drafts/) — [github-drafts.md](conventions/github-drafts.md) | diff --git a/agents/conventions/token-lean.md b/agents/conventions/token-lean.md index f6e89de..a42bb6a 100644 --- a/agents/conventions/token-lean.md +++ b/agents/conventions/token-lean.md @@ -17,6 +17,7 @@ Goal: give assistants **only what they need** for the task at hand. Full style g | Build / test | [workflows/local-dev.md](../workflows/local-dev.md) — or root [README.md](../../README.md#building-instructions) | | User-visible UI/settings | [user-docs-sync.md](user-docs-sync.md) + target `docs/usage-*.md` only | | Sketch subsystem (`src/sketch*`, `tests/sketch_tests.cpp`, sketch behavior in `occt_view` / `gui`) | [src/doc/sketch.md](../../src/doc/sketch.md) — read before editing; update when API, invariants, module layout, or workflows change | +| Shape module (`src/shp*`, shape ops in `occt_view` / `gui`) | [src/doc/shape.md](../../src/doc/shape.md) — read before editing; update when API, operation patterns, or registration/undo change | | Docs build | [workflows/docs-build.md](../workflows/docs-build.md) | | Release | [workflows/release.md](../workflows/release.md) | | Specific issue/PR | One file under `drafts/issues/active/` or `drafts/prs/active/` | @@ -36,9 +37,12 @@ Module notes live under `src/doc/` (IDE folder `src\doc`; see root `CMakeLists.t | Module doc | When | | --- | --- | | [src/doc/sketch.md](../../src/doc/sketch.md) | Editing `src/sketch*`, `tests/sketch_tests.cpp`, or sketch-facing behavior in `occt_view` / `gui` / `mode` | +| [src/doc/shape.md](../../src/doc/shape.md) | Editing `src/shp*`, shape operations in `occt_view` / `gui`, or 3D solid behavior | **On sketch changes:** read `sketch.md` first for context. Update it in the same branch when you change public `Sketch` API, coordinator/sub-module boundaries, invariants (IDs, undo, transient vs committed state), or developer usage patterns. Skip updates for internal-only refactors with no doc impact. +**On shape changes:** read `shape.md` first for context. Update it when you change `Shp` API, `Shp_operation_base` contract, operation class behavior, undo/finalize patterns, or how shapes are registered in `Occt_view`. Skip updates for internal-only refactors with no doc impact. + ## Cursor / IDE rules Keep personal rules minimal: point at `AGENTS.md` and `agents/conventions/ascii-source.md`. Avoid copying whole `local-dev.md` or `release.md` into global rules. diff --git a/src/doc/shape.md b/src/doc/shape.md new file mode 100644 index 0000000..0418870 --- /dev/null +++ b/src/doc/shape.md @@ -0,0 +1,232 @@ +# Shape module + +Developer reference for 3D solid modeling in EzyCad. The public C++ entry point is [`shp.h`](../shp.h). + +Maintainers: update this file when shape API, operation patterns, or invariants change (see [agents/conventions/token-lean.md](../../agents/conventions/token-lean.md#developer-docs-in-srcdoc)). User-facing 3D workflows live in [`docs/usage.md`](../../docs/usage.md) (Shape List, extrude, booleans, transforms). + +## Purpose + +`Shp` wraps a `TopoDS_Shape` as an OCCT `AIS_Shape` in the viewer. The `shp_*` translation units implement **3D modeling operations**: primitive creation, sketch-face extrusion, booleans, fillet/chamfer, move/rotate/scale, polar duplicate, and shape inspection metadata. + +Unlike the sketch subsystem (2D coordinator + sub-modules), shape code is organized as: + +- One core type (`Shp`) in `shp.h` / `shp.cpp` +- Shared operation helpers in `Shp_operation_base` +- One class or namespace per operation file (`Shp_fuse`, `shp_create`, `shp_info`, ...) + +`Occt_view` owns the shape list (`m_shps`), constructs all operation objects, routes UI input, and handles JSON save/load for shapes. + +Typical uses: + +- Display and select 3D solids in the interactive context. +- Create primitives (box, sphere, cylinder, ...) and add them to the document. +- Extrude a sketch face into a solid; revolve sketch geometry (revolve lives in `sketch_operations.cpp`, returns `Shp_rslt`). +- Boolean fuse/cut/common on selected shapes. +- Interactive move, rotate, and scale with preview transforms. +- Fillet/chamfer by shape, face, wire, or edge pick mode. +- Polar duplicate selected shapes about an arm on the current sketch plane. + +## Requirements and invariants + +### Lifetime and ownership + +- Shapes are stored in `Occt_view::m_shps` (`std::list`). Access via `get_shapes()` or internal `add_shp_()`. +- `Shp_ptr` is `opencascade::handle`. New shapes are allocated with `new Shp(ctx(), topo_shape)` then registered through `Occt_view::add_shp_()`. +- `Shp_operation_base` is a **`friend` of `Occt_view`** so operations can call `add_shp_()`, read selection, and use pick helpers without exposing those on the public view API. +- **`Occt_view&` must outlive** all `Shp_*` operation objects (they are member subobjects of the view). + +### Adding a shape to the document + +Always go through `Occt_view::add_shp_(Shp_ptr&)` (or a wrapper that calls it): + +| Step | Action | +| --- | --- | +| 1 | Apply the view default material and shading refresh | +| 2 | Set the document-wide shape selection mode (`m_shp_selection_mode`) | +| 3 | Redisplay and append to `m_shps` | + +`add_shp_()` does **not** push undo; callers that mutate the document should call `view().push_undo_snapshot()` first when appropriate. + +### Operation selection cache (`m_shps` on `Shp_operation_base`) + +Each operation object inherits `Shp_operation_base` and uses **`m_shps`** as a lazy cache of shapes involved in the current operation: + +| Helper | Requirement | +| --- | --- | +| `ensure_operation_shps_()` | One or more selected `Shp` objects | +| `ensure_operation_multi_shps_()` | Two or more selected shapes (fuse, cut, common) | + +On first call, `m_shps` is filled from `get_selected_shps_()` (viewer selection filtered to `Shp`). Cleared on `reset()` / cancel paths in interactive tools. + +Do not confuse this vector with `Occt_view::m_shps` (the document list). + +### Transform preview vs bake + +| Phase | Move / rotate / scale | Booleans / fillet / chamfer | +| --- | --- | --- | +| **Preview** | `SetLocalTransformation()`; `redisplay_operation_shps_after_transform_()` | N/A (immediate replace) | +| **Finalize** | `operation_shps_finalize_()` -> `bake_transform_into_geometry()` | New `Shp`; `delete_operation_shps_()`; `add_shp_()` | +| **Cancel** | `operation_shps_cancel_()` -> `ResetTransformation()` | N/A | + +### Sketch-linked geometry + +| Operation | Module | Sketch tie-in | +| --- | --- | --- | +| Face extrude | `Shp_extrude` | Picks `Sketch_face_shp`; uses owning sketch plane | +| Polar duplicate | `Shp_polar_dup` | Rotation axis and arm on `curr_sketch().get_plane()` | +| Revolve | `Sketch::revolve_selected` | Returns `Shp_rslt`; view calls `add_shp_()` | + +### Result type + +`Shp_rslt` is `Result` (`shp.h`). Used when an operation may fail before producing a shape (e.g. revolve). + +## Architecture + +``` +Occt_view + | + +-- std::list m_shps document shapes + +-- Shp_move / Shp_rotate / Shp_scale interactive transforms + +-- Shp_extrude sketch face extrude session + +-- Shp_fuse / Shp_cut / Shp_common booleans + +-- Shp_fillet / Shp_chamfer edge modifiers (replace in place) + +-- Shp_polar_dup polar array duplicate + +shp.h / shp.cpp Shp AIS wrapper (name, visibility, display mode) +shp_operation.h / shp_operation.cpp Shp_operation_base shared helpers +shp_create.* stateless primitive TopoDS builders (namespace shp_create) +shp_info.* shape info dialog lines (namespace shp_info) +``` + +There is no single `Shape` coordinator class; **`Occt_view` is the hub** and exposes accessors such as `shp_move()`, `shp_fuse()`, `add_box()`, etc. + +## Core type: `Shp` + +```cpp +class Shp : public AIS_Shape { + Shp(AIS_InteractiveContext& ctx, const TopoDS_Shape& shp); + // name, display mode (shaded/wire), visibility, selection mode (TopAbs_ShapeEnum) +}; +``` + +`set_visible(false)` erases from context; `true` re-displays with stored mode and selection mode. `update_display_()` re-binds selection after mode changes. + +## `Shp_operation_base` + +Protected helpers used by all operation classes: + +| Method | Purpose | +| --- | --- | +| `get_selected_shps_()` | Current viewer selection as `Shp_ptr` vector | +| `ensure_operation_shps_()` / `ensure_operation_multi_shps_()` | Lazy-fill `m_shps` from selection | +| `delete_operation_shps_()` | Remove operation shapes from viewer and document list | +| `operation_shps_finalize_()` | Bake local transforms into geometry | +| `operation_shps_cancel_()` | Reset local transforms | +| `get_shape_` / `get_face_` / `get_wire_` / `get_edge_` | Pick sub-shapes at screen coords | +| `add_shp_(Shp_ptr&)` | Register new shape in the document | +| `copy_shape_material_from_(dest, src)` | Preserve material after replace-style ops | + +## Operation modules + +| File | Type | Behavior | +| --- | --- | --- | +| `shp_create.h` | `namespace shp_create` | Pure functions: `create_box`, `create_pyramid`, `create_sphere`, `create_cylinder`, `create_cone`, `create_torus` -> `TopoDS_Shape`. Called from `Occt_view::add_*` helpers. | +| `shp_extrude.h` | `Shp_extrude` | Two-click extrude of `Sketch_face_shp`; live preview solid + tmp length dimension; `finalize` / `cancel`; optional both-sides extrude. | +| `shp_fuse.h` | `Shp_fuse` | `selected_fuse()` -- sequential `BRepAlgoAPI_Fuse` on all selected shapes -> one new `Shp`. | +| `shp_cut.h` | `Shp_cut` | `selected_cut()` -- first selected = blank, rest = tools (`BRepAlgoAPI_Cut`). | +| `shp_common.h` | `Shp_common` | `selected_common()` -- sequential `BRepAlgoAPI_Common` (intersection). | +| `shp_move.h` | `Shp_move` | Drag on view plane; axis constraints (`Move_options`); Tab distance entry; finalize bakes translation. | +| `shp_rotate.h` | `Shp_rotate` | Rotate about view axis, global X/Y/Z, or view-to-object; angle Tab entry; optional axis/center AIS guides. | +| `shp_scale.h` | `Shp_scale` | Uniform scale from bbox center vs mouse distance; clamped factor 0.01..100. | +| `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_info.h` | `namespace shp_info` | `collect(TopoDS_Shape, Display_meta*)` -> labeled lines for Shape info dialog. | + +## Input routing (from UI / `Occt_view`) + +`GUI` and `Occt_view` dispatch by `Mode` and toolbar actions: + +| Mode / action | Mouse move (`GUI::on_mouse_pos`) | Left click | Tab / Enter | Esc (`Occt_view::cancel`) | +| --- | --- | --- | --- | --- | +| `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::Sketch_face_extrude` | `sketch_face_extrude(..., true)` | 1st click: pick face; 2nd click (view): `finalize_sketch_extrude_` if active | `dimension_input` / `on_enter` refresh preview | `m_shp_extrude.cancel` | +| `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 | +| 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_` | -- | -- | +| Polar duplicate commit | -- | Options **Dup** button -> `shp_polar_dup().dup()` | -- | -- | + +On mode change, the view cancels in-progress move, rotate, scale, and sketch extrude sessions. + +## Undo + +| Mechanism | When | +| --- | --- | +| `Occt_view::push_undo_snapshot()` | Before booleans, fillet/chamfer, primitive add, polar dup, transform finalize, extrude commit, revolve | +| Transform preview only | No undo until **finalize** (not on each mouse move) | +| `Sketch_delta` (sketch subsystem) | Separate from shape ops; shape list captured in view snapshots | + +## Persistence + +Shape serialization is handled in **`Occt_view`** (JSON `shapes` array in project save/load), not in a dedicated `shp_json` module. Fields include name, display mode, visibility, material, and tessellated/brep payload as implemented in the view I/O path. + +Import/export (STEP, IGES, STL, PLY) also flows through `Occt_view` reader/writer helpers. + +## Typical developer usage + +### Create and register a primitive + +```cpp +Occt_view& view = ...; +view.push_undo_snapshot(); +TopoDS_Shape box = shp_create::create_box(0, 0, 0, 10, 10, 10); +Shp_ptr shp = new Shp(view.ctx(), box); +shp->set_name(view.get_unique_shape_name("Box")); +view.add_shp_(shp); // friend access, or use view.add_box(...) wrapper +``` + +### Implement a replace-style operation + +```cpp +Status My_op::run() { + view().push_undo_snapshot(); + CHK_RET(ensure_operation_multi_shps_()); + // ... BRepAlgoAPI_* ... + Shp_ptr result = new Shp(ctx(), new_topo); + result->set_name("MyOp"); + delete_operation_shps_(); + add_shp_(result); + return Status::ok(); +} +``` + +### Shape info lines + +```cpp +shp_info::Display_meta meta{shp->get_name(), "...", "...", shp->get_visible()}; +auto lines = shp_info::collect(shp->Shape(), &meta); +``` + +## Testing + +| Item | Notes | +| --- | --- | +| Dedicated suite | None (`shp_tests` does not exist) | +| Coverage | GUI/view tests and manual smoke (extrude, fuse, move, fillet) | +| Adding tests | Drive `Occt_view` helpers or operation classes through existing fixtures when possible | + +## Related code outside `src/shp_*` + +| Location | Role | +| --- | --- | +| `occt_view.h` / `gui_occt_view.cpp` | Shape list, `add_shp_`, primitives, I/O, operation member objects | +| `sketch_ais.h` | `Sketch_face_shp` extrusion source | +| `sketch_operations.cpp` | `Sketch::revolve_selected` -> `Shp_rslt` | +| `mode.h` | `Fillet_mode`, `Chamfer_mode`, tool modes | +| `gui.h` / `gui.cpp` | Toolbar, Shape List, fillet/chamfer mode, material UI | +| `utl_geom.h` | Plane projection, bbox center, rotation helpers used by transforms and polar dup | diff --git a/src/doc/sketch.md b/src/doc/sketch.md index 944c21e..cf4327e 100644 --- a/src/doc/sketch.md +++ b/src/doc/sketch.md @@ -33,9 +33,12 @@ Typical uses: ### Transient vs committed state -- **Committed** geometry: `Sketch_edges` (persistent edge list), `Sketch_topo` (face cache), `Sketch_dims` (length dimensions), `Sketch_node_marks` (permanent "+" markers). -- **Transient** draw state: `Sketch_tools` (`m_tmp_edges`, `m_tmp_node_idxs`, rubber-band previews). Cleared by `cancel_elm()` or committed by `finalize_elm()`. -- Do not mix transient edges into the persistent list; tools finalize through `Sketch_edges` and `Sketch_topo`. +| Layer | Owner | Cleared by | +| --- | --- | --- | +| **Committed** edges, faces, dimensions, permanent "+" markers | `Sketch_edges`, `Sketch_topo`, `Sketch_dims`, `Sketch_node_marks` | Explicit remove / undo delta | +| **Transient** tmp edges, tmp nodes, rubber-band previews | `Sketch_tools` | `cancel_elm()` or `finalize_elm()` | + +Do not mix transient edges into the persistent list; tools finalize through `Sketch_edges` and `Sketch_topo`. ### Operation axis @@ -86,20 +89,22 @@ Sketch(const std::string& name, Occt_view& view, const gp_Pln& pln, const TopoDS The wire overload creates a sketch **from a planar face**; `m_originating_face` is displayed and its vertices contribute to snap targets. -### Input routing (from UI / Occt_view) +### Input routing (from UI / `Occt_view`) -The viewer forwards pointer and keyboard events to the **current** sketch: +The viewer and `GUI` forward pointer and keyboard events to the **current** sketch: -| Method | When | -| --- | --- | -| `add_sketch_pt(screen_coords)` | Click / left-button down for the active sketch tool | -| `sketch_pt_move(screen_coords)` | Mouse move (rubber band, snap guides) | -| `dimension_input(screen_coords)` | Tab: precise length while drawing | -| `angle_input(screen_coords)` | Shift+Tab: angle constraint while drawing | -| `on_enter()` | Enter: confirm typed numeric input | -| `finalize_elm()` | Complete multi-step tool (e.g. right-click finish) | -| `cancel_elm()` | Esc: discard transient state; returns whether an operation was canceled | -| `on_mode()` | Called when `Mode` changes; resets transient state and refreshes axis/mark display | +| Event | `Occt_view` / `GUI` | `Sketch` method | +| --- | --- | --- | +| Mouse move (sketch tools) | `GUI::on_mouse_pos` | `sketch_pt_move` | +| Left click (place point) | `GUI::on_left_click_` | `add_sketch_pt` | +| Tab (length) | `Occt_view::dimension_input` | `dimension_input` | +| Shift+Tab (angle) | `Occt_view::angle_input` | `angle_input` | +| Enter (confirm numeric input) | `Occt_view::on_enter` | `on_enter` | +| Esc (cancel in-progress tool) | `Occt_view::cancel` | `cancel_elm` | +| Right click (finish line / multi-line) | `GUI::on_mouse_button` | `finalize_elm` | +| Mode change | (via `GUI::set_mode`) | `on_mode` | + +Face extrude mode (`Mode::Sketch_face_extrude`) routes mouse, Tab, and Enter through `Occt_view::sketch_face_extrude` / `Shp_extrude` instead of the sketch tool paths above. `get_mode()` reads `Occt_view::get_mode()`; tool behavior in `Sketch_tools` branches on the active `Mode`. @@ -204,20 +209,28 @@ Prefer these visitors in JSON/delta/topo code over iterating `std::list Date: Sun, 5 Jul 2026 18:51:43 -0600 Subject: [PATCH 5/8] gui --- agents.md | 2 +- agents/README.md | 1 + agents/conventions/token-lean.md | 4 + src/doc/gui.md | 228 +++++++++++++++++++++++++++++++ src/doc/shape.md | 2 + src/doc/sketch.md | 2 + 6 files changed, 238 insertions(+), 1 deletion(-) create mode 100644 src/doc/gui.md diff --git a/agents.md b/agents.md index 998af54..4555c44 100644 --- a/agents.md +++ b/agents.md @@ -14,7 +14,7 @@ Pointer for AI coding assistants. Details live in [agents/README.md](agents/READ - UI/settings/docs changes: [agents/conventions/user-docs-sync.md](agents/conventions/user-docs-sync.md) - Sketch subsystem: [src/doc/sketch.md](src/doc/sketch.md) (read; update when API or architecture changes) - Shape module: [src/doc/shape.md](src/doc/shape.md) (read; update when API or operation patterns change) -- Shape module: [src/doc/shape.md](src/doc/shape.md) (read; update when API or operation patterns change) +- GUI module: [src/doc/gui.md](src/doc/gui.md) (read; update when input routing, modes, or settings change) - Build/test: [agents/workflows/local-dev.md](agents/workflows/local-dev.md) or root README - Release (maintainers only): [agents/workflows/release.md](agents/workflows/release.md) - Open work context: one file under [agents/drafts/](agents/drafts/) if relevant diff --git a/agents/README.md b/agents/README.md index 30e128b..fa53d04 100644 --- a/agents/README.md +++ b/agents/README.md @@ -13,6 +13,7 @@ Root markers: [AGENTS.md](../AGENTS.md) / [agents.md](../agents.md). | User docs when UI changes | [conventions/user-docs-sync.md](conventions/user-docs-sync.md) | | Sketch module (dev doc) | [src/doc/sketch.md](../src/doc/sketch.md) — read when editing sketch code; update if API/architecture changes | | Shape module (dev doc) | [src/doc/shape.md](../src/doc/shape.md) — read when editing `shp_*` code; update if API/operations change | +| GUI module (dev doc) | [src/doc/gui.md](../src/doc/gui.md) — read when editing `gui_*` / viewer shell; update if routing or settings change | | Build / test / wasm | [workflows/local-dev.md](workflows/local-dev.md) | | Release | [workflows/release.md](workflows/release.md) | | Issue/PR drafts | [drafts/](drafts/) — [github-drafts.md](conventions/github-drafts.md) | diff --git a/agents/conventions/token-lean.md b/agents/conventions/token-lean.md index a42bb6a..548d1ef 100644 --- a/agents/conventions/token-lean.md +++ b/agents/conventions/token-lean.md @@ -18,6 +18,7 @@ Goal: give assistants **only what they need** for the task at hand. Full style g | User-visible UI/settings | [user-docs-sync.md](user-docs-sync.md) + target `docs/usage-*.md` only | | Sketch subsystem (`src/sketch*`, `tests/sketch_tests.cpp`, sketch behavior in `occt_view` / `gui`) | [src/doc/sketch.md](../../src/doc/sketch.md) — read before editing; update when API, invariants, module layout, or workflows change | | Shape module (`src/shp*`, shape ops in `occt_view` / `gui`) | [src/doc/shape.md](../../src/doc/shape.md) — read before editing; update when API, operation patterns, or registration/undo change | +| GUI / viewer shell (`src/gui*`, input routing, settings panes) | [src/doc/gui.md](../../src/doc/gui.md) — read before editing; update when modes, Options, hotkeys, or settings keys change | | Docs build | [workflows/docs-build.md](../workflows/docs-build.md) | | Release | [workflows/release.md](../workflows/release.md) | | Specific issue/PR | One file under `drafts/issues/active/` or `drafts/prs/active/` | @@ -38,11 +39,14 @@ Module notes live under `src/doc/` (IDE folder `src\doc`; see root `CMakeLists.t | --- | --- | | [src/doc/sketch.md](../../src/doc/sketch.md) | Editing `src/sketch*`, `tests/sketch_tests.cpp`, or sketch-facing behavior in `occt_view` / `gui` / `mode` | | [src/doc/shape.md](../../src/doc/shape.md) | Editing `src/shp*`, shape operations in `occt_view` / `gui`, or 3D solid behavior | +| [src/doc/gui.md](../../src/doc/gui.md) | Editing `src/gui*`, `Occt_view` in `gui_occt_view*`, input routing, Options/Settings UI | **On sketch changes:** read `sketch.md` first for context. Update it in the same branch when you change public `Sketch` API, coordinator/sub-module boundaries, invariants (IDs, undo, transient vs committed state), or developer usage patterns. Skip updates for internal-only refactors with no doc impact. **On shape changes:** read `shape.md` first for context. Update it when you change `Shp` API, `Shp_operation_base` contract, operation class behavior, undo/finalize patterns, or how shapes are registered in `Occt_view`. Skip updates for internal-only refactors with no doc impact. +**On GUI changes:** read `gui.md` first for context. Update it when you change input routing, mode/options panels, toolbar commands, settings JSON keys, or pane visibility rules. Sync user docs per `user-docs-sync.md` when behavior is user-visible. + ## Cursor / IDE rules Keep personal rules minimal: point at `AGENTS.md` and `agents/conventions/ascii-source.md`. Avoid copying whole `local-dev.md` or `release.md` into global rules. diff --git a/src/doc/gui.md b/src/doc/gui.md new file mode 100644 index 0000000..c980d62 --- /dev/null +++ b/src/doc/gui.md @@ -0,0 +1,228 @@ +# GUI module + +Developer reference for EzyCad's Dear ImGui shell and input routing. The public C++ entry point is [`gui.h`](../gui.h). + +Maintainers: update this file when GUI input routing, mode/options behavior, settings keys, or pane layout change (see [agents/conventions/token-lean.md](../../agents/conventions/token-lean.md#developer-docs-in-srcdoc)). User-facing UI guides live in [`docs/usage.md`](../../docs/usage.md), [`docs/usage-settings.md`](../../docs/usage-settings.md), and [`docs/usage-sketch.md`](../../docs/usage-sketch.md). + +## Purpose + +`GUI` owns the application window chrome (menus, toolbar, lists, options, settings, consoles) and **routes GLFW input** to the 3D viewer (`Occt_view` in [`gui_occt_view.h`](../gui_occt_view.h)) and to sketch/shape subsystems. + +Typical responsibilities: + +- ImGui frame: menu bar, toolbar, Sketch List, Shape List, Options, Settings, dist/angle popups. +- Mode switching (`Mode` enum in [`mode.h`](../mode.h)) and parent-mode Esc behavior. +- Persisted preferences (`ezycad_settings.json` via [`gui_settings.cpp`](../gui_settings.cpp)). +- Project I/O (`.ezy` load/save, import/export dialogs). +- Contextual help links (`doc_urls` in `gui.h`). + +## Requirements and invariants + +### Lifetime and ownership + +| Object | Owner | Notes | +| --- | --- | --- | +| `GUI` | `main` / Emscripten singleton | Constructs `std::unique_ptr m_view` in constructor | +| `Occt_view` | `GUI::m_view` | Holds sketches, shapes, undo stacks, shape operation subobjects | +| `GLFWwindow*` | Passed to `GUI::init` | Also wrapped by `Occt_glfw_win` inside the view | + +`GUI` and `Occt_view` reference each other (`Occt_view(GUI& gui)`, `m_view->gui()`). Both must outlive the session. + +### Mode changes + +`GUI::set_mode` (in `gui_mode.cpp`): + +| Step | Action | +| --- | --- | +| 1 | `cancel_underlay_calib_()` | +| 2 | Set `m_mode`, call `m_view->on_mode()` | +| 3 | `sync_sketch_add_mid_pt_edges_if_applicable_()` | +| 4 | Update toolbar active state | + +`set_parent_mode()` maps each tool mode back to `Normal` or `Sketch_inspection_mode` (see parent map in `gui_mode.cpp`). + +### UI verbosity + +`gui.ui_verbosity` gates panes and help (Settings slider). Derived tiers on `GUI`: + +| API | Meaning | +| --- | --- | +| `ui_feature_tier()` | `(verbosity + 1) / 2` -- Options, lists, log at tier 1+ | +| `ui_help_tier()` | `verbosity / 2` -- contextual help depth | +| `ui_show_contextual_help()` | `verbosity >= 5` -- `?` buttons and doc links | +| `show_*_effective()` | Pane flag AND feature tier | + +Constants and ranges live in `gui.h` (`k_gui_ui_verbosity_*`, dimension defaults, view roll/zoom ranges). + +### Dist / angle edit popups + +Tab and Shift+Tab in the 3D view open numeric entry via `GUI::set_dist_edit` / `set_angle_edit`. While active (`is_dist_or_angle_edit_active()`), keys route to `on_key()` instead of ImGui text fields (`main` checks this). + +## Architecture + +``` +GUI (gui.h / gui.cpp) + | + +-- gui_mode.cpp set_mode, on_key, Options panel per Mode + +-- gui_add.cpp Add menu dialogs (primitives, new sketch) + +-- gui_settings.cpp Settings dialog, load/save ezycad_settings.json + | + +-- Occt_view (gui_occt_view.h / gui_occt_view.cpp / .inl) + | +-- gui_occt_glfw_win.* GLFW Aspect_Window wrapper + | +-- sketch*, shp_* ops, document I/O, undo + | + +-- scr_lua_console / scr_python_console (scripting UI) +``` + +CMake IDE group: `src\gui` (files matching `gui*` or `occt*` prefix). + +## Input routing (GLFW -> `GUI` -> downstream) + +`main` forwards GLFW callbacks to `GUI`. The view receives mouse events first in some paths (`on_mouse_button` calls `m_view->on_mouse_button` before `on_left_click_`). + +### 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` | +| Ctrl+N/O/S | | `new_project_` / `open_file_dialog_` / `save_file_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) | + +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`. + +### Mouse move (`GUI::on_mouse_pos`) + +| `Mode` | Delegate | +| --- | --- | +| `Move` | `shp_move().move_selected` | +| `Rotate` | `shp_rotate().rotate_selected` | +| `Scale` | `shp_scale().scale_selected` | +| `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. + +### Mouse buttons (`GUI::on_mouse_button` + `on_left_click_`) + +| Event | Handler | +| --- | --- | +| LMB (underlay calib active) | `try_underlay_calib_click_` (early return) | +| LMB | `m_view->on_mouse_button` then `on_left_click_` | +| RMB press | `finalize_elm` for line / multi-line sketch modes | +| LMB in `on_left_click_` | Mode-specific: transform finalize, sketch `add_sketch_pt`, fillet/chamfer click, polar dup `add_point`, extrude pick | + +Tests use `sketch_left_click` to simulate sketch LMB without ImGui mouse position. + +### Scroll / resize + +| Callback | Delegate | +| --- | --- | +| `on_mouse_scroll` | `Occt_view::on_mouse_scroll` (Shift = finer zoom) | +| `on_resize` | `Occt_view::on_resize` | + +## Options panel dispatch + +`GUI::options_()` switches on `get_mode()`: + +| `Mode` | Options function | +| --- | --- | +| `Normal` | `options_normal_mode_` (selection filter, orthographic) | +| `Move` / `Rotate` / `Scale` | `options_*_mode_` (constraints, axis, material) | +| `Shape_chamfer` / `Shape_fillet` | mode + radius/distance | +| `Shape_polar_duplicate` | angle, count, rotate/combine, **Dup** button | +| `Sketch_inspection_mode` | `options_sketch_common_` | +| Each sketch tool mode | Matching `options_sketch_*_mode_` | +| `Sketch_operation_axis` | Mirror / Revolve / Clear axis | +| `Sketch_face_extrude` | Both sides, material | + +Shared sketch controls (snap, midpoint nodes, place-from-center) live in `options_sketch_common_` and helpers in `gui_mode.cpp`. + +## 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_` | Shape List + info popup | +| 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 | + +3D redraw: `render_occt()` -> `Occt_view::do_frame()` (separate from ImGui pass). + +## Settings and persistence + +| File | Role | +| --- | --- | +| `gui_settings.cpp` | Settings dialog UI; read/write `ezycad_settings.json` | +| `save_occt_view_settings` | Persists `gui.*`, `occt_view.*`, pane visibility, last project path | +| `load_occt_view_settings_` | Called from `GUI::init` | +| `occt_view_settings_json()` | Scripting API for settings blob | + +User-visible key tables: [`docs/usage-settings.md`](../../docs/usage-settings.md). When adding a Settings control, follow [agents/conventions/user-docs-sync.md](../../agents/conventions/user-docs-sync.md). + +## Toolbar and one-shot commands + +Toolbar buttons hold `std::variant`. `Command` (`Shape_cut`, `Shape_fuse`, `Shape_common`) runs immediately on click via `shp_cut` / `shp_fuse` / `shp_common` (no persistent mode). + +Mode buttons call `set_mode`. Active state tracks `m_mode`. + +## Typical developer usage + +### Drive sketch input from tests + +```cpp +GUI_access::sketch_left_click(gui, ScreenCoords(dvec2(x, y))); +// or GUI::sketch_left_click when GUI is accessible +``` + +### Open dist edit from a tool + +```cpp +gui.set_dist_edit(dist, [view](float v, bool is_final) { + // apply v; if (is_final) finalize +}); +``` + +### Read current mode / view + +```cpp +Mode m = gui.get_mode(); +Occt_view* view = gui.get_view(); +``` + +## Testing + +| Item | Notes | +| --- | --- | +| Sketch tests | `GUI_access` friend in `tests/sketch_tests.cpp` | +| Headless / partial GUI | `sketch_left_click`, message getters on `GUI_access` | +| Full UI | Manual smoke; no dedicated `gui_tests` target | + +## Related code outside `src/gui*` + +| Location | Role | +| --- | --- | +| [`mode.h`](../mode.h) | `Mode`, `Fillet_mode`, `Chamfer_mode`, sketch/shape mode helpers | +| [`main.cpp`](../main.cpp) | GLFW callbacks -> `GUI`; dist/angle edit key guard | +| [`src/doc/sketch.md`](sketch.md) | Sketch methods called from `GUI` / `Occt_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 | diff --git a/src/doc/shape.md b/src/doc/shape.md index 0418870..1cdf1f0 100644 --- a/src/doc/shape.md +++ b/src/doc/shape.md @@ -163,6 +163,8 @@ Protected helpers used by all operation classes: On mode change, the view cancels in-progress move, rotate, scale, and sketch extrude sessions. +Full GLFW -> `GUI` routing before these delegates: [`src/doc/gui.md`](gui.md). + ## Undo | Mechanism | When | diff --git a/src/doc/sketch.md b/src/doc/sketch.md index cf4327e..0151a51 100644 --- a/src/doc/sketch.md +++ b/src/doc/sketch.md @@ -106,6 +106,8 @@ The viewer and `GUI` forward pointer and keyboard events to the **current** sket Face extrude mode (`Mode::Sketch_face_extrude`) routes mouse, Tab, and Enter through `Occt_view::sketch_face_extrude` / `Shp_extrude` instead of the sketch tool paths above. +Full GLFW -> `GUI` -> view routing: [`src/doc/gui.md`](gui.md). + `get_mode()` reads `Occt_view::get_mode()`; tool behavior in `Sketch_tools` branches on the active `Mode`. ### Visibility and display From 500254601b85966601c7de95f714e10a8c6a8a2e Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 19:03:58 -0600 Subject: [PATCH 6/8] scripts and utility doc --- agents.md | 4 + agents/README.md | 2 + agents/conventions/token-lean.md | 8 + src/doc/script.md | 163 ++++++++++++++++ src/doc/utility.md | 181 ++++++++++++++++++ src/gui.cpp | 2 +- src/gui_occt_view.h | 2 +- src/sketch_json.cpp | 2 +- src/sketch_underlay.cpp | 2 +- ...zy_asset_store.cpp => utl_asset_store.cpp} | 2 +- src/{ezy_asset_store.h => utl_asset_store.h} | 0 src/{ezy_io.cpp => utl_io.cpp} | 4 +- src/{ezy_io.h => utl_io.h} | 0 tests/sketch_tests.cpp | 4 +- 14 files changed, 367 insertions(+), 9 deletions(-) create mode 100644 src/doc/script.md create mode 100644 src/doc/utility.md rename src/{ezy_asset_store.cpp => utl_asset_store.cpp} (98%) rename src/{ezy_asset_store.h => utl_asset_store.h} (100%) rename src/{ezy_io.cpp => utl_io.cpp} (99%) rename src/{ezy_io.h => utl_io.h} (100%) diff --git a/agents.md b/agents.md index 4555c44..5310cde 100644 --- a/agents.md +++ b/agents.md @@ -15,6 +15,10 @@ Pointer for AI coding assistants. Details live in [agents/README.md](agents/READ - Sketch subsystem: [src/doc/sketch.md](src/doc/sketch.md) (read; update when API or architecture changes) - Shape module: [src/doc/shape.md](src/doc/shape.md) (read; update when API or operation patterns change) - GUI module: [src/doc/gui.md](src/doc/gui.md) (read; update when input routing, modes, or settings change) +- Script consoles: [src/doc/script.md](src/doc/script.md) (read; update when bindings or console UI change) +- Utilities: [src/doc/utility.md](src/doc/utility.md) (read; update when utl_* contracts or I/O change) +- Script consoles: [src/doc/script.md](src/doc/script.md) (read; update when bindings or console UI change) +- Utilities: [src/doc/utility.md](src/doc/utility.md) (read; update when utl_* contracts or I/O change) - Build/test: [agents/workflows/local-dev.md](agents/workflows/local-dev.md) or root README - Release (maintainers only): [agents/workflows/release.md](agents/workflows/release.md) - Open work context: one file under [agents/drafts/](agents/drafts/) if relevant diff --git a/agents/README.md b/agents/README.md index fa53d04..08968ed 100644 --- a/agents/README.md +++ b/agents/README.md @@ -14,6 +14,8 @@ Root markers: [AGENTS.md](../AGENTS.md) / [agents.md](../agents.md). | Sketch module (dev doc) | [src/doc/sketch.md](../src/doc/sketch.md) — read when editing sketch code; update if API/architecture changes | | Shape module (dev doc) | [src/doc/shape.md](../src/doc/shape.md) — read when editing `shp_*` code; update if API/operations change | | GUI module (dev doc) | [src/doc/gui.md](../src/doc/gui.md) — read when editing `gui_*` / viewer shell; update if routing or settings change | +| Script module (dev doc) | [src/doc/script.md](../src/doc/script.md) — read when editing `scr_*`; update if bindings change | +| Utility module (dev doc) | [src/doc/utility.md](../src/doc/utility.md) — read when editing `utl_*`; update if shared helpers or I/O change | | Build / test / wasm | [workflows/local-dev.md](workflows/local-dev.md) | | Release | [workflows/release.md](workflows/release.md) | | Issue/PR drafts | [drafts/](drafts/) — [github-drafts.md](conventions/github-drafts.md) | diff --git a/agents/conventions/token-lean.md b/agents/conventions/token-lean.md index 548d1ef..1d1a658 100644 --- a/agents/conventions/token-lean.md +++ b/agents/conventions/token-lean.md @@ -19,6 +19,8 @@ Goal: give assistants **only what they need** for the task at hand. Full style g | Sketch subsystem (`src/sketch*`, `tests/sketch_tests.cpp`, sketch behavior in `occt_view` / `gui`) | [src/doc/sketch.md](../../src/doc/sketch.md) — read before editing; update when API, invariants, module layout, or workflows change | | Shape module (`src/shp*`, shape ops in `occt_view` / `gui`) | [src/doc/shape.md](../../src/doc/shape.md) — read before editing; update when API, operation patterns, or registration/undo change | | GUI / viewer shell (`src/gui*`, input routing, settings panes) | [src/doc/gui.md](../../src/doc/gui.md) — read before editing; update when modes, Options, hotkeys, or settings keys change | +| Script consoles (`src/scr*`, bindings) | [src/doc/script.md](../../src/doc/script.md) — read before editing; update when `ezy`/`view` API or console UI changes | +| Utilities (`src/utl*`, results, I/O, geometry) | [src/doc/utility.md](../../src/doc/utility.md) — read before editing; update when shared helper contracts change | | Docs build | [workflows/docs-build.md](../workflows/docs-build.md) | | Release | [workflows/release.md](../workflows/release.md) | | Specific issue/PR | One file under `drafts/issues/active/` or `drafts/prs/active/` | @@ -40,6 +42,8 @@ Module notes live under `src/doc/` (IDE folder `src\doc`; see root `CMakeLists.t | [src/doc/sketch.md](../../src/doc/sketch.md) | Editing `src/sketch*`, `tests/sketch_tests.cpp`, or sketch-facing behavior in `occt_view` / `gui` / `mode` | | [src/doc/shape.md](../../src/doc/shape.md) | Editing `src/shp*`, shape operations in `occt_view` / `gui`, or 3D solid behavior | | [src/doc/gui.md](../../src/doc/gui.md) | Editing `src/gui*`, `Occt_view` in `gui_occt_view*`, input routing, Options/Settings UI | +| [src/doc/script.md](../../src/doc/script.md) | Editing `src/scr*`, Lua/Python bindings, console behavior | +| [src/doc/utility.md](../../src/doc/utility.md) | Editing `src/utl*`, `Status`/`Result`, `.ezy` I/O, geometry/settings helpers | **On sketch changes:** read `sketch.md` first for context. Update it in the same branch when you change public `Sketch` API, coordinator/sub-module boundaries, invariants (IDs, undo, transient vs committed state), or developer usage patterns. Skip updates for internal-only refactors with no doc impact. @@ -47,6 +51,10 @@ Module notes live under `src/doc/` (IDE folder `src\doc`; see root `CMakeLists.t **On GUI changes:** read `gui.md` first for context. Update it when you change input routing, mode/options panels, toolbar commands, settings JSON keys, or pane visibility rules. Sync user docs per `user-docs-sync.md` when behavior is user-visible. +**On script changes:** read `script.md` and sync [`docs/scripting.md`](../../docs/scripting.md) when user-visible bindings change. + +**On utility changes:** read `utility.md` when altering `Status`/`Result`, `.ezy` format, asset store, or widely used geometry/settings helpers. + ## Cursor / IDE rules Keep personal rules minimal: point at `AGENTS.md` and `agents/conventions/ascii-source.md`. Avoid copying whole `local-dev.md` or `release.md` into global rules. diff --git a/src/doc/script.md b/src/doc/script.md new file mode 100644 index 0000000..876cc54 --- /dev/null +++ b/src/doc/script.md @@ -0,0 +1,163 @@ +# Script console module + +Developer reference for embedded Lua and Python consoles. Headers: [`scr_lua_console.h`](../scr_lua_console.h), [`scr_python_console.h`](../scr_python_console.h). + +Maintainers: update this file when script bindings, console UI, or startup script loading change (see [agents/conventions/token-lean.md](../../agents/conventions/token-lean.md#developer-docs-in-srcdoc)). User-facing scripting guide: [`docs/scripting.md`](../../docs/scripting.md). + +## Purpose + +The `scr_*` translation units embed **interactive script consoles** in the ImGui UI. They expose live `ezy.*` and `view.*` APIs to Lua and Python so users can inspect the document, add primitives, tweak the camera, and log messages while EzyCad runs. + +| Class | File | Runtime | +| --- | --- | --- | +| `Lua_console` | `scr_lua_console.cpp` | Lua C API (native + Emscripten) | +| `Python_console` | `scr_python_console.cpp` | pybind11 embed (`EZYCAD_HAVE_PYTHON` only) | + +Both are owned by `GUI` and rendered from `GUI::render_gui()` when feature tier allows (`show_lua_console_effective` / `show_python_console_effective`). + +## Requirements and invariants + +### Lifetime and threading + +| Object | Notes | +| --- | --- | +| `Lua_console` / `Python_console` | Constructed with `GUI*`; must not outlive `GUI` | +| `lua_State* m_L` | Created in ctor, closed in dtor | +| Python runtime | `Ezycad_python_runtime` (pybind11) initialized in `init_python_()` | + +Script callbacks run on the **main UI thread** during console input handling. Do not call OCCT or ImGui from worker threads without existing app synchronization. + +### Script file locations + +| Platform | Lua | Python | +| --- | --- | --- | +| Native | `res/scripts/lua/*.lua` | `res/scripts/python/*.py` | +| Emscripten | `/res/scripts/lua/*.lua` (preloaded) | Not built | + +On startup, **Lua** files in that directory are loaded into tabbed `TextEditor` buffers and **executed once** to define helpers. Python tabs load file contents but are not auto-run on startup (run manually or from input line). + +### Index conventions + +| API | Index base | +| --- | --- | +| Lua `view.get_shape(i)` | **1-based**; returns `nil` out of range | +| Python `view.get_shape(i)` | **0-based**; raises `IndexError` out of range | + +Document this when adding parallel bindings. + +## Architecture + +``` +GUI::render_gui() + +-- lua_console_() -> Lua_console::render + +-- python_console_() -> Python_console::render (if EZYCAD_HAVE_PYTHON) + +Lua_console + +-- lua_State + register_bindings() + +-- m_script_editors (path, filename, TextEditor) + +-- REPL input (m_input_buf) + m_history + +Python_console + +-- pybind11 embedded module ezycad_native + +-- exec bootstrap sets ezy / view on __main__ + +-- m_script_editors + REPL input +``` + +CMake IDE group: `src\scr`. + +## Binding surface + +Implementation lives in the `.cpp` files; [`docs/scripting.md`](../../docs/scripting.md) is the user-facing API reference. Summary for developers: + +### `ezy` table (both runtimes) + +| Method | C++ delegate | +| --- | --- | +| `ezy.log(msg)` | `GUI::log_message` + console history | +| `ezy.msg(text)` | `GUI::show_message` | +| `ezy.get_mode()` | `GUI::get_mode()` -> mode name string | +| `ezy.set_mode(name)` | `GUI::set_mode(mode_from_string(...))` | +| `ezy.save_occt_view_settings()` | `GUI::save_occt_view_settings()` | +| `ezy.occt_view_settings_json()` | `GUI::occt_view_settings_json()` | +| `ezy.help()` / global `help()` | Prints binding summary to console | + +Lua overrides global `print` to call `ezy.log`. Python bootstrap assigns `builtins.print` similarly. + +### `view` table + +| Method | Notes | +| --- | --- | +| `sketch_count()` | `Occt_view::get_sketches().size()` | +| `shape_count()` | `Occt_view::get_shapes().size()` | +| `curr_sketch_name()` | Current sketch display name | +| `add_box(...)` / `add_sphere(...)` | `Occt_view` primitive helpers | +| `get_camera()` | `{ eye, center, up }` tables / dicts | +| `set_camera(ex,ey,ez,cx,cy,cz,ux,uy,uz)` | `Occt_view::set_camera` | +| `get_shape(i)` | Returns `Shp` wrapper | + +**Python only** (current sketch inspection): + +| Method | Returns | +| --- | --- | +| `curr_sketch_node_count()` | Node count | +| `curr_sketch_node(i)` | `(x, y)` plane coords | +| `curr_sketch_dim_count()` | Length dimension count | +| `curr_sketch_dim(i)` | `(lo, hi, visible, offset, name, distance)` | + +### `Shp` object + +| Method | Lua | Python | +| --- | --- | --- | +| `name()` / `set_name(s)` | userdata metatable | `ezycad_native.Shp` methods | +| `visible()` / `set_visible(v)` | same | same | + +Lua stores `Shp_ptr` in userdata with `__gc`; Python wraps `Ezy_shp{ Shp_ptr shp }`. + +## Console UI flow + +| UI region | Behavior | +| --- | --- | +| Output history | `m_history` lines (errors flagged) | +| Input line | `ImGui::InputTextWithHint`; Enter runs `execute()` | +| Command history | Up/down browses prior input lines | +| Script tabs | One tab per file in scripts dir; Save writes buffer to disk | + +`Lua_console::text_edit_callback` / `Python_console::text_edit_callback` hook editor keyboard shortcuts (shared pattern with dist/angle edit). + +## Adding a new binding + +| Step | Lua | Python | +| --- | --- | --- | +| 1 | Add `l_*` C function using `get_gui(L)` | Add `m.def("view_foo", ...)` in `PYBIND11_EMBEDDED_MODULE` | +| 2 | Register in `register_bindings()` on `ezy` or `view` table | Expose on `view` in bootstrap `exec()` string | +| 3 | Update help text in `l_ezy_help` / `ezy_help` | same | +| 4 | Update [`docs/scripting.md`](../../docs/scripting.md) | same | + +Prefer thin wrappers: validate args, call `GUI` / `Occt_view` / `Shp` methods, return simple types. + +## Build flags + +| Flag | Effect | +| --- | --- | +| (default) | Lua console always linked (bundled Lua sources) | +| `EZYCAD_HAVE_PYTHON` | Enables Python console, pybind11 embed, `python3.dll` copy on Windows | + +If Python init fails, `Python_console` shows an error in history and disables execution. + +## Testing + +| Item | Notes | +| --- | --- | +| Automated API tests | Limited; bindings exercised manually and via `res/scripts/*` | +| Regression | Run sample lines from `docs/scripting.md` after binding changes | +| Lua on WASM | Verify `/res/scripts/lua` preload paths | + +## Related code outside `src/scr*` + +| Location | Role | +| --- | --- | +| [`gui.cpp`](../gui.cpp) | Owns consoles, `lua_console_()` / `python_console_()` | +| [`gui.h`](../gui.h) | `get_view()`, settings JSON, mode API used by bindings | +| [`mode.h`](../mode.h) | `mode_from_string`, `c_mode_strs` | +| [`gui_occt_view.h`](../gui_occt_view.h) | Document access from `view.*` | +| [`shp.h`](../shp.h) | Shape wrapper type | diff --git a/src/doc/utility.md b/src/doc/utility.md new file mode 100644 index 0000000..778de7c --- /dev/null +++ b/src/doc/utility.md @@ -0,0 +1,181 @@ +# Utility module (`utl_*`) + +Developer reference for shared C++ helpers under the `utl_` prefix. Umbrella header: [`utl.h`](../utl.h). + +Maintainers: update this file when utility APIs, result types, I/O formats, or settings paths change (see [agents/conventions/token-lean.md](../../agents/conventions/token-lean.md#developer-docs-in-srcdoc)). + +## Purpose + +`utl_*` files hold **cross-cutting utilities** used by sketch, shape, GUI, and scripting code: error/results, geometry, OCCT helpers, project I/O, settings persistence, logging, and shared typedefs. They are not a single class; include the smallest header that matches your dependency. + +Typical uses: + +- Return `Status` / `Result` from operations (`CHK_RET` macro). +- Plane/point/wire helpers for sketch and extrude (`utl_geom`). +- Pack/unpack `.ezy` v3 zip projects (`utl_io` + `Ezy_asset_store`). +- Load/save `ezycad_settings.json` (`settings` namespace in `utl_settings`). +- OCCT handle typedefs and `ScreenCoords` (`utl_types`). + +## Module map + +| File | Responsibility | +| --- | --- | +| [`utl.h`](../utl.h) / [`utl.cpp`](../utl.cpp) | `Status`, `Result`, `CHK_RET`, `clear_all`, textures, image decode, name uniquification | +| [`utl_types.h`](../utl_types.h) | OCCT/AIS handle typedefs, `ScreenCoords`, `Export_format`, `DECL_PTR`, `SafeType` | +| [`utl_geom.h`](../utl_geom.h) / [`.cpp`](../utl_geom.cpp) | 2D/3D geometry, wires, dimensions, Boost polygon tests, plane projection | +| [`utl_geom_boost.inl`](../utl_geom_boost.inl) | `ezy_geom` Boost.Geometry aliases | +| [`utl_occt.h`](../utl_occt.h) / [`.cpp`](../utl_occt.cpp) | `TopAbs` name table, `try_make_solid` | +| [`utl_json.h`](../utl_json.h) / [`.cpp`](../utl_json.cpp) | JSON serializers for `gp_Pnt`, `gp_Pln`, etc. | +| [`utl_io.h`](../utl_io.h) / [`.cpp`](../utl_io.cpp) | `.ezy` zip v3 pack/unpack, format sniff, base64 | +| [`utl_asset_store.h`](../utl_asset_store.h) / [`.cpp`](../utl_asset_store.cpp) | Content-addressed RGBA blobs for sketch underlay assets | +| [`utl_settings.h`](../utl_settings.h) / [`.cpp`](../utl_settings.cpp) | User settings file paths, startup project blob I/O | +| [`utl_ply_io.h`](../utl_ply_io.h) / [`.cpp`](../utl_ply_io.cpp) | PLY import/export for mesh shapes | +| [`utl_log.h`](../utl_log.h) / [`.cpp`](../utl_log.cpp) | `Log_strm` redirecting stdout/stderr to `GUI::log_message` | +| [`utl_dbg.h`](../utl_dbg.h) | `EZY_ASSERT`, `DBG_MSG`, debug break macros | + +CMake IDE group: `src\utl` (pattern `^utl(_|\.)`). + +## Core types (`utl.h`) + +### `Status` and `Result` + +| Type | Use | +| --- | --- | +| `Status::ok()` / `user_error(msg)` | Void operations; user-facing message string | +| `Result` | Value or error (`Shp_rslt` = `Result` in `shp.h`) | +| `CHK_RET(expr)` | Early-return if sub-call returns non-ok `Status` | + +### General helpers + +| API | Purpose | +| --- | --- | +| `clear_all(...)` | Reset optional/containers/arithmetic in one call | +| `unique_sequential_name(base, existing)` | `Name`, `Name.001`, ... for sketches/shapes | +| `load_texture(path)` | Toolbar icon loading | +| `decode_image_bytes(bytes)` | stb_image -> RGBA for underlay import | +| `safe_cstr_copy` | ImGui fixed-buffer copies (MSVC-safe) | + +## Geometry (`utl_geom`) + +Large module; grouped by concern: + +| Area | Examples | +| --- | --- | +| Plane / 2D | `to_2d`, `to_3d`, `xy_plane`, `sketch_reference_plane`, `Plane_side` | +| Profile wires | `make_square_wire`, `make_circle_wire`, `make_slot_wire`, `create_wire_box` | +| Sketch dimensions | `Length_dimension_style`, `create_distance_annotation`, `apply_length_dimension_style` | +| Analysis | `to_boost` (polygon), `get_shape_bbox_center`, `plane_from_face`, `side_of_plane` | +| Tests / debug | `to_wkt_string`, `ezy_geom::area`, `is_valid` | + +Includes [`utl_geom_boost.inl`](../utl_geom_boost.inl) for Boost.Geometry polygon types used in tests and face validation. + +## Project I/O (`utl_io` + `Ezy_asset_store`) + +### `.ezy` v3 zip layout + +| Path in archive | Content | +| --- | --- | +| `manifest.json` | Document JSON (sketches, shapes, view state) | +| `assets/.rgba` | Raw RGBA pixels for underlay `"asset"` references | + +| Function | Role | +| --- | --- | +| `is_ezy_zip` / `is_ezy_json` | Sniff loaded bytes | +| `unpack_ezy(bytes)` | -> manifest + asset map | +| `pack_ezy(manifest, store)` | Build zip from manifest + store entries referenced by underlays | +| `ezy_base64_encode` / `decode` | Emscripten startup project in localStorage | + +`Ezy_asset_store` deduplicates RGBA by FNV-1a id (`register_rgba`, `get`, `import_asset`). Owned on `Occt_view` for the session. + +## Settings (`settings` namespace) + +| API | Role | +| --- | --- | +| `load_defaults()` | Ship defaults from `res/ezycad_settings.json` | +| `load_with_defaults()` | User file or defaults + save | +| `save(content)` | Native file or Wasm localStorage | +| `user_config_directory()` | `%APPDATA%/EzyCad`, etc. | +| `user_settings_json_path()` | Persisted GUI/OCCT settings | +| `load/save/clear_user_startup_project()` | Optional startup `.ezy` blob | + +`GUI::load_occt_view_settings_` / `save_occt_view_settings` parse and emit JSON keys documented in [`docs/usage-settings.md`](../../docs/usage-settings.md). + +## OCCT helpers (`utl_occt`) + +| API | Role | +| --- | --- | +| `c_names_TopAbs_ShapeEnum` | String names parallel to OCCT enum (selection filter UI) | +| `try_make_solid(shape)` | Wrap closed shell as solid when possible | + +## JSON geom (`utl_json`) + +Symmetric `to_json` / `from_json_*` for `gp_Pnt2d`, `gp_Pnt`, `gp_Dir`, `gp_Pln` used in sketch/project serialization. + +## PLY (`utl_ply_io`) + +| API | Role | +| --- | --- | +| `import_ply_shape(bytes, out)` | ASCII or binary_little_endian PLY -> `TopoDS_Shape` | +| `export_ply_binary_file(shape, path)` | Mesh export (mesh shape first) | + +## Logging and debug + +| Component | Role | +| --- | --- | +| `Log_strm` | Installed on cout/cerr in `GUI::setup_log_redirection_` | +| `EZY_ASSERT` / `EZY_ASSERT_MSG` | Debug-break assertions (`utl_dbg.h`) | +| `DBG_MSG` | Verbose debug logging macro | + +## Typical developer usage + +### Propagate operation errors + +```cpp +Status Shp_fuse::selected_fuse() { + view().push_undo_snapshot(); + CHK_RET(ensure_operation_multi_shps_()); + // ... + return Status::ok(); +} +``` + +### Pack a project with underlay assets + +```cpp +const std::string manifest = view.to_json(); +std::vector ezy = pack_ezy(manifest, view.asset_store()); +``` + +### Project plane point + +```cpp +gp_Pnt2d uv = to_2d(sketch.get_plane(), pick_3d); +gp_Pnt back = to_3d(sketch.get_plane(), uv); +``` + +## Dependencies and layering + +| Layer | May include | +| --- | --- | +| Low | `utl_dbg`, `utl_types` | +| Mid | `utl`, `utl_json`, `utl_occt`, `utl_io`, `utl_asset_store`, `utl_settings` | +| Heavy | `utl_geom` (OCCT + Boost + glm), `utl_ply_io` | + +Avoid circular includes: `utl_types.h` pulls sketch AIS typedefs via `sketch_ais.h`; geometry code should not include GUI headers. + +## Testing + +| Item | Location | +| --- | --- | +| Geometry / polygon | `tests/sketch_tests.cpp` (`ezy_geom::`, `to_wkt_string`) | +| `.ezy` zip / underlay | `tests/sketch_tests.cpp` (`pack_ezy`, `is_ezy_zip`) | +| Settings | Manual; paths vary by platform | + +## Related code outside `src/utl*` + +| Location | Role | +| --- | --- | +| [`gui.cpp`](../gui.cpp) | `pack_ezy` / `unpack_ezy` for file open/save | +| [`sketch_underlay.cpp`](../sketch_underlay.cpp) | `Ezy_asset_store` for underlay pixels | +| [`sketch_json.cpp`](../sketch_json.cpp) | Manifest JSON + asset references | +| [`gui_settings.cpp`](../gui_settings.cpp) | Parses `gui.*` / `occt_view.*` keys into members | diff --git a/src/gui.cpp b/src/gui.cpp index a7a72d8..a39d2e4 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -26,7 +26,7 @@ #include "utl_log.h" #include "scr_lua_console.h" #include "gui_occt_view.h" -#include "ezy_io.h" +#include "utl_io.h" #include "scr_python_console.h" #include "shp_info.h" #include "sketch.h" diff --git a/src/gui_occt_view.h b/src/gui_occt_view.h index f9fca4c..b6fe2eb 100644 --- a/src/gui_occt_view.h +++ b/src/gui_occt_view.h @@ -26,7 +26,7 @@ #include "shp_rotate.h" #include "shp_scale.h" #include "utl_types.h" -#include "ezy_asset_store.h" +#include "utl_asset_store.h" class Delta; class GUI; diff --git a/src/sketch_json.cpp b/src/sketch_json.cpp index 8a22088..3397410 100644 --- a/src/sketch_json.cpp +++ b/src/sketch_json.cpp @@ -17,7 +17,7 @@ #include "sketch_edge.h" #include "sketch_nodes.h" #include "sketch_underlay.h" -#include "ezy_asset_store.h" +#include "utl_asset_store.h" #include "gui_occt_view.h" #include "utl_json.h" diff --git a/src/sketch_underlay.cpp b/src/sketch_underlay.cpp index 5c8fa25..e1e9193 100644 --- a/src/sketch_underlay.cpp +++ b/src/sketch_underlay.cpp @@ -23,7 +23,7 @@ #include #include "utl_geom.h" -#include "ezy_asset_store.h" +#include "utl_asset_store.h" #include "gui_occt_view.h" #include "sketch_nodes.h" #include "utl.h" diff --git a/src/ezy_asset_store.cpp b/src/utl_asset_store.cpp similarity index 98% rename from src/ezy_asset_store.cpp rename to src/utl_asset_store.cpp index e36291b..eac8e60 100644 --- a/src/ezy_asset_store.cpp +++ b/src/utl_asset_store.cpp @@ -1,4 +1,4 @@ -#include "ezy_asset_store.h" +#include "utl_asset_store.h" #include #include diff --git a/src/ezy_asset_store.h b/src/utl_asset_store.h similarity index 100% rename from src/ezy_asset_store.h rename to src/utl_asset_store.h diff --git a/src/ezy_io.cpp b/src/utl_io.cpp similarity index 99% rename from src/ezy_io.cpp rename to src/utl_io.cpp index 3588dc7..e487cfc 100644 --- a/src/ezy_io.cpp +++ b/src/utl_io.cpp @@ -1,6 +1,6 @@ -#include "ezy_io.h" +#include "utl_io.h" -#include "ezy_asset_store.h" +#include "utl_asset_store.h" #include diff --git a/src/ezy_io.h b/src/utl_io.h similarity index 100% rename from src/ezy_io.h rename to src/utl_io.h diff --git a/tests/sketch_tests.cpp b/tests/sketch_tests.cpp index 15c6191..cfb88a2 100644 --- a/tests/sketch_tests.cpp +++ b/tests/sketch_tests.cpp @@ -19,8 +19,8 @@ #include "sketch_json.h" #include "sketch_edge.h" #include "sketch_nodes.h" -#include "ezy_io.h" -#include "ezy_asset_store.h" +#include "utl_io.h" +#include "utl_asset_store.h" #include "utl_occt.h" using namespace glm; From 2e5382f707f83fcd3b9df41d2f78966047a51b58 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 19:07:55 -0600 Subject: [PATCH 7/8] Remove experiment dbg code. --- agents/workflows/local-dev.md | 2 -- docs/bugs.md | 2 +- docs/building-occt.md | 2 +- src/gui_occt_view.cpp | 42 +++++------------------------------ src/occt_view_wasm_bisect.h | 23 ------------------- 5 files changed, 8 insertions(+), 63 deletions(-) delete mode 100644 src/occt_view_wasm_bisect.h diff --git a/agents/workflows/local-dev.md b/agents/workflows/local-dev.md index 1566df8..e5a359e 100644 --- a/agents/workflows/local-dev.md +++ b/agents/workflows/local-dev.md @@ -68,8 +68,6 @@ High-level flow (after `emsdk_env`): ninja -C build-em-7-9-3 ``` - **Wasm GLES bisection:** uncomment one toggle in `src/occt_view_wasm_bisect.h`, then `ninja`. - 3. Serve locally: ```powershell diff --git a/docs/bugs.md b/docs/bugs.md index da00abf..c26886a 100644 --- a/docs/bugs.md +++ b/docs/bugs.md @@ -42,4 +42,4 @@ - `add_arc_circle_edges` (`sketch_edges.cpp`) may use inconsistent node indices for intersection/knot logic vs `GC_MakeArcOfCircle` (vector is `[start, pick, end]` from `Sketch::add_arc_circle_`; `pt_end` / knot end node must be `node_idxs[2]`, not `[1]`). A long-standing knot bug masked wrong argument order in circle finalize (`sketch_tools.cpp`: `add_arc_circle_(points[0], points[2], points[1])` passes bottom/top as `pt_b` where the API expects arc *end*, with pick on the opposite side). - `update_faces()` face walker can loop on parallel shared edges (e.g. slot horizontals); a `Walk_key` step cap was added in `sketch_topo.cpp` as a guard — may hide topology errors rather than fix turn logic. - Relevant tests (if re-added): `SlotHorizontalLinesUnsplitBeforeCircleArcs`, `ArcSplit_CircleCrossesSlotLines` in `tests/sketch_tests.cpp`. Use `slot_c` perpendicular to the slot centerline (e.g. `(15, 5)` for `slot_b = (15, 0)`), not `(0, 5)`, to match the slot tool. -* Wasm / OCCT 8.x GLES shading regression: With OCCT **8.0.0.p1** (`V8_0_0_p1`) and `TKOpenGles`, new solids and extrude previews often render wireframe-only or with partial face shading in the browser. Native desktop (OCCT 8 + `TKOpenGl`) is unaffected. Rebuilding against **7.9.3** (`scripts/build-occt-793-wasm.ps1`) restores correct shading. Workarounds in EzyCad (`SetMaterial` + `Redisplay`, `refresh_shape_shading_`, bisect toggles in `occt_view_wasm_bisect.h`) do not fix the root cause on OCCT 8. Use 7.9.3 for wasm builds and demo publishing until upstream fixes GLES presentation. +* Wasm / OCCT 8.x GLES shading regression: With OCCT **8.0.0.p1** (`V8_0_0_p1`) and `TKOpenGles`, new solids and extrude previews often render wireframe-only or with partial face shading in the browser. Native desktop (OCCT 8 + `TKOpenGl`) is unaffected. Rebuilding against **7.9.3** (`scripts/build-occt-793-wasm.ps1`) restores correct shading. Workarounds in EzyCad (`SetMaterial` + `Redisplay`, `refresh_shape_shading_`) do not fix the root cause on OCCT 8. Use 7.9.3 for wasm builds and demo publishing until upstream fixes GLES presentation. diff --git a/docs/building-occt.md b/docs/building-occt.md index ed361c9..6c9c2c9 100644 --- a/docs/building-occt.md +++ b/docs/building-occt.md @@ -140,7 +140,7 @@ FreeType wasm configure also disables optional zlib/png/harfbuzz finds to simpli | `emcc` not found | Run `emsdk_env.bat` / `emsdk_env.ps1` in the same shell | | EzyCad configure hangs on `find_package(OpenCASCADE)` | `emcmake cmake ... --debug-output`; verify `OpenCASCADE_DIR` path | | OCCT 8 + ghosted dimension labels | Retest `gui.edge_dim_text_render_mode` (Z-layer Topmost); grid compositing changed in 8.0 | -| Shaded faces missing / wireframe-only solids (wasm, OCCT 8.x) | Use **7.9.3** (`build-occt-793-wasm.ps1`); see [bugs.md](bugs.md). Bisect toggles in `src/occt_view_wasm_bisect.h` do not fix the OCCT 8 GLES regression | +| Shaded faces missing / wireframe-only solids (wasm, OCCT 8.x) | Use **7.9.3** (`build-occt-793-wasm.ps1`); see [bugs.md](bugs.md) | --- diff --git a/src/gui_occt_view.cpp b/src/gui_occt_view.cpp index bb0cc15..2fa10e4 100644 --- a/src/gui_occt_view.cpp +++ b/src/gui_occt_view.cpp @@ -61,7 +61,6 @@ #include #include #include -#include "occt_view_wasm_bisect.h" #endif #include @@ -151,12 +150,8 @@ void Occt_view::init_viewer() #else // __EMSCRIPTEN__ Handle(Aspect_DisplayConnection) aDisp; Handle(OpenGl_GraphicDriver) aDriver = new OpenGl_GraphicDriver(aDisp, false); - aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL -#if defined(WASM_BISECT_DISABLE_OPAQUE_ALPHA) - aDriver->ChangeOptions().buffersOpaqueAlpha = false; -#else + aDriver->ChangeOptions().buffersNoSwap = true; // swap has no effect in WebGL aDriver->ChangeOptions().buffersOpaqueAlpha = true; // avoid unexpected blending of canvas with page background -#endif // Match native OpenGL path (sRGBDisable) so shading/material gamma is consistent vs desktop. aDriver->ChangeOptions().sRGBDisable = true; if (!aDriver->InitContext()) @@ -170,7 +165,6 @@ void Occt_view::init_viewer() aViewer->SetDefaultShadingModel(Graphic3d_TypeOfShadingModel_Phong); aViewer->SetDefaultLights(); aViewer->SetLightOn(); -#ifndef WASM_BISECT_DISABLE_LIGHT_CAST_SHADOWS for (NCollection_List::Iterator aLightIter(aViewer->ActiveLights()); aLightIter.More(); aLightIter.Next()) { @@ -178,7 +172,6 @@ void Occt_view::init_viewer() if (aLight->Type() == Graphic3d_TypeOfLightSource_Directional) aLight->SetCastShadows(true); } -#endif Handle(Wasm_Window) aWindow = new Wasm_Window("#canvas"); @@ -214,34 +207,11 @@ void Occt_view::init_viewer() params.ToShowStats = true; params.ShadowMapResolution = 1024; params.OitDepthFactor = 0.0; - params.Resolution = (unsigned int)(96.0 * myDevicePixelRatio + 0.5); -#ifdef __EMSCRIPTEN__ -#if defined(WASM_BISECT_DISABLE_MSAA) - params.NbMsaaSamples = 0; -#else - params.NbMsaaSamples = 8; -#endif -#if defined(WASM_BISECT_DISABLE_RESOLUTION_SCALE) - params.RenderResolutionScale = 1.0; -#else - params.RenderResolutionScale = 2.0; -#endif -#if defined(WASM_BISECT_DISABLE_VIEW_SHADOWS) - params.IsShadowEnabled = false; -#else - params.IsShadowEnabled = true; -#endif -#if defined(WASM_BISECT_DEPTH_PEELING_OIT) - params.TransparencyMethod = Graphic3d_RTM_DEPTH_PEELING_OIT; -#else - params.TransparencyMethod = Graphic3d_RTM_BLEND_UNORDERED; -#endif -#else - params.NbMsaaSamples = 8; - params.RenderResolutionScale = 2.0; - params.IsShadowEnabled = true; - params.TransparencyMethod = Graphic3d_RTM_BLEND_UNORDERED; -#endif + params.Resolution = (unsigned int)(96.0 * myDevicePixelRatio + 0.5); + params.NbMsaaSamples = 8; + params.RenderResolutionScale = 2.0; + params.IsShadowEnabled = true; + params.TransparencyMethod = Graphic3d_RTM_BLEND_UNORDERED; capture_occt_grid_rect_from_viewer_(aViewer); diff --git a/src/occt_view_wasm_bisect.h b/src/occt_view_wasm_bisect.h deleted file mode 100644 index 9913e29..0000000 --- a/src/occt_view_wasm_bisect.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -// WASM / TKOpenGles bisection toggles (Emscripten only). All OFF by default. -// -// To isolate OCCT 8 wasm shading bugs: uncomment ONE line below, rebuild build-em, retest. -// -// DISABLE_VIEW_SHADOWS - RenderingParams.IsShadowEnabled=false -// DISABLE_LIGHT_CAST_SHADOWS - skip SetCastShadows on directional lights -// DISABLE_MSAA - NbMsaaSamples=0 -// DISABLE_RESOLUTION_SCALE - RenderResolutionScale=1.0 (default wasm uses 2.0) -// DEPTH_PEELING_OIT - TransparencyMethod depth peeling instead of BLEND_UNORDERED -// DISABLE_OPAQUE_ALPHA - OpenGl_GraphicDriver buffersOpaqueAlpha=false - -#ifdef __EMSCRIPTEN__ - -// #define WASM_BISECT_DISABLE_VIEW_SHADOWS -// #define WASM_BISECT_DISABLE_LIGHT_CAST_SHADOWS -// #define WASM_BISECT_DISABLE_MSAA -// #define WASM_BISECT_DISABLE_RESOLUTION_SCALE -// #define WASM_BISECT_DEPTH_PEELING_OIT -// #define WASM_BISECT_DISABLE_OPAQUE_ALPHA - -#endif From e3d557666826725cc867b8b87b2d85317da0a490 Mon Sep 17 00:00:00 2001 From: Trailcode Date: Sun, 5 Jul 2026 19:11:55 -0600 Subject: [PATCH 8/8] Doc --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a997f89..53456b3 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ EzyCad is maintained by a small team and we would love more contributors. If you **Style guides:** [ezycad_code_style.md](docs/ezycad_code_style.md) for C++ in `src/`; [ezycad_doc_style.md](docs/ezycad_doc_style.md) for user guides and [Read the Docs](https://ezycad.readthedocs.io/). Both human developers and AI coding agents should follow the relevant guide. +**Developer module docs:** Maintainer notes for major `src/` modules live under [src/doc/](src/doc/) (sketch, shape, GUI, script, utilities). These are for contributors working in the codebase, not end-user guides. + **AI / agent instructions:** Short repo-local notes for coding assistants live under [agents/](agents/). Root-level discovery markers are `AGENTS.md` and `agents.md`. See [agents/README.md](agents/README.md) for the index (start with [agents/workflows/local-dev.md](agents/workflows/local-dev.md)). ## In-tree third-party libraries