Commit Graph
156 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 10e4c18ba7 chore: silence native and wasm build warnings
Gate target-specific code so neither build warns about the other's items:
native-only helpers (view, window icon, file dialogs, file association, plugin
host/guard/registry, self-update) are cfg'd to non-wasm; the wasm-only font
fallback (script_of / cjk_lang / request) is cfg'd to wasm. Drop an unused mut,
allow the few genuinely-conditional dead items (mesh_click_hit, GPU layout
helpers, parity stubs), and gate the embedded PlotSettings-unrelated dead
constants. Both targets now build clean (only the unavoidable nom/quick-xml
future-incompat dependency notice remains).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 02:03:34 +03:00
Hakan SevenandClaude Opus 4.8 8c48233dcf merge: TEXTEDIT command + TEXTEDITMODE sysvar (#130)
Merge PR #130 (KarimJerbi) and resolve review findings:

- Conflict (settings.rs): keep current persisted-settings layout, append
  texteditmode field.
- Conflict (command/mod.rs): keep both new CmdResult variants — main's
  live-entity ops and the PR's text-edit suspend/resume.
- Bug: invalid TEXTEDITMODE value no longer aborts via the Measurement
  arm; it now re-prompts with the error and stays active.
- Dedup: single parse_texteditmode() helper replaces the value parsing
  duplicated across commands.rs, settings.rs and textedit.rs.
- Strip trailing whitespace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 12:05:54 +03:00
Hakan SevenandClaude Opus 4.8 de1f1c0e24 fix(3d): move and paste ACIS solids to the right place
Solids were missing from the entity transform dispatch, so move / rotate /
scale / mirror were silently no-ops on a 3D solid (only its wires shifted)
and a pasted solid stayed at its original location. Add a Transformable impl
for Solid3D/Region/Body/Surface (delegating to acadrust, which now composes
the move into the ACIS body placement) and register them in the dispatcher.

Also make cross-drawing paste land at the cursor: the clipboard centroid is
measured in the source drawing's offset-relative frame, so correct the paste
translation by the source/target world_offset difference (zero within one
drawing). Bumps acadrust for the solid-geometry transform fixes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 02:46:56 +03:00
Hakan SevenandClaude Opus 4.8 e45064f8a8 fix(clipboard): carry block-internal layers and refresh the layer panel
Cross-drawing paste recreated layers/linetypes/styles only for the
top-level selection, so objects inside a pasted block landed on layers the
target drawing didn't have. Scan the entities inside captured block
definitions for their dependencies too. Also refresh the layer panel after
a paste so the brought-in layers show in the manager and the dropdown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 02:12:58 +03:00
Hakan SevenandClaude Opus 4.8 fd15dd0aac feat(select): invert selection from the right-click menu
Add an Invert Selection entry to the selection context menu (below
Select Similar): it replaces the current selection with every other
selectable object in the active layout. Candidates come from the visible
wire set, so objects on off/frozen layers stay excluded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:31:05 +03:00
Hakan SevenandClaude Opus 4.8 e562738d9e fix(clipboard): carry block definitions across drawings on paste
Copying an INSERT and pasting it into another drawing rendered nothing:
the clipboard snapshotted referenced layers/linetypes/styles but not the
block definition itself, so the pasted reference pointed at a block the
target drawing didn't have.

Capture every block an copied INSERT references — walking nested INSERTs
transitively, skipping model/paper space and xref blocks — and store the
definition's base point plus its owned entities. On paste, recreate any
of those blocks the target drawing lacks via a new define_block_raw
(entities kept in block-local coordinates), before adding the pasted
references. Same-document pastes are unaffected since the blocks already
exist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 21:23:59 +03:00
Hakan SevenandClaude Opus 4.8 082018756e fix(hover): defer rollover pick until the cursor stops
The rollover hit-test now sweeps wires, hatches, block-internal hatches
and shaded solid bodies, which is O(N) per cursor frame and was freezing
the crosshair on large drawings. Each idle move now clears the live
highlight and arms a dwell timer; HoverDwellTick runs the pick once the
cursor has been still for HOVER_DWELL_MS (120 ms), using the active
tile's bounds so the deferred pick matches the entity actually under
the cursor in tiled viewports.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 20:43:33 +03:00
Hakan SevenandClaude Opus 4.8 e1c5917644 fix(web): paste clipboard text into the TEXT / MText editors
Ctrl+V was globally bound to the entity paste command, so it never pasted
into an open text editor, and even when routed there iced's clipboard read
is a no-op on the web. Route Ctrl+V to the focused editor and, on the web,
read the system clipboard via the browser's async Clipboard API (the
keypress is a user gesture, so the read is permitted):

- MText editor: insert at the caret (web + native).
- TEXT field: append on the web; native lets the text_input paste itself.
- No editor open: falls through to the entity paste command as before.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 17:05:02 +03:00
Hakan SevenandClaude Opus 4.8 54106ca9b0 feat(web): lazy per-script font loading for international CAD text
The web build has no system-font access, so CAD text in non-Latin scripts
(Cyrillic, Greek, CJK, …) rendered as nothing — build_fallback was a hard
None on wasm (#141). Instead, ship per-script Noto subsets under web/fonts
(one alphabet per file, SIL OFL 1.1) and fetch them lazily over HTTP the
first time a drawing uses that script, then outline glyphs with ttf-parser.

- web_font: Script enum + script_of (char → script font), a per-script
  store with lazy fetch, and a pending queue drained by the app loop.
- CJK is split by language (chinese/japanese/korean). Han ideographs share
  code points but differ by language, so the shared block is routed by the
  document's $DWGCODEPAGE (932→JP, 949→KR, GB/936→CN); kana is always
  Japanese, Hangul always Korean. Re-tessellates when the language changes.
- build_fallback (wasm) outlines from the fetched subset; clear_fallback_cache
  lets glyphs that missed while a font was in flight reappear on load.
- PollWebFonts subscription (web only) + WebFontLoaded message drive fetches.
- Trunk copy-dir serves web/fonts; nothing is bundled into the native binary
  (desktop keeps using system fonts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 16:13:26 +03:00
Hakan SevenandClaude Opus 4.8 65369f53cb fix(web): gate boot() to native so the wasm build compiles
The clap CLI work made boot() read crate::cli, but cli is cfg'd out on
wasm (the web build takes no args), so the wasm target failed to compile.
boot() is only used by the native iced::daemon run(); the web build boots
via boot_web(). Gate boot() with cfg(not(wasm32)) to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 14:27:51 +03:00
Hakan SevenandClaude Opus 4.8 79bb1d67b1 feat(cli): full command-line interface via clap
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>
2026-06-20 13:59:20 +03:00
Hakan SevenandClaude Opus 4.8 36d5891572 fix(layers): freeze toggle in ribbon dropdown + sortable manager columns
Ribbon layer dropdown rows were a single button, so a click on the
visible/freeze/lock icon fell through to the make-active handler instead
of toggling that state. Split the row: each icon is its own toggle
button, only the swatch+label selects the active layer. Toggle handlers
now resync the ribbon so the icon updates while the dropdown stays open.

Layer Manager columns are now sortable: clicking a header sorts by that
column, clicking again flips direction (SVG arrow indicator). Sort is
reapplied on every panel sync so it survives edits, preserves the
selection by name, and keeps the ribbon dropdown order in step.

Fixes #133

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 20:39:09 +03:00
Karim Jerbi 9a258f29ce feat: implement TEXTEDIT command and TEXTEDITMODE system variable
- Adds TEXTEDIT command to seamlessly edit annotation objects in-place.
- Matches industry-standard CAD tools' prompt behavior and "Current settings" banner logic.
- Implements the TEXTEDITMODE system variable (0/1) for Multiple vs Single editing modes.
- Enhances command lifecycle with CmdResult::SuspendForTextEdit and post_editor_closed hooks, allowing commands to cleanly pause and resume after external UI editors close.
2026-06-18 15:56:27 +01:00
Hakan SevenandClaude Opus 4.8 70e73877b2 feat(clipboard): carry referenced records across drawings on paste
Copy/paste between drawings dropped the entities' layer (and linetype /
text + dim style): the clipboard held only the entities, which reference
those records by name, so pasting into a drawing without a matching
record left the entity dangling on a non-existent layer. (#129)

Snapshot the referenced table records from the source drawing at copy
time (ClipboardDeps::capture) and, on paste, recreate any the target
drawing lacks with a fresh handle (merge_clipboard_deps). A record that
already exists by name in the target is left untouched — the target's
own definition wins; only missing ones are imported from the source.
Same-document pastes are unaffected (every record already exists).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:34:32 +03:00
Hakan SevenandClaude Opus 4.8 7f10b69e3c feat(automation): headless JSON server (--serve) + Python client
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>
2026-06-17 21:05:12 +03:00
Hakan SevenandClaude Opus 4.8 157412419e feat(plugin): uninstall + clean upgrade/reinstall of external plugins
Add an Uninstall button on installed external plugin cards that deletes
the package folder (effective next restart — the library stays resident
for the session). Reinstalling or upgrading via the marketplace now
clears any stale native library with a different name before writing the
new one, so the loader always picks the freshly installed binary.

Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:57:44 +03:00
Hakan SevenandClaude Opus 4.8 020a3a4c84 feat(plugin): marketplace — install plugins from a curated registry
Add a Plugin Manager marketplace that installs external add-ons from a
GitHub repo's Releases. A curated registry (plugins/registry.json in this
repo, extended by PR) lists discoverable plugins; the host fetches it,
shows each entry with a release dropdown, and on install downloads the
asset matching the user's platform plus plugin.toml into the plugins
folder (API-version checked first). Users can also link arbitrary
owner/repo manually. Linked repos persist in settings.

Desktop only (ureq + serde_json); the install path reuses the phase-2
loader. Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:24:56 +03:00
Hakan SevenandClaude Opus 4.8 29c71be101 feat(plugin): load external cdylib plugins at runtime (phase 2)
Move BuiltinPlugin into ocs_plugin_api (host feature) so out-of-tree
crates can implement it, and add export_plugin! to emit the two C
symbols a cdylib exposes: ocs_plugin_api_version (checked first, so an
ABI-incompatible build never runs) and ocs_plugin_register -> boxed
BuiltinPlugin. The host loads every compatible package from the plugins
folder at startup via libloading (desktop only), keeps the library
resident for the session, and merges its ribbon tab + command dispatch
into the same paths as built-ins (honouring enable/disable). The Plugin
Manager shows external packages with a Loaded / incompatible status.

Approach B: the plugin hands back a boxed trait object, assuming a
matching toolchain + ocs_plugin_api version (the version symbol enforces
the latter). crates/ocs_example_plugin is the reference cdylib.

Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:48:56 +03:00
Hakan SevenandClaude Opus 4.8 f6219a9609 feat(plugin): discover external plugin packages from the plugins folder
Phase-2 groundwork: scan <config>/OpenCADStudio/plugins/<id>/plugin.toml,
parse the documented keys (no new TOML dep), check for a platform native
library beside it, and gate on the API version. The Plugin Manager gains
an "External (plugins folder)" section showing each package with a
status pill (Ready to load / API incompatible / No library). Discovery
only — actually loading the cdylib is the next step.

Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:43:24 +03:00
Hakan SevenandClaude Opus 4.8 96ec672d5b feat(points): point style commands + DDPTYPE dialog, viewport-aware size
Points could only render as a 1px dot — no way to change PDMODE/PDSIZE.

- PDMODE / PDSIZE commands set the header value and rebuild the glyphs.
- DDPTYPE opens a Point Style dialog: a 5x4 grid of rendered glyphs
  (shape x enclosure), a size field, and relative/absolute radios.
- Relative (<=0) PDSIZE now sizes from the current world-per-pixel so the
  marker stays a roughly constant on-screen size across zoom, rebuilding
  on zoom via the existing camera-generation cache key. Absolute (>0)
  scales with the world as before. Switching to absolute with a zero size
  seeds a positive value from the on-screen size so it stays representable.

Part of the #100 extensibility epic (point style surfaced in #106).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 11:13:12 +03:00
Hakan SevenandClaude Opus 4.8 50825b6346 feat(plugin): ModuleEvent::PluginFileDialog for native file import
Add-ons had no way to request a file picker — only the host's own Open
dialog. PluginFileDialog lets a plugin tool ask the host to open a native
picker; on selection the host dispatches "<command> <path>" back to the
plugin with original case preserved (bypassing the command-line
upper-casing that mangles case-sensitive paths on Linux/macOS). The demo
plugin gains an Import tool exercising it.

Part of the #100 extensibility epic (surfaced in #106).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:22:08 +03:00
Hakan SevenandClaude Opus 4.8 844069b4ef feat(plugin): enable/disable plugins from the Plugin Manager
The manager was a read-only inventory. Each plugin now has an
Enable/Disable toggle: a disabled plugin keeps its manifest listed but
drops its ribbon tab and command dispatch. The disabled-id set persists
in settings.txt (disabled_plugins=) and the ribbon rebuilds on toggle.

Part of the #100 extensibility epic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 10:15:38 +03:00
Hakan SevenandClaude Opus 4.8 195415af84 feat(modal): drag dialogs by a title-bar grip, clamped to the window
In-canvas modals were locked to screen-centre. Add a green move-arrow
grip beside the ✕ that drags the dialog; positioning is an offset from
centre applied via asymmetric padding. The drag offset is clamped to the
window so a dialog stops at the edge instead of being squeezed, and the
offset resets when the dialog closes so each opens centred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 09:59:17 +03:00
Hakan SevenandClaude Opus 4.8 fefae274d4 feat(wasm): web Save/Save As go through the dialog with a direct download
On the web, Save and Save As now open the in-app Save dialog (filename +
format) like the desktop, and the unsaved-changes prompt's Save routes
through it too — instead of an immediate rfd download. Confirm serializes
to bytes and downloads directly via a Blob + hidden `<a download>` click
(a user gesture), so there is no intermediate "click to download" link.

Replaces the rfd-based download_web / WebSaveResult round-trip with a
synchronous sys::download_bytes. Native save is unchanged.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:52:20 +03:00
Hakan SevenandClaude Opus 4.8 3dd1276b6a refactor(ui): remove dead OS-window machinery after Plan B
With every dialog now an in-canvas modal, the per-dialog
`*_window: Option<window::Id>` fields are never set. Remove all 17
fields and their inits, trim `OsWindowClosed` to just the main window
(exit), drop the dialog branches from the window `title` closure, the
leftover `gain_focus` guards in the open handlers, and the now-unused
`iced::window` import in update.rs. Page-setup apply clears
`active_modal` instead of closing a window.

No behaviour change; native + web build, 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 00:09:04 +03:00
Hakan SevenandClaude Opus 4.8 94b906d95b feat(ui): migrate the last dialogs to modals — fully single-window (Plan B)
Move the unsaved-changes prompt, the Save As browser and the
file-association prompt onto `active_modal` / `modal_content`, reusing
their existing open/close helpers (now just set/clear active_modal). The
daemon `view` no longer dispatches per window — every dialog is an
in-canvas modal, so the app is single-window on native and web alike.

`close_active_modal` handles their ✕: the unsaved prompt cancels the
pending close, the association prompt records that it was answered.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:59:38 +03:00
Hakan SevenandClaude Opus 4.8 28232f1247 feat(ui): migrate style editors + nested colour picker (Plan B)
Move Table Style, MLeader Style and Dimension Style editors off OS
windows onto `active_modal` / `modal_content`, and turn the colour
picker into a nested modal that stacks over whichever dialog requested
it (`color_pick_target.is_some()` drives the overlay; CloseColorPicker
dismisses it). OpenColorWindow/ColorWindowPick no longer open/close an
OS window.

All thirteen former pop-up windows now render as in-canvas modals on
both native and web; only the native-only assoc/unsaved/save dialogs
remain as OS windows. Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:42:14 +03:00
Hakan SevenandClaude Opus 4.8 7cd938443e feat(ui): migrate Text Style and Multiline Style to modals (Plan B)
Move the Text Style and Multiline Style editors off OS windows onto the
shared `active_modal` / `modal_content` path. Their open handlers set
`active_modal` (keeping the existing buffer/stage setup); construction
moves from the daemon `view` dispatch into `modal_content`.

The remaining style editors (Table, MLeader, Dimension) still use OS
windows for now — they carry a nested colour picker that needs its own
handling. Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:29:50 +03:00
Hakan SevenandClaude Opus 4.8 b21d10f6e8 feat(ui): migrate 4 more dialogs to in-canvas modals (Plan B)
Consolidate all migrated dialogs into a single `modal_content` builder
matched on `active_modal`, rendered by view_main. Move Layers, Page
Setup, Layout Manager and Plot Style off OS windows onto it (their
construction relocated from the daemon `view` dispatch, which now only
handles the not-yet-migrated dialogs). Each keeps a bounded size.

Open/close handlers set `active_modal` instead of opening/closing
windows; ToggleLayers toggles it.

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 23:22:54 +03:00
Hakan SevenandClaude Opus 4.8 6a3663b3f1 feat(ui): unified modal dispatch; migrate 4 dialogs (Plan B)
Replace the per-dialog open flag with a single `active_modal:
Option<ModalKind>` and one `CloseModal` message. view_main matches it to
render the open dialog as an in-canvas modal.

Migrated off OS windows: About (content-sized), Shortcuts, Plugin
Manager and the Update notice (the latter three keep a bounded size via
a wrapping container, since their lists/scroll need defined bounds).

Native + web build; 75 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 19:07:25 +03:00
Hakan SevenandClaude Opus 4.8 e6213eafbb feat(ui): shared in-canvas modal overlay; migrate About (Plan B)
Start Plan B — replace the daemon's pop-up OS windows with in-canvas
modal overlays so dialogs work identically on native (one main window)
and web (canvas only).

- New `ui::modal::modal(base, content, on_close)`: dims the view behind
  a backdrop that blocks clicks but does NOT dismiss, frames the content,
  and adds a top-right ✕ as the sole close affordance. Sizes to the
  content (no `Fill`), so dialogs shrink to fit.
- view_main stacks the active modal on top, so both run paths show it.
- Migrate About as the pilot: `about_open` bool + AboutOpen/AboutClose
  instead of an OS window; About's view shrinks to content.

Remaining dialogs migrate to the same pattern next. Native + 75 tests OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:59:13 +03:00
Hakan SevenandClaude Opus 4.8 9d378a0533 feat(wasm): save files from the browser
Wire up save for the web build:

- `io::save_to_bytes`: serialize a document to a byte buffer (DXF via
  write_to_vec, DWG via write_to_writer over a Cursor).
- `io::download_web`: browser save dialog → write bytes (triggers a
  download).
- Save and Save As on wasm both go through a new `web_save` helper:
  serialize the active tab and offer a download (no persistent path or
  in-app save dialog on the web). Format follows the tab name's
  extension, defaulting to DWG. Result reported via `WebSaveResult`.

Native build + 75 tests unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 18:20:22 +03:00
Hakan Seven 1ab892c635 Merge pull request #105 from KarimJerbi/rectang-suppress-ortho
Temporarily suppress Ortho mode during RECTANG commands
2026-06-16 18:17:18 +03:00
Hakan SevenandClaude Opus 4.8 c49ad887b8 fix(wasm): make the web build actually run in the browser
With the wasm bundle building, get it rendering in a browser:

- Enable iced `webgl` so wgpu uses WebGL2 where WebGPU is absent
  (e.g. Firefox by default), and `fira-sans` so UI text has an
  embedded font (the web has no system fonts) — without it every
  label was blank.
- Replace `std::time::Instant` with `iced::time::Instant` (web-safe);
  `Instant::now()` panics ("time not implemented") on wasm and crashed
  at boot via the command line.
- Skip the batched-hatch pipeline on wasm: its bind group needs
  vertex-stage storage buffers, which WebGL2 lacks (validation error
  on "New drawing"). Fields become Option; upload/draw guard on them.
  Hatches don't draw on the web for now; everything else does.
- Fix the trunk feature flag (`data-cargo-no-default-features`) and
  ignore the `dist/` build output.

Native build + 75 tests unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:53:56 +03:00
Hakan SevenandClaude Opus 4.8 157d54a4d4 feat(wasm): single-window web entry + trunk harness (issue #45)
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>
2026-06-16 13:16:35 +03:00
Karim Jerbi a431e02378 Temporarily suppress Ortho mode during RECTANG commands 2026-06-16 09:53:32 +01:00
Hakan SevenandClaude Opus 4.8 7fe7e0230d refactor(scene): group modules into role-based subfolders
Move the 30 flat modules under src/scene/ into six folders by role:
convert (entity→geometry), text (fonts/shaping), model (render models),
pick (hit-test/selection/grips/xclip), view (camera/transform/render),
cache (block + property caches). pipeline/ is unchanged.

Full path migration — all `scene::X` references become
`scene::<group>::X`, no compatibility re-exports. render.rs items that
were `pub(super)` (parent was scene) become `pub(in crate::scene)` to
keep their original scene-wide visibility.

No behavior change; build and tests are green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:33:46 +03:00
Hakan SevenandClaude Opus 4.8 ec865a95aa feat(plugin): add a Plugin Manager window listing installed add-ons
Read-only inventory of the add-ons compiled into the build — name, version,
id, API level, description, command prefixes — built from the extracted
`ocs_plugin_api` manifest. Opened with the `PLUGINS` / `PLUGINMANAGER` command
or the Start-page "Plugins" button.

Adds `installed_manifests()` to the plugin registry and wires the window the
same way as the existing About / Shortcuts windows.

Part of #100 (phase-1 plugin manager UI stub).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 01:46:09 +03:00
Hakan SevenandClaude Opus 4.8 f7734b698f feat(ui): use the shared colour selector in properties, ribbon and layers
Replace the bespoke colour pickers with the shared color_selector / color_list
widget so colour selection is identical everywhere:
- left properties panel (selected entities' colour)
- ribbon Home-tab colour dropdown
- layer manager per-layer colour

Each routes its "More…" entry to the standalone Select Color window via a
ColorPickTarget (Properties / Ribbon / Layer). Extracted color_list as the
shared popup content; the colour selector button now fills its container.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:22:24 +03:00
Hakan SevenandClaude Opus 4.8 3d050fe1ab feat(ui): move full ACI palette to a separate "Select Color" window (#94)
The colour selector popup now lists only the named colours (ByLayer,
ByBlock, ACI 1-9) plus a "More…" entry. "More…" opens a standalone
"Select Color" window with the full 256-colour grid; picking there applies
to the originating field (dimension/multileader/table style) and closes
the window. Routed via a ColorPickTarget so one window serves every field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 11:14:14 +03:00
Hakan SevenandClaude Opus 4.8 8cdb17a6b4 feat(ui): shared colour selector used across all style editors (#94)
Add a reusable color_selector widget (ui/color_select.rs): a main-colour
dropdown plus a "more colours" button that expands the full 255-entry ACI
palette — the same design the properties panel uses. Replace the flat ACI
swatch rows added earlier in the dimension, multileader and table style
editors with it, wired through each editor's existing colour edit message
(the chosen colour is encoded as an ACI string) plus a per-field
palette-open toggle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:42:39 +03:00
Claude 5ba7101022 feat(app): silently register as a .dwg/.dxf handler so portable builds show in "Open with"
Installed builds already register a handler (MSI ProgIDs, the Linux
.desktop MimeType, the macOS Info.plist), but the portable .exe and the
AppImage register nothing, so they never appear in the OS "Open with"
list. Add a best-effort, idempotent runtime registration that runs
silently on a detached thread at startup:

  * Windows — writes HKCU\Software\Classes\Applications\OpenCADStudio.exe
    (open command + FriendlyAppName + SupportedTypes .dwg/.dxf), which is
    what lists the exe in "Open with → Choose another app".
  * Linux — writes a user-level .desktop pointing at the running binary
    ($APPIMAGE when set) and refreshes the desktop database. Skipped when
    a system package already provides the desktop entry, so we never
    shadow a distro install.
  * macOS — no-op; .app bundles auto-register with LaunchServices.

Unlike the default-app prompt this changes no defaults and shows no UI;
it only makes the binary discoverable as a handler.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 08:00:18 +00:00
Claude a7d85fe148 feat(app): first-launch prompt to set Open CAD Studio as default for .dwg/.dxf
On the first launch that hasn't answered it yet, the app shows a small
dialog offering to become the default handler for .dwg and .dxf files.
The choice is persisted (default_assoc_prompted) so the prompt never
reappears.

The platform plumbing lives in io/file_association.rs:
  * Windows — opens the OS's own per-app default-programs dialog via
    IApplicationAssociationRegistrationUI::LaunchAdvancedAssociationUI,
    passing the "Open CAD Studio" RegisteredApplications name the MSI
    registers. Modern Windows guards the actual default behind a
    UserChoice hash, so this is the supported consent path.
  * Linux — xdg-mime default against the installed .desktop file, whose
    MimeType entries already cover image/vnd.dwg and image/vnd.dxf.
  * macOS — LSSetDefaultRoleHandlerForContentType binds the DWG/DXF UTIs
    declared in Info.plist to the app bundle id.

The work runs on a dedicated thread (the Windows dialog is modal) with
the result delivered back through a oneshot channel and surfaced in the
command line.

https://claude.ai/code/session_01DFVSKgViFaxEjZD2KsTdtt
2026-06-12 07:50:41 +00:00
Hakan SevenandClaude Opus 4.8 9b1bd011ca feat(blocks): switch dynamic-block visibility states from a lookup grip
A block reference with a visibility parameter now shows a triangular
lookup grip at the parameter point. Clicking it opens a dropdown of the
named states (current one marked); picking a state toggles which of the
anonymous block's members are visible and rebuilds, so the reference
shows the chosen profile instead of being frozen on the saved state.

The state list is data-driven (owned strings from the file) and applying
it mutates other entities (the anonymous block's members), so it stays
separate from the static GripMenuAction grip-menu: a dedicated
VisibilityPopup and an app-level apply path. The anonymous block is a
parallel-ordered clone of its definition, so a member visible in the
definition's state maps to the same index in the inserted block; the
chosen visibility flags round-trip through the DWG writer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 20:11:19 +03:00
Hakan SevenandClaude Opus 4.8 de4eb31003 feat(styles): make style managers transactional — persist only on Apply
A style manager is now a staged transaction. When it opens, a snapshot of
the style tables, style objects and current-style pointers is taken. Every
New / Copy / Delete / Rename / Set Current / property edit mutates the
document live so the dialog previews the result, but the tab is not marked
dirty, nothing is pushed to undo, and the drawing is not rebuilt.

- Apply commits: one undo entry (restoring the pre-edit state), the tab is
  marked dirty, the drawing rebuilds, and the baseline is reset so further
  edits stage afresh.
- Closing the window without Apply discards: the opening snapshot is
  restored, so none of the changes persist.

Side effects (dirty / undo / bump_geometry) were removed from every style
edit handler and routed exclusively through the commit. Multiline's Apply
is a structural-only commit since its editor has no editable properties.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:38:32 +03:00
Hakan SevenandClaude Opus 4.8 f2dfd01795 feat(styles): unify the five style managers behind a shared frame
The text/dimension/table/multileader/multiline managers were hand-copied,
so their list CRUD kept drifting and breaking: Dimension's New created
nothing (#73), new text styles never reached the ribbon dropdown (#74),
and several were added with a NULL handle (dropped on DWG save, #67).

Collapse the duplicated parts into three shared pieces and leave only the
per-manager property editor bespoke:

- app/style_ops.rs: one StyleKind-dispatched CRUD layer (new / copy /
  delete / rename) handling handle allocation, ribbon sync, Table re-key
  and name-reference rewrites in a single place.
- ui/style_manager.rs: the window scaffold — uniform toolbar (New / Copy /
  Delete on the left, Set Current + Apply on the right), style list and
  chrome. Each manager now only builds its editor.
- ui/style_list.rs: the list row — single click selects, double click
  starts an inline rename.

New capabilities, now consistent across all five:
- Copy duplicates the selected style.
- Double-click a name to rename it inline.
- Right side is always Set Current + Apply (added Table set-current;
  MLine apply is a documented placeholder until its editor is editable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:03:34 +03:00
Hakan Seven 6a70e99dd5 Merge pull request #80 from mf4633/feature/plugin-host
Add plugin host (Phase 1)
2026-06-10 00:37:37 +03:00
Hakan SevenandClaude Opus 4.8 f2fd36b5fa feat(otrack): polar coordinates from tracking point (#69)
OTRACK alignment was computed in the wrong plane (it used Z as the
second axis while the model is XY / Z-up), so tracking never aligned
correctly and the aligned point was never fed to a pick.

Rework it: project the cursor onto a ray from a tracking point in the
XY plane — horizontal/vertical by default, or every POLAR increment
when POLAR is on. The aligned point is now used for both the live
preview and the committed click, and a dashed guide shows the ray.

While aligned, a typed distance places the point along the ray from the
tracking point, via the command line and the dynamic-input box (which
shows a single Distance field). The alignment clears with the command.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 23:38:08 +03:00
Michael Flynn 82993bc1db Add demo_plugin and plugin host integration tests
Minimal in-tree add-on (DP_HELLO) validates registry, ribbon merge, and try_dispatch. Five new lib tests; EntryKind gains PartialEq for test helpers.
2026-06-09 13:52:15 -04:00
Michael Flynn e46701456f Add plugin host architecture and Storm Sewer add-on package
Generic plugin runtime (src/plugin/, HostSession, per-tab plugin_state, command router). QGIS-style add-on layout with plugin.toml and single PluginRegistration. Storm Sewer refactored as reference consumer with dispatch.rs, catchments, LandXML, headless tests (43 passing). Core update.rs decoupled from storm_sewer via CadCommand object-pick hooks. Docs: plugin-architecture.md, plugin-template, PR and issue-78 reply drafts.
2026-06-09 13:41:21 -04:00