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>
The text-to-line gap used a raw DIMGAP while text height already scaled
by DIMSCALE, so dimension text was misplaced whenever DIMSCALE != 1.
Thread dim_scale into dimension_text_pos_f64 and text_fill_rect and scale
DIMGAP the same way as DIMTXT.
Closes#94
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tracking and polar pickup used the full crosshair-arm radius (60 px)
while OSNAP used the smaller ¼ radius, so the vector-intersection lock
grabbed over a much larger area than object snaps. Collapse to a single
osnap_radius_px shared by OSNAP, tracking, polar and extension; the
crosshair keeps following the cursor and the point snaps on contact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When two tracking vectors were active, clicking placed a free point on
one of them instead of their crossing. otrack_snap now builds the active
rays (OTRACK rays from tracking points, plus POLAR rays from the last
point) and, when the cursor is near the crossing of two vectors from
different origins, locks onto that intersection. Covers OTRACK×OTRACK and
POLAR×OTRACK; single-vector alignment is unchanged when no crossing is
near.
Closes#111Closes#112
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A tracking point was created as soon as a snap point was highlighted,
so moving the mouse across the view spawned accidental tracking points.
Acquisition now requires the cursor to rest near the snap point for
250ms (wall-clock) rather than a fixed number of move events.
Closes#110
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was no way to drop an acquired OTRACK point short of ending the
command. Dwelling the cursor over an existing tracking point now toggles
it off instead of re-acquiring it.
Closes#109
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
OSNAP registered snapping over the full crosshair-arm radius (60 px),
making it too easy to snap and leaving little room to click just outside
a snap point. Split out a dedicated osnap_radius_px (¼ of the arm) used
only for object-snap matching; tracking, polar and extension pickup keep
the larger snap_radius_px.
Closes#108
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show a centered spinner + title until the app's canvas mounts, then fade
it out — the wasm bundle takes a moment to download and start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add an "OCS Web" button (bright ribbon-blue, desktop only) to the Start
page that opens the web build at hakanseven12.github.io/OpenCADStudio.
- Send Feedback now opens the GitHub issue with the body pre-filled:
app version + platform (OS/arch on desktop, browser user-agent on web),
via sys::platform_info + sys::percent_encode.
- Remove the redundant About button from the Start page (ABOUT command /
menu still opens it).
Native + web build; 75 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add .github/workflows/pages.yml: on every published release (and manual
dispatch) it builds the web bundle with trunk
(`--public-url /OpenCADStudio/`, no-default-features so no solid3d/C
deps), adds .nojekyll, and publishes dist/ to GitHub Pages. No COOP/COEP
needed since wasm runs single-threaded.
Enable via repo Settings → Pages → Source: GitHub Actions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- Add DONATE to the Start-tab command allowlist; it was blocked there
with "No drawing open" while REPORT/CHANGELOG/ABOUT were allowed, so
the Donate button did nothing on the welcome screen.
- Implement sys::open_url on wasm via web_sys window.open(_blank) — it
was a no-op stub, so Donate, Release Notes and Send Feedback opened
nothing on the web. The click is a user gesture, so the pop-up blocker
allows it.
Native + web build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Closing a dialog as an OS window used to discard a style editor's
un-applied (staged) changes and de-highlight the ribbon tool that
launched it. The in-canvas modal ✕ (CloseModal) lost that, so a Cancel
kept staged edits and left the button stuck active.
Add `close_active_modal`: it runs `style_stage_discard` for the style
editors and deactivates the matching ribbon tool by ModalKind, then
clears `active_modal`. CloseModal and the dialog close handlers route
through it. Apply still commits (re-baselined), so Apply-then-✕ keeps
the change.
Native + web build; 75 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Text Style editor gained a system-font (TTF) column in the Plan B /
web-fonts work, so its content no longer fits the old 620px width. Size
the modal to 860×480 so all panels show.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
Wire up file open for the web build:
- `io::pick_and_load_web`: browser file picker → read bytes → parse via
`load_bytes` → build caches, returning the same payload as the native
loader so it feeds the shared `Message::FileOpened` handler.
- `Message::OpenFile` cfg-dispatches: native picks a path then loads on a
worker thread; web picks + parses in one async step. The web branch also
sets `opening` (the handler drops results when no open is in progress).
- Disable the cosmic-text shaping / fallback paths on wasm: the web has no
system fonts, so cosmic-text panics with "no default font found". LFF
stroke fonts (embedded) still render; LFF-missing glyphs are skipped.
Native build + 75 tests unchanged. Hatches still don't draw on the web.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Add a cross-platform `io::load_bytes(name, bytes)` that parses DWG/DXF
from an in-memory buffer via acadrust's stream readers (Cursor), picking
the format from the name's extension. This is the foundation for opening
files on the web, where the browser delivers bytes rather than a path.
Native keeps the streaming `load_file` (lower memory on large drawings);
both share the post-load fixups. Builds on native and wasm.
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>
The Home tab was already titled "Draw"; rename the module to match:
folder modules/home → modules/draw, HomeModule → DrawModule, id "home"
→ "draw". The inner draw/ command folder is kept (modules::draw::draw).
Update build.rs PRIORITY ("home" → "draw") so the tab returns to the
front of the ribbon; registry.rs regenerates accordingly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the `*_model` naming of the sibling render models (wire_model,
hatch_model, mesh_model, image_model). Also realign the related Scene
API for consistency: model_solids → solid_models, add_model_solid →
add_solid_model, register_model_solid → register_solid_model.
No behavior change; build and tests are green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Add a TrueType/OpenType render path alongside the LFF stroke engine so
users can pick fonts installed on their machine for text and MText.
- Discover system families via fontdb (sysfont) and list them in the
text-style picker.
- Extract glyph outlines with ttf-parser, flattened into wire strokes
normalized to the same 9-unit cap-height space as LFF glyphs.
- Shape runs with cosmic-text (ligatures, Arabic joining, kerning, bidi)
plus automatic font fallback: a glyph missing from the chosen family
is taken from another installed font.
- Unify both sources behind a single Face type; a shared run tokenizer
drives the stroke (per-glyph) and shaped (TTF) renderers identically.
- Stroke fonts now fall back to a system TTF (cosmic-text picks it) for
characters no LFF font covers, replacing the unicode/iso3098 chain.
Also trim the embedded LFF set from 47 to 26, keeping the AutoCAD-SHX
equivalents and dropping LibreCAD-only fonts now covered by TrueType.
LFF builtins keep priority over same-named system fonts so DXF text
renders identically across machines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
submit() uppercased the entire typed line, mangling case-sensitive arguments
(file paths on case-sensitive filesystems, identifiers, plugin command args) on
Linux/macOS. Uppercase only the first token; verbs still match and each
sub-command handler already re-uppercases its own keywords.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows installer:
- Optional Desktop shortcut feature (default on, deselectable) via a switch to
the WixUI_FeatureTree UI.
- Refresh the shell after install (ie4uinit -show) so .dwg/.dxf icons and
associations appear without a reboot.
- Tidy the Add/Remove Programs entry: ARPNOREPAIR plus contact/update/comments
metadata (Modify stays enabled for the feature tree).
Release workflow:
- Publish SHA256 sidecars for the AppImage, portable .exe, MSI and .dmg.
Linux:
- Ship an AppStream metainfo.xml for software-centre integration.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Windows executable carried no icon, so Explorer, the taskbar, the
Start-menu tile and file-association entries showed a generic icon. Embed
AppIcon.ico via winresource in build.rs, and generate the .ico from the logo
before the build (it was produced after the build, too late to embed). Local
and non-Windows builds skip the step when the .ico is absent.
Fixes#107.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Properties color dropdown stayed open after deselecting, reselecting, or
clicking into the viewport. refresh_properties carried color_picker_open across
rebuilds, so a selection change never closed it; and a viewport press starts a
box selection, so it only closed on the second click. Stop preserving the
picker state across rebuilds and dismiss both color dropdowns on the first
viewport press.
Fixes#104.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The recent-file buttons dispatched the open command without closing the app
menu, so it stayed open over the loaded drawing. Use CloseAppMenuAndRun so the
menu closes first, matching the other menu entries. Also commits the Cargo.lock
update from dropping iced's debug feature.
Fixes#103.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `debug` feature on iced installs a built-in F12 devtools hook that opens
the comet debugger (and prompts to install it when missing). That intercepted
F12 before OCS could toggle dynamic input. Drop the feature so F12 reaches the
app's own handler.
Fixes the F12 part of #101.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>