- Pillar 1 (Runtime Auto-Sync):
* Add sync_agent_tool_schemas() in src/mcp.rs to atomically export live tool schemas & instructions to ~/.gemini/antigravity/mcp/opencadstudio/
* Automatically invoke sync on MCP startup (mcp::run()) and GUI automation startup (transport::listen())
* Add --sync-mcp-schemas CLI flag in src/cli.rs and src/main.rs
- Pillar 2 (Development Hook):
* Strengthen export_agent_tool_schemas test in src/mcp.rs to assert all schema files and instructions.md exist and are in sync on every cargo test run
- Pillar 3 (In-Band Dynamic Introspection):
* Add 'tools' to READ_OPS in src/mcp.rs and query match in src/app/control/mod.rs
* Enable agents to dynamically query live tool definitions and instructions via ocs_read (op: 'tools') over MCP or raw socket
- Pillar 4 (Non-Blocking Validation):
* Set additionalProperties: true on ocs_capture schema in src/mcp.rs to prevent rigid schema rejection of emerging options
A release build hides the console and is stripped, so a panic ended the
process with nothing on screen, nothing on stderr and nothing on disk.
The window simply disappeared. That is all a reporter has ever had to
work from, which is why #635 and #845 ask for crash logs and why a
crash like #830 can be reported for months with no detail beyond the
steps to reach it.
Every panic now writes one file to <config>/crash_logs naming the
version, the OS, the graphics backend that was live, the thread, the
panic message, the source location and a backtrace. The hook chains
onto whatever was installed before it, so a debug build still prints to
stderr as well, and it is installed after the child-process handoffs:
a GPU probe aborting is how an unusable backend gets detected, not a
crash worth filing.
The report is written by a plain function taking the message and
location, so the layout is covered by tests without installing a panic
hook -- a test that asserts inside a hook fires on every other test's
panic and aborts the whole binary, which is how the first version of
these tests failed.
Backtraces use `force_capture`, not `capture`: a report that says
"disabled backtrace" because the user never set RUST_BACKTRACE is
exactly the report that helps nobody.
The text is deliberately English and does not go through `t!`. It is
read by whoever receives the bug report, and a panic handler that calls
into the localisation machinery risks a second panic while handling the
first.
Confirmed on Windows 11 by reproducing a real crash: opening either of
two large drawings on an Intel Iris Xe dies inside wgpu, and the report
names it -- "Buffer with '(wgpu internal) StagingBelt staging buffer'
label is invalid", backend dx12, with the full frame list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The crash sentinel is armed before the event loop and disarmed after it
returns, so anything that stops the process without unwinding leaves the
file behind and the next launch skips that backend. A real GPU failure
does that — and so does the user closing the app from Task Manager, an
OOM kill, a power cut, or a panic with nothing to do with graphics.
The cost is not a one-off: the skip is permanent, and each later
abnormal end takes the next backend with it, walking a machine down
dx12 -> vulkan -> gl -> software rasterizer. The user is told their
driver is at fault the whole way.
Seen on this machine: a debug build died once while opening a large
drawing, and every launch afterwards reported "dx12 is not usable on
this GPU" on an Intel Iris Xe. Probing the backends directly showed
dx12 not merely usable but the best of the three
(max_storage_buffers_per_shader_stage 262144, against 59 for Vulkan
and 16 for GL). Clearing the stale sentinel brought dx12 straight back.
The failures this guard was built for kill the process at GPU init or
on the first drawing viewport, which is why arming it at all works. A
run that keeps drawing well past that has already shown the backend is
fine, so it now marks the sentinel `survived=1` after
SENTINEL_PROOF_DELAY, and a sentinel carrying that marker accuses
nobody. An early death still blacklists exactly as before.
Verified both ways on Windows 11 with an Intel Iris Xe: killed after
40s, the next launch still chose dx12; killed after 8s, the next launch
skipped dx12 and fell back to Vulkan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- `--export ... --target-version`, and explicit target_format /
target_version on automation and protocol saves; a conversion that
would drop unsupported records is refused unless allow_lossy is set.
- `audit` read op: dangling layers/blocks, duplicate handles, non-finite
bounds, serialization, lossy records and the source DXF handle graph.
- `save_verified`: writes the requested version from the same snapshot a
normal save writes, reopens it, checks the version and the entity
manifest, audits the ASCII DXF handle graph and returns a SHA-256.
- ZOOM EXTENTS includes SDF text glyphs.
- Live MCP acceptance script and an optional reference-console audit
runner under docs/automation.
Squashed from the PR with review fixes: merged with the DWT save path,
the save snapshot, the tool lists added since, and vendor-neutral
harness names; the dated verification record is left out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The operations docs/automation/API-SPEC.md (PR #1383) describes, brought
over from huaninstratech/OpenCADStudio so the specification no longer runs
ahead of the code:
- entity CRUD: entities_create, entities_delete, entities_transform,
entities_copy_to, and `where` filters on query
- blocks and export: block_define, block_delete, a normalised wblock
- publishing: layout_create, page_setup_set, plot (per_page included)
- metadata: sysvar, xdata_set / xdata_get, file_identity
- collections: group_create, selection_set_save / load
- control plane: batch, events, view_focus, close
- the REST transport: `--http <port>`, the /api/v1 routes and their
OpenAPI 3 description
- person-in-the-loop picks: user_select and getpoint, hosted by the GUI
over the same REST routes, with on-screen pending signals
Taken from the fork's commits b7a7ed80, 69396b42, 64f1cf6a, c3aafc8e,
bb5a7bfa, 52317a5c, 50d293ba, b90e194f, 517c6397, 53a5c4c9, a49ac045,
37de5a46, a78b1822, 6d9aed2a, 33f522da, 5b5cafe2, 02a7e78d, 94df40dc,
fd5bc14d, 485fac61 and 2c5fc994.
Left out on purpose: 37de5a46 also removed the donation surface (the
Patreon fetch, the donation prompt, the Start-page supporters rail, the
DONATE command and its locale strings) and replaced the About dialog's call
to action; that is the fork's own product decision and stays out. The
fork's version bump and its unrelated IFC/BIM work are not part of this.
Adapted while porting: the REST server and the GUI bridge are desktop-only
and a template start is refused in the web build, so the wasm target still
compiles; the two plot tests read the PDF through the shared stream helper,
because exported streams are Flate-compressed; the benches set the new
crosshair field; comments and docs describe each operation in its own
terms rather than by another product's API names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Older GPUs without usable DirectX 12 Feature Level 12_0 or working
Vulkan support (e.g. legacy Intel iGPUs and other old parts), and old
GLES drivers whose shader compiler rejects iced's own shaders, crash
at startup or on the first drawing viewport: StagingBelt aborts in
Buffer::get_mapped_range (panic=abort, silent exit) on the DX12 path,
and shader compilation fails inside iced's gradient pipeline on the
GLES path. Selecting a working backend via WGPU_BACKEND before iced
boots avoids both classes.
- Probe each candidate backend (Windows: dx12,vulkan,gl; Linux:
vulkan,gl) in an isolated --gpu-probe child: device request,
staging-style mapped upload, render pipeline creation (its fragment
shader reads a private vec4 array, catching old-Mesa compile
failures), offscreen render and readback. First fully-passing
backend wins; explicit --backend / WGPU_BACKEND / --safe-mode
always win, and Windows keeps preferring DX12/Vulkan (#55).
- Software adapters (WARP/llvmpipe/SwiftShader) are the last resort
before a best-effort GL attempt: slow but alive.
- The crash sentinel names the attempted backend so only implicated
backends are skipped next launch.
- Winning probes report adapter limits; GPUs without shader storage
buffers get the packed compatibility renderer automatically, with
a startup command-line notice so the switch is never silent.
Reuse the application library from the binary entry point instead of compiling the module tree twice. Keep desktop-only transports and caches out of web builds, remove unused test code, and check executable targets in CI.
Use the operating system trust store and Windows proxy settings while keeping TLS verification enabled. Add retryable registry errors with copyable diagnostics.\n\nRefs #563
wgpu/naga report pipeline and shader failures through the log facade,
then leave the canvas empty - with no logger installed on wasm the
message was lost, so a broken GPU path looked like "draws nothing" with
a clean console. Install a web logger that mirrors Error-level records
to the browser console and into a fixed DOM overlay with selectable
text, a Copy button and repeat-collapsing; panics land in the same
banner via a chained hook. Windows Chrome/Edge reporters can now paste
the exact ANGLE/D3D11 error instead of a screenshot of a blank canvas.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Source half of b672023 (which captured only the removed bundled
thumbnails — the add aborted on the already-staged deletions): the
videos module (playlist scan, oEmbed titles, config-dir thumbnail
cache), the boot fetch on its own thread with the loading state, the
overlay rail + Videos tab on the Start page, and the earlier tabbed-
layout handover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Double-clicking a drawing started a whole second copy of the app. Now it
lands as a tab in the editor already open, and that window comes forward.
The election is the bind itself: every GUI launch tries a deterministic
per-user loopback port, and the OS grants it to exactly one process and
releases it on any death, SIGKILL included. Nothing to reap, no PID to
probe, no stale lock after a crash. The winner serves the port from an
iced subscription; the loser checks it is talking to a matching editor,
hands the paths over and exits. Every surprise — no answer, a stranger on
the port, a timeout — boots a normal window, i.e. the old behaviour: the
feature can degrade, but it cannot lose the file.
The gate in main is positional, after every headless mode has returned.
The plugin runner is this same binary re-spawning itself, so a flag list
would rot the first time a mode is added; a position cannot.
Handing a drawing over that is already open switches to its tab instead
of loading a second copy, comparing resolved paths so a symlink or a
`..` hop is still recognised as the same file.
`opening` is a single slot that a second open would overwrite, and
on_file_opened drops any result arriving once it is clear — so drawings
handed over at once would silently vanish. They queue in pending_opens,
drained from every path that clears the slot, error and cancel included.
Exec becomes %F so one launch takes the whole selection; the CLI takes a
list and boot fans it out through the same door. --new-instance forces a
separate process; --read-only / --script / --new always get their own.
Not reused: the automation server. It dispatches arbitrary commands and
writes arbitrary paths, and its open replaces the active tab's document
in place — publishing that on a port every local process can reach would
be unforced privilege escalation. Only its JSON-lines framing is shared.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Round-trip a raster preview through the DWG's embedded preview slot and
surface it to the OS file managers, so drawings show their contents in
Explorer / Finder / Nautilus instead of a generic document icon.
OCS:
* io::thumbnail — rasterize the scene to a BMP DIB on save (embedded via
acadrust's new Preview type) and read it back on open for the Start
page recent list; `--dwg-thumbnail IN OUT SIZE` CLI extracts a badged
PNG for external thumbnailers.
* io::file_association::install_thumbnailer — self-install the OS
integration on startup: Linux writes a .thumbnailer + hicolor mimetype
icons; Windows registers the IThumbnailProvider DLL under HKCU.
Shared core:
* crates/dwg-thumbnailer — lean (image-only) preview extractor + the
`badge_dwg` full-width "DWG" band, used by every platform so the
ribbon is single-sourced. Ships an rlib+staticlib.
* crates/dwg-thumbnailer-win — IThumbnailProvider COM in-proc server
(cfg(windows), CI-built as a workspace member).
* macos/ — QuickLook thumbnail extension (Swift + C-ABI bridge to the
core staticlib), assembled into a .appex.
Icons & packaging (single SVG source -> per-platform assets in CI):
* assets/mimetypes/image-vnd.{dwg,dxf}.svg — themed file icons.
* packaging: WiX ships dwg/dxf.ico + the thumbnail DLL and points the
ProgIds at them; Info.plist gets CFBundleTypeIconFile + the QuickLook
extension in Contents/PlugIns.
* release.yml — generate .ico (Windows) and .icns (macOS) from the SVGs,
build the QuickLook .appex, and bundle everything.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ribbon:
- A ▾ selector at the right of the tab row picks the tool-panel density —
Auto (size to window), Full, Compact, or Collapsed. The choice is persisted.
It hides itself when the tool row is already tight, giving the cramped tab
row its space back.
Start page:
- The Recent Documents card is restyled to match the Supporters card
(rounded corners, 20px padding, matching title + colours).
- Its scrollbar no longer sits on top of the row ✕ remove buttons (a gutter
is reserved).
- A footer control sets how many recent files to keep: [-] an editable count
[+] with the max shown. Typing applies on Enter; an over-max entry snaps to
the max. The limit is persisted and enforced on load and on every open.
- The "Support on Patreon" heart is now the SVG icon instead of a font glyph.
Config:
- Add a shared `config::config_dir()` helper; the recent-files and status-bar
stores now go through it instead of each re-deriving the path. New minus.svg
icon for the stepper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a right-hand rail on the Start page listing active paying patrons
(name + pledge amount, highest first) with a "Support on Patreon" button.
The list is fetched once at boot from the Patreon API in the background;
free followers, $0 tiers, declined and former patrons are excluded.
The creator access token is read at build time from OCS_PATREON_TOKEN
(option_env!), so it never lives in source or git — the release workflow
passes it from a repo secret, and build.rs re-bakes when it changes.
Without a token the rail just shows the support button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocate three root-level modules into io/, where they belong by concern:
- update_check.rs -> io/update_check.rs (its own doc-comment notes it
mirrors how crate::io wraps blocking calls in async).
- patterns.rs, linetypes.rs -> io/ (standard resource catalogs parsed
from asset files and populated into the document — IO-shaped work).
Fix the moved files' include_str! paths (one level deeper), add the
mod declarations to io/mod.rs, drop them from lib.rs/main.rs, and
re-point all crate:: references to crate::io::. No behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the ad-hoc `--serve` arg sniff with a clap-parsed CLI:
- positional <file> to open at startup (also the OS file-association path)
- --new, --read-only, --backend, --safe-mode/--no-gpu
- --serve/--port (headless JSON automation, unchanged behaviour)
- --export IN OUT (headless format convert via the automation load/save infra)
- --script FILE (run command lines at startup)
- --log/RUST_LOG, plus --version/--help for free
GUI-only options pass to app::boot through a OnceLock config; read-only
gates the save paths. The web build skips parsing (no args).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an external automation API (issue #29 / #100 track 2): `OpenCADStudio
--serve` runs without a GUI and is driven over a line-based JSON protocol
on stdin/stdout — open / new / run / entities / save. State persists
across requests so a script or AI agent can act, observe, and act again.
`run` drives the app's existing command system rather than a separate
binding, so coverage grows with the app; synchronous commands apply now,
pick-based interactive ones come once coordinate feeding is wired. Ships
a ~100-line example ocs.py client over the same protocol — no FFI to
maintain. Headless works because the app already constructs and dispatches
GUI-less in tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
wasm32 has no threads, so rayon's global pool would panic at runtime.
Add `crate::par::prelude`: native re-exports `rayon::prelude`, wasm
provides sequential `std`-iterator equivalents (par_iter / into_par_iter
/ flat_map_iter over slices, Vec, HashMap, HashSet). All call sites now
`use crate::par::prelude::*;` instead of rayon directly.
- wire_gpu's rayon `reduce` (2-arg, no std equivalent) is cfg-split to
a sequential flat_map concat on wasm.
- rayon moves to a non-wasm target dependency.
Native still runs in parallel; all 75 tests pass. wasm bin compiles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The binary now compiles for wasm32 as a runnable web target:
- Split the main-window render into `view_main`, bypassing the
multi-window id dispatch the browser cannot use.
- Add `run_web` / `boot_web`: a single-window `iced::application`
entry for wasm (the browser canvas is the only window). Native
keeps the multi-window `iced::daemon` via cfg-split in `main`.
- Add a `console_error_panic_hook` (wasm-only) for readable panics.
- Add `index.html` + `Trunk.toml` so `trunk serve` builds the wasm
bundle (no-default-features → no solid3d) with COOP/COEP headers.
Native build and all 75 tests unchanged. Browser runtime (rayon
threads, canvas attach, real file I/O) is the next iteration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First step toward a web build: the library now compiles for
wasm32-unknown-unknown with --no-default-features.
- Add a `solid3d` feature (default on) gating truck-meshalgo /
truck-shapeops / lzma-sys. These pull vtkio → xz2 → lzma-sys (C),
which cannot cross-compile to wasm. Without the feature the 3-D
solid path (tessellation, booleans, ACIS import) becomes no-ops;
2-D CAD is unaffected. Native builds keep the feature by default.
- Move native-only crates (ureq, open) to non-wasm target deps and
add cfg shims: update-check is a no-op on wasm, and a new `sys`
module provides open_url + handle_path (rfd FileHandle → PathBuf;
synthesizes a name-based path on the web).
Native build + all 75 tests unchanged. Browser runtime wiring
(entry point, trunk, real file I/O) is a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On some Windows hybrid-GPU laptops the AMD OpenGL driver (atio6axx.dll)
access-violates the instant wgpu enumerates its GL backend at startup,
killing the app before any window appears — even though DX12 works fine.
The crash is inside AMD's proprietary GL ICD, so it can't be fixed
app-side; instead default WGPU_BACKEND to dx12,vulkan on Windows so the
GL ICD is never touched. An explicit user-set WGPU_BACKEND still wins, so
GL remains available via override.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .github/workflows/release.yml: remove the build-flatpak job; matrix-
add macOS (arm64 + x86_64) that builds H7CAD.app by hand, renders
.icns from assets/logo.svg via librsvg + iconutil, and packages a
UDZO .dmg. AppImage path updated to packaging/H7CAD.desktop.
- packaging/H7CAD.desktop: moved out of flatpak/ so the AppImage job
doesn't depend on a Flatpak layout.
- packaging/Info.plist: macOS bundle template; __VERSION__ swapped
for the release tag at build time.
- flatpak/: deleted (manifests, metainfo, desktop, builder cache).
- src/main.rs: `#![cfg_attr(all(windows, not(debug_assertions)),
windows_subsystem = "windows")]` so the Windows GUI binary no
longer spawns a console window alongside it (debug builds keep
the console for log output).
- README: add macOS install section, document Gatekeeper / SmartScreen
on first launch since the binaries are unsigned.
Bump to 0.3.5.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a one-shot update check that runs on app boot and pops up a small
"Update Available" window when the GitHub releases API reports a newer
tag than this build's `CARGO_PKG_VERSION`.
- `src/update_check.rs` — async wrapper around a blocking `ureq` GET to
`api.github.com/repos/HakanSeven12/H7CAD/releases/latest`, returns
`Some(version)` when a newer release exists, `None` otherwise (silent
on network / parse failure so a missing connection never blocks
launch).
- `H7CAD::boot` chains a `Task::perform` next to the main-window open
so the check runs in parallel with first-paint.
- New window plumbed like the other floating panels (page setup, about,
etc.): `update_notice_window`/`update_notice_version` state, title
entry, `OsWindowClosed` cleanup, and a `ui::update_notice::view_window`
modal that shows installed vs. latest version plus "Later" /
"Open Release Page" buttons. Open routes through `open::that` to the
releases URL and dismisses the modal.
Dependency: adds `ureq = { version = "2", default-features = false,
features = ["tls"] }` — the smallest blocking HTTP client + TLS combo
that doesn't drag in an async runtime.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tessellates each block_record once into local-coord LocalWires; Insert
references transform-copy + ByBlock-resolve the cache rather than
explode+re-tessellate per reference. Same-style wires inside one Insert
are NaN-batched into a single WireModel to keep wgpu buffer counts
sane — large blocks that previously bailed to a marker now render in
a handful of buffers each.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>