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>
Move the dependency-free, semver-versioned half of the plugin contract into a
standalone workspace crate `crates/ocs_plugin_api`:
- manifest types: PluginManifest, ApiVersion, API_VERSION
- ribbon vocabulary: CadModule trait + ToolDef/RibbonGroup/RibbonItem/IconKind/
ModuleEvent/StyleKey
The host re-exports them from `crate::plugin::manifest` and `crate::modules`, so
every existing call site is unchanged. The acadrust-typed runtime surface
(HostSession) stays in the host binary; lifting it behind a HostApi trait in the
same crate is the remaining phase-1b step.
Part of #100. Docs updated in docs/plugin-architecture.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
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
Phase 4.1. Handle / u64 keys dominate the hot maps (block_defn,
hatch / image / mesh caches, draw-depth map, viewport wire caches);
the default SipHash is wasted work on integer keys. Alias the std
types to rustc_hash::FxHashMap / FxHashSet crate-wide so every map and
set — including the cross-module draw-depth map shared between scene
and the GPU pipelines — uses the faster hasher consistently.
Mechanical: no behavioural change. Constructors moved from ::new() to
::default() (Fx variants carry no RandomState). Build + 13 tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read the model-space annotation scale from the standard CANNOSCALE /
CANNOSCALEVALUE header variables instead of a user-variable hack, and
populate both the status-bar scale picker and the viewport Scale
property purely from the drawing's ACAD_SCALELIST — no built-in
fallback set. Xref-derived scales (`_XREF` suffix, `name|` prefix)
are filtered out so only the host drawing's own scales appear.
Bumps the patched acadrust to the commit that adds CANNOSCALE/
CANNOSCALEVALUE read/write.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
vtkio (via truck-meshalgo → xz2 → lzma-sys) linked the system liblzma
dynamically, baking a Homebrew path (/opt/homebrew/.../liblzma.5.dylib)
into the macOS binary. The .app then crashed on launch on any Mac
without that exact library. Enable lzma-sys's `static` feature so
liblzma is compiled from the bundled source into the binary, making it
self-contained on every platform (verified: no dynamic liblzma link).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename the Home tab to "Draw" and add a Model tab beside it with two
groups:
- Model: BOX / CYLINDER / CONE / SPHERE / WEDGE / TORUS, placed with a
footprint + height. Each is built as a real ACIS Solid3D (acadrust
primitive builders) plus a truck B-rep, tessellated into the shaded
mesh pipeline, with edge wires for picking + wireframe.
- Design: UNION / SUBTRACT / INTERSECT via truck-shapeops.
A session-only Scene.model_solids cache holds each solid's truck B-rep
so the boolean tools can combine and chain them. Supersedes the old
command-line BOX/SPHERE/CYLINDER (insert::solid3d_cmds keeps EXTRUDE/
REVOLVE/SWEEP/LOFT).
Known limitations: booleans need solids created this session; curved
ACIS primitives render in-session but not after reload (the SAT
tessellator handles only planar caps); geometry assumes world_offset 0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Type directly into the rendered MText preview: click to position the
caret, insert/delete, Enter for a new line, Space for a literal space,
and arrow keys to move. The caret blinks when idle.
Keep the trailing empty paragraph in the editor so the caret appears on a
fresh line immediately after Enter, before any typing. parse_mtext_paragraphs
split into a _ex variant; layout_mtext skips the blank-edge trim when
emitting glyph boxes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Type-as-you-go suggestion popup above the input that filters known
command names by substring (so `LEADER` surfaces both LEADER and
MLEADER). Up / Down navigates the list, Enter dispatches the
highlighted entry, clicking a row dispatches directly. Esc on a
non-empty input clears the buffer instead of escalating to
command-cancel.
The name corpus comes from `inventory::submit!` blocks placed next
to each `CadCommand` impl rather than a hand-maintained static list.
Adding a new interactive command means dropping one `inventory::submit!`
line in its own module file — the autocomplete picks it up at the
next compile. 98 submissions seeded across 60 module files;
`command::all_registered_command_names()` flattens them at runtime.
One-shot dispatch arms in `app/commands.rs` (NEW, OPEN, SAVE, UNDO,
…) don't have a `CadCommand` impl and are therefore absent from
autocomplete by design.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bumps bytemuck 1.14->1.25, glam 0.27->0.33, rfd 0.15->0.17,
windows-sys 0.59->0.61, ureq 2->3. ureq 3 changed the agent API
and feature flags, so update_check.rs switches to Agent::config_builder
and body_mut().read_to_string(), and the feature toggles from tls
to rustls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Joining the OpenAEC Foundation (open-aec.com). Crate/struct become
OpenCADStudio, user-facing strings use the spaced "Open CAD Studio"
form, repo URLs point to HakanSeven12/open-cad-studio. Logo (window
icon + assets/logo.svg) redrawn with OCS initials. Drop examples/
binaries — unused.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
acadrust's DWG reader read the wipeout/raster-image clip boundary
points only to advance the bit cursor and then discarded them, so every
wipeout fell back to `Wipeout::new()`'s default placeholder rectangle
(`-0.5..0.5`). For files that store the real polygon mask in clip
vertices (anteen.dwg's `DIMBLOCK` style: u_vector ≈ 643k, size = (1, 1),
clip polygon ~0.0016×0.0033 in pixel space), the wipeout rendered at the
full image bbox — hundreds of thousands of units wide — instead of the
intended ~1000×2000 mask.
Pinned to the new `fix/wipeout-clip-vertices` acadrust branch (carries
the upstream parser fix). On the H7CAD side, the polygon mapping now
follows the DXF convention:
x_off = (clip.x + size.x/2) × u_vec
y_off = (size.y/2 − clip.y) × v_vec (image-Y points down)
…in `wipeout_boundary_2d` (fill), `Wipeout::to_truck` (border line),
and `Wipeout::grips` (vertex handles) so all three stay in sync. The
fill path also explicitly closes the polygon loop — without it the GPU
`in_polygon` ray-cast skipped the v(n-1)→v(0) edge and the solid mask
bled far past the boundary.
Co-Authored-By: Claude Opus 4.7 <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>
The acadrust fix from https://github.com/hakanaktt/acadrust/pull/27
unblocks files with corrupt hatch boundary-handle counts (anteen.dwg
went from >100 s "stuck" to ~0.7 s release). Until the PR lands on
upstream/main, [patch.crates-io] points at the fork branch carrying
that commit; the comment in Cargo.toml notes the revert.
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>
Three changes to make paper-space viewports render correctly on
UTM-scale drawings whose viewports were saved with default
view_target=(0, 0, 0) and a view_center pointing at empty WCS:
1. **Auto-fit fallback.** When the saved view's WCS rect doesn't
overlap the IQR-bounded content cluster (`world_offset ±
local_extent_max`), override `view_center` with `world_offset` and
recompute `view_height` to fit the cluster into the paper viewport.
Matches AutoCAD's silent auto-fit-on-open for stale / uninitialized
viewports; without it those viewports rendered blank.
2. **f64 projection inner loop.** The previous f32 path computed
`(wire_offset_rel - target_offset_rel).dot(view_right) -
view_center` by subtracting values at ~5e6 magnitude (f32 ULP
~0.5 m) to land on a small paper offset. The cancellation produced
centimetre-scale jitter on paper output even when the model was
clean. Reconstruct the wire WCS coord, subtract the display center,
and dot-project in f64; cast to f32 only at the final paper
position where magnitudes are bounded by the viewport rect.
3. **examples/inspect_viewports.rs** — diagnostic that dumps every
viewport's saved view fields (view_target, view_center, view_height
vs paper height, custom_scale, status flags) plus drawing-level
insertion_units and model-space extents. Used to verify that
acadrust's DWG reader reads view_height correctly; the
`view_height == vp.height` patterns observed on real files turn out
to be genuine "default 1:1" file content rather than a reader bug
(a round-trip test through DwgWriter / DwgReader survives all
per-viewport values).
Also: switch the `acadrust` patch from `HakanSeven12/acadrust@feat/
expose-blockrecord-is-loaded` (a stale branch with the abandoned
is_loaded experiment) to `hakanaktt/acadrust@main`. Upstream main
now carries the layout paper-dimensions and mirrored-arc fixes that
this branch used to add — plus PR #20's DXF reading fixes that the
old branch was missing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When the host DWG marks an xref as Unloaded (via XREF→Unload in
AutoCAD), the user explicitly chose NOT to load that reference. We
were ignoring that and pulling the external file in anyway, which
on large drawings could push GPU memory past the device budget.
Bump the acadrust dep to the branch that exposes
`BlockRecord::is_loaded`, then short-circuit `resolve_xrefs` for
any entry with `is_loaded == Some(false)`. New `XrefStatus::Unloaded`
surfaces this in the command-line log so the user can see which
references were skipped (and can re-load them via the XREF dialog).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Old fork was deleted and re-forked from upstream; the mirror-handedness fix
is now isolated on fix/mirrored-arc-handedness with no other diverging work.
Mirrored INSERTs reverse OCS handedness, but acadrust's explode used to
keep the original normal — making CCW-around-normal arcs and ellipses
sweep the wrong way. The fix lives in acadrust 7c711e9, which flips the
new normal when det < 0 and reprojects center/major_axis/angles into the
new OCS. H7CAD's fix_mirrored_arc workaround is no longer needed.