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
Replace the raw sentinel input with typed CAD lineweight options so special
values display correctly and unsupported values cannot be entered.
Refs #522
Hold edit leases and compare disk fingerprints before replacement so another editor cannot be silently overwritten.
Offer retry, reload, Save As, or explicit overwrite recovery.
Closes#498
- Block reference Name (Misc) is editable: the dropdown re-points the
insert to another definition; typing a new name renames the definition
everywhere (record re-key, Block marker, every INSERT reference).
Anonymous (*) and xref blocks stay read-only, with the same guards
added to Scene::rename_block.
- Custom single-control dropdown (text field + caret) built on the
shared floating_below overlay so the list always opens downward,
unlike iced's space-based menu placement.
- Consecutive "<Base> X/Y/Z" rows (Position, Scale, Start, Center, ...)
collapse into one expandable summary row across all entities; the
expand state persists across rebuilds and selection changes.
- The BEDIT block tab no longer offers the layout Rename/Delete
context menu.
Co-Authored-By: Claude Fable 5 <[email protected]>
Tool highlights used to stick: every click set active_tool, but only
interactive commands and a hand-maintained per-modal list ever cleared
it, so one-shots (Cleanup/AUDIT, view actions) and unlisted dialogs
(Customization/ALIASEDIT, CUI, Plugin Manager…) stayed blue forever.
Now the rule is mechanical: state toggles light from state only (the
click highlight drops immediately), interactive commands stay lit
while running, dialog openers stay lit until the modal closes (generic
clear replaces the per-modal list), and one-shots never keep it — the
dispatch site turns the highlight off when nothing stayed running.
On the Start tab, tools whose command the start gate refuses now render
dimmed (grey icon + label) across all button styles, dropdowns and the
quick-access strip; start_allowed() is the single shared authority.
Co-Authored-By: Claude Fable 5 <[email protected]>
The ribbon layer dropdown gets a live search field above the list
(cleared when the dropdown closes, panel height tracks the filtered
count), and the Layer Manager toolbar gets one too. Both filter by
case-insensitive substring while toggle/rename messages keep the
original row indices, so acting on a filtered row hits the right layer.
Co-Authored-By: Claude Fable 5 <[email protected]>
A thin divider after the Name header resizes the column by drag
(60-640 px, session-persistent), riding the shared modal drag flow.
The name elide budget follows the column width, so widening the
column reveals long layer names; the hover tooltip stays for overflow.
Co-Authored-By: Claude Fable 5 <[email protected]>
Deleting/renaming Standard was already blocked in the five style
managers, but a file saved without the built-ins (foreign or damaged)
opened with broken style dropdowns and nothing to reference — re-seed
missing Standard text/dim/table/mleader/mline styles on every open.
Every modal dialog now shows its name in the title bar: centred at
15px across the dialog width, with the move grip and close button
overlaid at the right edge. The bar takes an explicit width from the
caller — a Fill child inside the Shrink frame would blow the dialog
out to the full screen.
Co-Authored-By: Claude Fable 5 <[email protected]>
PURGE now repeats until a pass removes nothing, so definitions freed by
a purged block (nested blocks, layers, linetypes, styles) go in one run.
Reference scan keeps layer linetypes, ATTDEF/ATTRIB text styles, and
dimstyle DIMTXSTY + arrow blocks; the undo snapshot moves before the
first removal so one UNDO restores everything. Bare PURGE prompts with
clickable category options (aliases B/LA/LT/ST run directly), and
pinned prompts drop their "[A / B / …]" listing when the same options
render as buttons.
Co-Authored-By: Claude Fable 5 <[email protected]>
'>' button after the Command: label turns the leading-'>' literal-space
behavior into a mode: while on, Space stays in the line instead of
submitting, until toggled off. State persists in settings.json; a
hand-typed leading '>' still works for a single line.
Co-Authored-By: Claude Fable 5 <[email protected]>
Ribbon tooltips died the day the panels moved into CollapsePanels
(v0.7.6): its overlay() started with `self.open?`, so with no flyout
open — the normal state — it returned None and swallowed every child
overlay, and iced tooltips ARE overlays (#411). Buttons still hovered
and clicked, which is why only the tips were missing. With no flyout
the widget now collects the shown variants' overlays into a Group,
borrows split per panel slot.
Co-Authored-By: Claude Fable 5 <[email protected]>
Property edits on a hatch were invisible: fills render from the
prebuilt model cache, which move/copy refreshed but the properties
path never did — so a pattern-scale change (which must also rescale
the stored final pattern lines by the ratio) changed nothing on
screen (#415). invalidate_property_targets now rebuilds the edited
fill's cached model via Scene::refresh_fill_model.
The background row gains an on/off box: off removes the
HATCHBACKGROUNDCOLOR record entirely (the colour could never be
reverted before), on seeds ByLayer. The codec covers the full
colour-method range (ByLayer / ByBlock / RGB / ACI) and the backdrop
resolves ByLayer / ByBlock through the style chain. "More Colors…" on
the background picker now opens the palette window targeting the
background colour — it used to just close the picker.
Co-Authored-By: Claude Fable 5 <[email protected]>
The right-click menu was pinned to the screen's bottom-left corner.
Each layout tab now records its screen bounds through a PosReport
wrapper (which learned runtime-built ids for the dynamic tab names),
and the menu anchors next to the clicked tab via the shared
status-bar popup positioning, falling back to the left edge only when
no bounds were recorded (#428).
Co-Authored-By: Claude Fable 5 <[email protected]>
Route the MTEXT editor through the shared in-canvas modal frame with an
Apply-at-top toolbar (style-manager style): Apply commits and keeps the
editor open (creates on first apply, updates in place after), the ✕
cancels. Give every modal a bottom-right corner resize grip driven by a
shared grow-only `modal_resize` delta that resets with `modal_offset` on
open/close, so each dialog opens at its natural size and can be dragged
larger — style managers included.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Same hazard as the DYN guide in 48a43aa, in the three remaining dashed strokes:
a dash pattern is measured in pixels, so a path costs the tessellator a quad
every few pixels and its price is set by screen length. All three run from a
projected world point — an entity endpoint for the extension guides, the
acquired point for the OTRACK alignment line — which lands millions of pixels
away for a far or very large entity. That is millions of quads, and the process
fills RAM (#406).
The alignment line even said "the canvas clips it to the viewport" as the reason
extending it by 5000 px was safe. It isn't: the canvas clips after tessellating,
so every quad is built first. That assumption is what made these look bounded.
Clip in `clip_seg` (Liang–Barsky) before stroking, padded a dash period so a
guide still visibly leaves the frame. Unit-tested: a guide from 5,000,000 px
away comes back under 2000 px with its direction and its on-screen end intact,
a fully visible segment is untouched, and one that misses is dropped.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The dynamic-input guides are dotted, and a dash pattern is measured in pixels:
the tessellator emits a quad every few pixels, so a guide costs in proportion
to its SCREEN length, not to what it represents. Entering a large distance
(5000000 in the value box) projects the cursor millions of pixels from the
anchor, so the guide asks for millions of dash quads — gigabytes — and the
process fills RAM and dies while building a frame whose content is off-screen
anyway (#406).
Clamp the drawn length to just past the viewport diagonal. `dx`/`dy` still come
from the true cursor, and the angle and read-outs are computed from the raw
length, so direction and every displayed value are unchanged — only the tail
past the viewport edge, which nobody can see, is dropped. Clamping `cursor`
bounds every guide below it: they all derive from it, including the polar arc
and the perpendicular endpoint.
The reporter's own bisect points here: with DYN off the crash does not happen,
and drawing the same line by command (which builds no interactive guide) never
reproduced it.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Make objects genuinely annotative — carrying real per-scale
representations that interoperate with other CAD apps — rather than just
a native flag. `create_annotation_context` synthesizes the extension-dict
chain (AcDbContextDataManager -> ACDB_ANNOTATIONSCALES -> per-scale leaf)
from the entity's placement; the editable Annotative toggle now covers
Text and block references as well as MText/MLeader; and a new
"Annotation Object Scale" dialog (OBJECTSCALE) adds/removes an object's
per-scale memberships. Requires the acadrust ObjectContextData encoder
(Cargo.lock bump).
Also fixes a render bug where objects carrying an *empty*
ACDB_ANNOTATIONSCALES (a single-representation marker with no per-scale
reps) were treated as annotative and (mis)scaled by the annotation factor
in scaled paper viewports, ballooning the text: an object is now
annotative-by-context only when its scale collection is non-empty, so
such objects render at their base geometry.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The "Dim line color" row on Leader and Dimension entities was
read-only: an earlier attempt wired it to Leader.override_color, which
acadrust never serialises, so the pick was lost on save. Store it
instead as a standard ACAD_DSTYLE per-object dimension-style override
(DXF code 176, an ACI index) through the existing dim_override codec,
so it round-trips through both DWG and DXF like the other dim
overrides. RGB picks collapse to the nearest ACI, matching the rest of
the dim-colour stack (dimension styles are index-only through the file
layer). The renderer prefers the override over the style's DIMCLRD.
The write branch is guarded to leaders and dimensions so a mixed
selection cannot stamp the override onto other entity types.
Bumps acadrust to c9fe982, which carries parsed XDATA onto dimensions
on DXF read (it was dropping common.extended_data), so the dimension
override persists on DXF save as well as DWG.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Double-click rename did nothing and ratio edits were lost when switching
scales — and on drawings saved by other programs nothing could be edited
at all.
- Fallback scales were virtual (no Scale objects), so edit/rename no-oped;
materialize the standard set into real staged objects when the manager
opens, reverted on close unless applied.
- On third-party DWGs the mutators walked the root named-objects dict ->
ACAD_SCALELIST, which can be unreachable, and matched dictionary keys
that are internal ids (*A1, A0) rather than scale names. Locate the
scale-list dict robustly and match scales by object name + handle.
- Rename rows inline via double-click; repair a dangling current-scale
header pointer on staged rollback.
- Stage text/mleader style-manager edits on switch so they persist like
the scale manager.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The scale manager now runs as a transaction, matching the style managers'
staging: New starts a blank editor draft (nothing is added to the drawing
until Apply), Delete removes immediately but staged, and closing the window
without Apply restores the snapshot taken on open — so a scale created or
deleted mid-session is discarded on cancel. Apply commits the staged list
changes as a single undo entry. This replaces the earlier behaviour where
New copied the selected scale and edits landed immediately.
A `changed` flag guards the commit so a no-op / failed Apply (blank or
invalid fields, or an edit that didn't apply) no longer dirties the drawing
or pushes a phantom undo entry. Set Current is deliberately outside the
transaction — it takes effect immediately like the scale pill (and syncs
CANNOSCALEVALUE), so it is never rolled back on close. Renaming the current
scale follows the pointer; editing one of the built-in fallback scales (shown
when the drawing has no scale list) materialises it as a real scale; the
snapshot restore touches only the ACAD_SCALELIST root entry.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Replace the scale popup's inline delete/add affordances with a single
"Manage scales..." row that opens a proper manager, matching the style
managers: it reuses the shared style-manager palette and chrome (toolbar +
list + editor frame), so it looks and behaves like the Text / Dim / MLeader
managers.
The manager lists the drawing's annotation scales (name + ratio, the current
one checked) and edits them through a Name / Paper units / Drawing units
editor: New adds a scale from the fields, Apply renames / re-ratios the
selected one, Delete removes it (never the current scale), and Set Current
makes the selected scale the model-space annotation scale. Backed by
Scene::add_scale / remove_scale / edit_scale on ACAD_SCALELIST.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The drawing's annotation-scale list (ACAD_SCALELIST) is now editable, so the
set of scales offered by the status-bar scale pill can be grown or trimmed:
- Scene::add_scale / remove_scale create or drop a Scale object and its
SCALELIST dictionary entry (creating the SCALELIST dictionary, and
registering it in the root, for drawings that carry none).
- SCALELISTEDIT gains ADD <ratio> / DELETE <name> alongside the existing
listing (e.g. `SCALELISTEDIT ADD 1:50`).
- The scale popup (model space) grows a delete affordance on each non-active
scale and a "+ Add scale..." row that primes the command line. The current
annotation scale is protected from deletion.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The UCS Icon, Properties, File Tabs, and Layout Tabs buttons were highlighted unconditionally because their ids fell through to an active_tool equality check in is_active_tool, which activate_tool sets on every click and never clears. Each id is now mapped to its corresponding visibility flag (show_ucs_icon, show_properties, show_file_tabs, show_layout_tabs), and those flags are kept in sync from their toggle handlers so the highlight reflects live state. The per-toggle booleans previously threaded through the render path are consolidated into a single ToggleState struct read from one place, so introducing a further toggle no longer requires changing every render-function signature and call site.
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
External PR by Karim Jerbi (@KarimJerbi).
- NAVVCUBE is a command toggle so it never set active_tool; add a
show_viewcube flag to the ribbon and special-case is_active_tool so the
button highlights only while the cube is on.
- show_viewcube was absent from render_signature, so toggling a cached
view produced an identical signature and left stale cube pixels; hash
it into the signature so the toggle invalidates the cache and clears.
Replace the custom in-app file-browser save dialog with the native OS
save dialog for choosing the destination path — folder browsing and
overwrite confirmation now come from the OS. A compact in-app modal
still selects the DWG/DXF format and version (a native dialog cannot
carry the version year) and appears only for Save As. Plain Save on an
unsaved drawing defaults to DWG 2018 with no picker. The file-name field
is shown only on web, where there is no native dialog.
Remove the hand-rolled directory listing, folder navigation, and
overwrite modal.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
NAVVCUBE is a command toggle, so it never set active_tool and is_active_tool never matched it. Add a show_viewcube flag to the ribbon (mirroring wireframe/ortho_mode) and special-case "NAVVCUBE" in is_active_tool to return the live show_viewcube state; ToggleViewCube now calls ribbon.set_viewcube.
Two narrow-mode (e.g. phone-width) ribbon fixes; both are no-ops when the
row has room, so desktop layouts are unchanged.
- Tab row: let the inter-tab gap squeeze from 6px down to -12px, tucking
neighbouring tabs into each other's 14px side padding (labels never
overlap) so all tabs stay on one row instead of wrapping "Manage" onto a
second line. The gap only goes negative as far as needed to fit.
- Tool panels: at the tightest collapse step each panel is now a single
non-running button — the panel's first tool icon + title + a down arrow
that opens the tools flyout. No tool runs directly at this density; you
pick one from the dropdown.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
App preferences were spread across six flat plain-text files (settings.txt,
recent.txt, recent_limit.txt, statusbar.txt, ribbon.txt, plot.txt), each with a
bespoke line parser and no grouping. Replace them with a single grouped JSON,
serialized via serde (enabling the derive feature; serde_json was already a
dependency).
- New app/config.rs: AppConfig { settings, recent, statusbar, ribbon, plot } →
<config>/settings.json (pretty JSON), load()/save().
- Make UserSettings, StatusBarConfig/StatusPill, CollapseMode and
PlotDialogState serde-derived; #[serde(skip)] marks the plot dialog's runtime
fields so only print preferences persist. Their bespoke .txt load/save is
removed.
- App gains current_config/apply_config/save_config: one AppConfig::load at
startup distributes into live state; every settings-change site
(persist_settings_if_changed, recent add/remove/limit, status-bar pill
toggle, ribbon density, plot commit) routes through save_config, which diffs
against the last write to avoid thrashing.
- Clean start: the old flat files are ignored (no migration). ocad.pgp stays a
separate hand-editable file.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The Coords pill always showed live absolute X,Y,Z, ignoring the drawing's
$COORDS (coords_mode) sysvar. Drive the readout from it:
- 0 static — the last picked point; the readout freezes between picks.
- 1 live — continuous absolute X,Y,Z (previous behavior, default).
- 2 polar — distance<angle relative to the last point while a command is
prompting for a point; absolute otherwise.
The cursor and last point are reported in the active UCS, matching the rest of
the readout. Clicking the pill cycles the mode 0→1→2→0 (CycleCoordsMode), marks
the tab dirty and echoes it; SETVAR COORDS still works. $COORDS round-trips per
drawing via acadrust's coords_mode.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
The status bar showed all 17 pills out of the box. Hide the informational /
niche ones by default so the bar stays uncluttered — Coordinates, Show
Lineweight, Dynamic Input, Model/Paper Space, Drawing Units, Show
Transparency, Selection Cycling and Viewport Count. The user turns any of them
back on from the customization (gear) menu, which persists to statusbar.txt.
The main drafting toggles (Ortho, Polar, Otrack, Osnap, …) stay visible.
An existing statusbar.txt (even empty = user showed everything) is
authoritative; only a fresh install with no config gets the new defaults.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
When the viewport got too narrow the top-left render-mode bar and the
top-right ViewCube overlapped. Hide them adaptively, measuring the render
bar's real width rather than guessing:
- Wrap the render bar in DensitySwap, which measures its natural width every
frame and swaps it for an empty spacer once it no longer fits the tile. Add
report_width0 so it publishes that measured width to render_bar_w.
- Compute viewcube_visible from that measured width: the cube shows only while
bar_w + gap + cube region fits. This one flag drives the ViewCube nav/UCS
widgets, the hover hit-test, and — threaded through build_viewports /
build_viewport_for_pane / viewport_data_for — the GPU cube itself, which
previously ignored the pane flag (show_viewcube was inst.active only), so
the cube stayed while its surrounding widgets vanished.
Result: the ViewCube gives way first, then the render bar once it too has no
room — no overlap, sized to the bar's real width (which varies with the
render-mode label, split buttons and tile count).
Co-Authored-By: Claude Opus 4.8 <[email protected]>