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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
/.vs
# Cursor IDE (local only). Shared assistant-oriented notes live in agents/.
/.cursor/
/scripts/__pycache__
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Help > About: in-app dialog with the app version, a short product description, and a link to the GitHub repository (replaces opening the README in the browser from that menu item).
- **Keyboard zoom:** **NumPad +** / **NumPad -**, main **-**, and **Shift+=** (US **+**) zoom the 3D view in steps equal to **one mouse wheel tick** at the cursor (same internal scaling as scroll).
- **NumPad 8 / 2 / 4 / 6:** orbit the 3D view in fixed steps (same axes as LMB orbit); step is **View rotation step** in Settings (shared with Shift+NumPad roll; default 45 degrees; `gui.view_roll_step_deg`).
- **NumPad 5:** snap the 3D view to the nearest orthographic world-axis orientation (top/bottom/front/back/left/right) with roll reset; keeps eye-target distance.
- **View roll:** **Shift+NumPad 4** and **Shift+NumPad 6** roll the 3D view (Blender-style). **Settings** has **View rotation step** (degrees per key press for orbit and roll; default 45, stored as `gui.view_roll_step_deg`).
- Help > About: reads bundled `res/about.md` via [imgui_markdown](https://github.com/enkisoftware/imgui_markdown), shows version text, splash image (`res/AI-gen-splashscreen_05_01_2026_512.png`), and clickable links; assets are copied next to the executable (and preloaded for Emscripten).
- **Sketch length dimensions (Dimension tool):** dimensions are stored as an unordered pair of sketch nodes (not attached to a single edge). They can be selected in sketch mode and removed with **Delete**.
- **Project JSON:** root-level `ezyFormat` (current value `2`) on save. Sketches serialize `length_dimensions` as dense node-index pairs alongside linear edges as `[a, b, mid]` only.
- **Legacy load:** older sketch JSON that stored a per-edge dimension flag on indexed edges (`[a, b, mid, dim]`) or on legacy coordinate edges (`[pt_a, pt_b, dim]`) migrates those flags into `length_dimensions` when loading.

### Fixed

- Ship `res/default.ezy` (empty sketch template) so native copy steps and Emscripten `--preload-file` resolve the path; startup loading could already fall back if the file was absent.

### Changed

- **View roll** (**Shift**+**NumPad 4**/**6**, main **4**/**6**, or **Left**/**Right** arrow): same roll as **Shift**+**4**/**6**; helps when Num Lock makes the numpad send arrows. Handled on key **repeat** as well as press; **Shift**+main **4**/**6** no longer fall through to the selection filter.
- **Keyboard zoom** (**NumPad +/-**, **Shift+=**, **-**): each repeated OS key event zooms again so holding the key zooms continuously (uses GLFW key repeat).
- **Zoom:** **Zoom scroll scale** in Settings replaces the hard-coded wheel multiplier (**4**); stored as **`gui.view_zoom_scroll_scale`**. Hold **Shift** while scrolling or using +/- for Blender-style finer zoom (**x0.1** on the delta).
- **View roll** default step is **45** degrees (was 15).
- Window title shows the current project file name (e.g. after Open or Save), or `untitled` when there is no path yet; **File > New** clears the name so the title matches an empty document.
- **Dimension tool behavior:** click a straight edge to toggle a length dimension between its endpoints, or click two nodes to toggle a dimension between them; clicks away from nodes and edges do not create spurious dimensions. Node picks take precedence over edge picks when both could apply; moving the mouse updates node snap feedback in this mode.
- **Documentation:** `usage-sketch.md` and `usage.md` describe the tool as the **Dimension tool** and document the node-pair model.
- **Documentation:** `usage-sketch.md` and `usage.md` describe the tool as the **Dimension tool** and document the node-pair model. **Num Lock off** is documented as recommended for numpad view shortcuts (orbit, roll, zoom, snap); **Num Lock on** may remap the keypad on Windows and other OSes (`usage.md`, `usage-occt-view.md`, `usage-settings.md`).

## [0.1.0] - 2026-04-17

Expand Down
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Emscripten")
--preload-file ${CMAKE_SOURCE_DIR}/res/default.ezy@/res/default.ezy \
--preload-file ${CMAKE_SOURCE_DIR}/res/examples@/res/examples \
--preload-file ${CMAKE_SOURCE_DIR}/res/scripts/lua@/res/scripts/lua \
--preload-file ${CMAKE_SOURCE_DIR}/res/about.md@/res/about.md \
--preload-file ${CMAKE_SOURCE_DIR}/res/AI-gen-splashscreen_05_01_2026_512.png@/res/AI-gen-splashscreen_05_01_2026_512.png \
--shell-file ${CMAKE_SOURCE_DIR}/web/EzyCad.html"
)
else()
Expand Down Expand Up @@ -324,6 +326,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Emscripten")
--preload-file ${CMAKE_SOURCE_DIR}/res/default.ezy@/res/default.ezy \
--preload-file ${CMAKE_SOURCE_DIR}/res/examples@/res/examples \
--preload-file ${CMAKE_SOURCE_DIR}/res/scripts/lua@/res/scripts/lua \
--preload-file ${CMAKE_SOURCE_DIR}/res/about.md@/res/about.md \
--preload-file ${CMAKE_SOURCE_DIR}/res/AI-gen-splashscreen_05_01_2026_512.png@/res/AI-gen-splashscreen_05_01_2026_512.png \
--shell-file ${CMAKE_SOURCE_DIR}/web/EzyCad.html"
)
endif()
Expand Down Expand Up @@ -360,6 +364,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Emscripten")
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/res/default.ezy
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res/default.ezy
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/res/about.md
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res/about.md
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/res/AI-gen-splashscreen_05_01_2026_512.png
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res/AI-gen-splashscreen_05_01_2026_512.png
COMMENT "Copying res/ezycad_settings.json and res/default.ezy for Emscripten"
)
file(GLOB RES_EXAMPLE_FILES_EM "${CMAKE_SOURCE_DIR}/res/examples/*.ezy")
Expand Down Expand Up @@ -411,6 +421,12 @@ else()
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/res/default.ezy
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res/default.ezy
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/res/about.md
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res/about.md
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_SOURCE_DIR}/res/AI-gen-splashscreen_05_01_2026_512.png
$<TARGET_FILE_DIR:${PROJECT_NAME}>/res/AI-gen-splashscreen_05_01_2026_512.png
COMMENT "Copying res/ezycad_settings.json and res/default.ezy for native"
)
add_custom_command(
Expand Down Expand Up @@ -558,6 +574,7 @@ endif(APPLE)
include_directories(.)
include_directories("third_party")
include_directories("third_party/imgui")
include_directories("third_party/imgui_markdown")
include_directories("third_party/json/include")

# Define preprocessor macros for GLM
Expand Down
40 changes: 40 additions & 0 deletions agents/issues/001-imgui-openpopup-id-stack-and-tables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# [Draft] ImGui: document pattern for OpenPopup vs BeginPopup outside tables

**Paste into GitHub as a new issue.** Suggested labels: `documentation`, `imgui`, `low priority`

---

## Title

Document ImGui popup ID stack: avoid OpenPopup inside `BeginTable` unless BeginPopup matches scope

## Body

### Summary

Dear ImGui resolves `OpenPopup("id")` / `BeginPopup("id")` using the current **ID stack** (see [imgui#331](https://github.com/ocornut/imgui/issues/331)). Calling `OpenPopup` from inside a **table cell** while calling `BeginPopup` **after** `EndTable()` produces **different hashed popup IDs**, so the popup never opens.

### Context in EzyCad

We hit this when adding UI next to **Settings → 3D view navigation → View roll step** (slider inside `BeginTable`). The fix was to **defer** `OpenPopup` until after `ImGui::EndTable()`, in the same ID scope as `BeginPopup`.

**View roll (for users):** **Shift+NumPad 4** and **Shift+NumPad 6** roll the 3D view around the screen axis (Blender-style). The step in degrees is **Settings → 3D view navigation → View roll step** (`gui.view_roll_step_deg`, default 45). See [usage.md#view-roll](https://github.com/trailcode/EzyCad/blob/main/usage.md#view-roll) and `src/gui_mode.cpp` (`GUI::on_key`).

Related code: `src/gui_settings.cpp` (3D view navigation section). The experimental **Type** button + popup was removed; **Ctrl+click** on `SliderScalar` remains the supported way to type exact values.

### Suggestion

- Add a short note to **`ezycad_code_style.md`** or **`agents/README.md`** (or a tiny **`agents/imgui-notes.md`**) so future UI in tables does not repeat the mistake.
- Optional: extract a tiny helper, e.g. `defer_open_popup_after_table(bool& pending)` — only if we add more table-adjacent popups.

### Acceptance criteria

- [ ] Project docs mention ImGui popup + table ID stack **or** link to imgui#331 / upstream FAQ.
- [ ] No functional change required if docs-only.

---

## Metadata (do not paste)

- Created for agent/local drafting under `agents/issues/`.
- Repo: `trailcode/EzyCad`.
5 changes: 5 additions & 0 deletions agents/issues/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# GitHub issue drafts (repo-local)

Markdown drafts for issues to open at **https://github.com/trailcode/EzyCad/issues**. Copy the body into a new issue on GitHub (title + description).

These files are **not** a substitute for GitHub tracking; they keep wording and context in-repo for agents and contributors.
Binary file added res/AI-gen-splashscreen_05_01_2016_full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added res/AI-gen-splashscreen_05_01_2026_512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions res/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
![EzyCad splash](AI-gen-splashscreen_05_01_2026_512.png)

**EzyCad** (Easy CAD) is a CAD application for hobbyist machinists to design and edit 2D and 3D models for machining projects. It supports sketching, extruding, and geometric operations using OpenGL, Dear ImGui, and Open CASCADE Technology (OCCT). Export models to STEP, STL, and other formats for CNC or 3D printing.

Source repository: [https://github.com/trailcode/EzyCad](https://github.com/trailcode/EzyCad)

---

Product version appears in the application window title. MIT License; see the `LICENSE` file in the distribution.
37 changes: 4 additions & 33 deletions res/default.ezy
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
{
"ezyFormat": 2,
"mode": 0,
"shapes": [
{
"geom": "\nCASCADE Topology V3, (c) Open Cascade\nLocations 1\n1\n 1 0 0 0 \n 0 1 0 0 \n 0 0 1 0 \nCurve2ds 24\n1 0 0 1 0 \n1 0 0 1 0 \n1 10 0 0 -1 \n1 0 0 0 1 \n1 0 -10 1 0 \n1 0 0 1 0 \n1 0 0 0 -1 \n1 0 0 0 1 \n1 0 0 1 0 \n1 0 10 1 0 \n1 10 0 0 -1 \n1 10 0 0 1 \n1 0 -10 1 0 \n1 0 10 1 0 \n1 0 0 0 -1 \n1 10 0 0 1 \n1 0 0 0 1 \n1 0 0 1 0 \n1 10 0 0 1 \n1 0 0 1 0 \n1 0 0 0 1 \n1 0 10 1 0 \n1 10 0 0 1 \n1 0 10 1 0 \nCurves 12\n1 0 0 0 0 0 1 \n1 0 0 10 -0 1 0 \n1 0 10 0 0 0 1 \n1 0 0 0 -0 1 0 \n1 10 0 0 0 0 1 \n1 10 0 10 -0 1 0 \n1 10 10 0 0 0 1 \n1 10 0 0 -0 1 0 \n1 0 0 0 1 0 -0 \n1 0 0 10 1 0 -0 \n1 0 10 0 1 0 -0 \n1 0 10 10 1 0 -0 \nPolygon3D 0\nPolygonOnTriangulations 0\nSurfaces 6\n1 0 0 0 1 0 -0 0 0 1 0 -1 0 \n1 0 0 0 -0 1 0 0 0 1 1 0 -0 \n1 0 0 10 0 0 1 1 0 -0 -0 1 0 \n1 0 10 0 -0 1 0 0 0 1 1 0 -0 \n1 0 0 0 0 0 1 1 0 -0 -0 1 0 \n1 10 0 0 1 0 -0 0 0 1 0 -1 0 \nTriangulations 0\n\nTShapes 34\nVe\n1e-07\n0 0 10\n0 0\n\n0101101\n*\nVe\n1e-07\n0 0 0\n0 0\n\n0101101\n*\nEd\n 1e-07 1 1 0\n1 1 0 0 10\n2 1 1 0 0 10\n2 2 2 0 0 10\n0\n\n0101000\n-34 0 +33 0 *\nVe\n1e-07\n0 10 10\n0 0\n\n0101101\n*\nEd\n 1e-07 1 1 0\n1 2 0 0 10\n2 3 1 0 0 10\n2 4 3 0 0 10\n0\n\n0101000\n-31 0 +34 0 *\nVe\n1e-07\n0 10 0\n0 0\n\n0101101\n*\nEd\n 1e-07 1 1 0\n1 3 0 0 10\n2 5 1 0 0 10\n2 6 4 0 0 10\n0\n\n0101000\n-31 0 +29 0 *\nEd\n 1e-07 1 1 0\n1 4 0 0 10\n2 7 1 0 0 10\n2 8 5 0 0 10\n0\n\n0101000\n-29 0 +33 0 *\nWi\n\n0101100\n-32 0 -30 0 +28 0 +27 0 *\nFa\n0 1e-07 1 0\n\n0101000\n+26 0 *\nVe\n1e-07\n10 0 10\n0 0\n\n0101101\n*\nVe\n1e-07\n10 0 0\n0 0\n\n0101101\n*\nEd\n 1e-07 1 1 0\n1 5 0 0 10\n2 9 6 0 0 10\n2 10 2 0 0 10\n0\n\n0101000\n-24 0 +23 0 *\nVe\n1e-07\n10 10 10\n0 0\n\n0101101\n*\nEd\n 1e-07 1 1 0\n1 6 0 0 10\n2 11 6 0 0 10\n2 12 3 0 0 10\n0\n\n0101000\n-21 0 +24 0 *\nVe\n1e-07\n10 10 0\n0 0\n\n0101101\n*\nEd\n 1e-07 1 1 0\n1 7 0 0 10\n2 13 6 0 0 10\n2 14 4 0 0 10\n0\n\n0101000\n-21 0 +19 0 *\nEd\n 1e-07 1 1 0\n1 8 0 0 10\n2 15 6 0 0 10\n2 16 5 0 0 10\n0\n\n0101000\n-19 0 +23 0 *\nWi\n\n0101100\n-22 0 -20 0 +18 0 +17 0 *\nFa\n0 1e-07 6 0\n\n0101000\n+16 0 *\nEd\n 1e-07 1 1 0\n1 9 0 0 10\n2 17 2 0 0 10\n2 18 5 0 0 10\n0\n\n0101000\n-23 0 +33 0 *\nEd\n 1e-07 1 1 0\n1 10 0 0 10\n2 19 2 0 0 10\n2 20 3 0 0 10\n0\n\n0101000\n-24 0 +34 0 *\nWi\n\n0101100\n-14 0 -22 0 +13 0 +32 0 *\nFa\n0 1e-07 2 0\n\n0101000\n+12 0 *\nEd\n 1e-07 1 1 0\n1 11 0 0 10\n2 21 4 0 0 10\n2 22 5 0 0 10\n0\n\n0101000\n-19 0 +29 0 *\nEd\n 1e-07 1 1 0\n1 12 0 0 10\n2 23 4 0 0 10\n2 24 3 0 0 10\n0\n\n0101000\n-21 0 +31 0 *\nWi\n\n0101100\n-10 0 -18 0 +9 0 +28 0 *\nFa\n0 1e-07 4 0\n\n0101000\n+8 0 *\nWi\n\n0101100\n-27 0 -10 0 +17 0 +14 0 *\nFa\n0 1e-07 5 0\n\n0101000\n+6 0 *\nWi\n\n0101100\n-30 0 -9 0 +20 0 +13 0 *\nFa\n0 1e-07 3 0\n\n0101000\n+4 0 *\nSh\n\n0101100\n-25 0 +15 0 -11 0 +7 0 -5 0 +3 0 *\nSo\n\n1100000\n+2 0 *\n\n+1 1 ",
"material": 14,
"name": "Box"
}
],
"shapes": [],
"sketches": [
{
"arc_edges": [],
Expand All @@ -28,32 +22,9 @@
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": -0.0
"z": 0.0
}
}
}
],
"view": {
"at": {
"x": 0.8907552825678877,
"y": 5.662726832095677,
"z": 6.2384519054513206
},
"eye": {
"x": -61.538576671285604,
"y": -8.618591815144743,
"z": 53.52311764025625
},
"proj": {
"x": -0.7842226703185037,
"y": -0.17939858548361437,
"z": 0.5939789145121179
},
"scale": 6.280883086320588,
"up": {
"x": 0.36713761457808036,
"y": 0.6375645723074219,
"z": 0.6772897371881337
}
}
}
]
}
2 changes: 2 additions & 0 deletions res/ezycad_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"show_lua_console": true,
"show_options": true,
"show_python_console": true,
"view_roll_step_deg": 45.0,
"view_zoom_scroll_scale": 4.0,
"show_settings_dialog": true,
"show_shape_list": true,
"show_sketch_list": true,
Expand Down
41 changes: 41 additions & 0 deletions scripts/pbf-to-png.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Convert PDF content (often mislabeled as .pbf) to PNG with configurable DPI.
# Requires Python 3 and: pip install pymupdf
#
# Examples:
# .\pbf-to-png.ps1 -Input drawing.pbf -Dpi 300
# .\pbf-to-png.ps1 -Input doc.pdf -Output out.png -Dpi 150
# .\pbf-to-png.ps1 -Input manual.pdf -AllPages -Dpi 200

param(
[Parameter(Mandatory = $true, Position = 0)]
[string] $Input,

[string] $Output,

[double] $Dpi = 150,

[switch] $AllPages,

[int] $Page
)

$here = $PSScriptRoot
$py = Join-Path $here "pbf-to-png.py"
if (-not (Test-Path $py)) {
Write-Error "Missing script: $py"
exit 1
}

$argsList = @($py, $Input, "--dpi", $Dpi)
if ($Output) {
$argsList += @("-o", $Output)
}
if ($AllPages) {
$argsList += "--all-pages"
}
if ($PSBoundParameters.ContainsKey("Page")) {
$argsList += @("--page", $Page)
}

& python @argsList
exit $LASTEXITCODE
Loading
Loading