Three attack agents (state-machine tracing, executable input fuzzing,
browser-API review) found two breaks and several degradations; all are
fixed here, with the fuzzer's 22 tests kept as regression suites.
Breaks:
- Untitled tabs (and dot-names like ".dxf") autosave as DXF, but the
restore name defaulted to .dwg — the worker dispatches its reader by
the name's extension, so the only copy of unsaved work could never
be restored and was re-offered forever. recovered_name now uses the
worker's rsplit('.') convention and defaults extensionless names to
dxf, matching new-document serialization.
- " (recovered)" stacked one suffix per crash-restore cycle; the
marker is now stripped idempotently before appending.
- Opening a file (Ctrl+O / OPEN / automation) while a web open was in
flight clobbered it: the in-flight open's bytes were lost and every
queued restore prompt stalled for the session. on_open_file now
refuses while an open is running (web only).
Hardening:
- Escape on the Recover Drawing modal defers instead of deleting the
only snapshot; the Discard button still removes it.
- A cap-rejected restore enqueue warns in-app and re-offers the next
snapshot; the drop queue cap rises to 32.
- Exiting mid-round no longer races snapshot removals (deferrals are
drained, the sweep skips while a round is in flight); closing a tab
with "Don't save" now also removes its web snapshot; the snapshot
key derivation is shared via web_autosave_key.
- The plot-preview overlay no longer leaks a full PDF copy per preview
(the Download closure lives in a replaceable registry).
- A corrupt autosave index.json fails the round/boot instead of
silently resetting every row; plot pages with non-finite or non-
positive sheet dimensions error instead of emitting a NaN MediaBox.
- The drop filter accepts dot-less "DWG"/"dxf" names the worker
parses (same rsplit convention on both gates).
Second review pass found the restore marker matched WebFileOpened by
display name: any same-named open finishing while the recovered one
was still queued deleted the only copy. The snapshot key now travels
with the queued file (tagged queue entries), the marker records the
open id assigned at dispatch, and success, failure, and cancel all
match that id. Cancelling an unrelated open no longer clears a
pending restore marker.
Also: the boot scan removes dead index rows (missing or zero-byte
snapshot files) that no other path could discard, and the drop
listener install only latches when the listeners really registered.
Review of the drop-open and autosave commits surfaced one blocker and
five majors; all are fixed here, and two claims in the autosave commit
message are corrected by this one: tab close does NOT clear a snapshot
(matches native .sv$ semantics — saving, restoring, discarding, exit,
and the capped boot scan do), and serialization runs on the main
thread between awaits, not off the paint path.
- The restore prompt can no longer appear with no entries (empty
"Recover Drawing" modal on every boot once the autosave dir exists).
- Restore no longer consumes the snapshot before the open succeeds:
the recovered drawing goes through the guarded drop queue, and the
snapshot is deleted only after the tab opens; cancel or parse
failure keeps the only copy.
- Saving during an autosave round defers its snapshot cleanup until
the round finishes, so a stale round can no longer resurrect a
snapshot newer than the save.
- The boot scan no longer deletes snapshots by mtime comparison
(re-opening a drawing refreshes the recent copy's mtime, which
silently destroyed wanted snapshots); GC is now a 16-entry cap with
oldest-first eviction.
- OPFS write failures abort the writable stream (a leaked lock could
stall the same-key writer and disable autosave for the session), and
DOMException errors now carry their name/message.
- Empty (crash-window) snapshots are never offered; an unreadable one
warns and stays discardable instead of looping forever.
- Drop-open polish: reads start inside the handler and complete in
drop order (legacy WebKit safety, deterministic multi-file order),
dragenter is canceled too, the install guard only latches on
success, non-drawing drops don't steal propagation, a lock-poison
panic can't cross into JS, and re-dropping an open drawing no
longer risks clobbering an in-flight open (it re-queues).
- The model-space sheet floor is per-axis (a thin 8x400 mm profile
keeps its length) and centers content on the enlarged page.
The web build had no autosave: on_autosave was a no-op and the
subscription was native-only. Both gates are gone; every savetime tick
now serializes each dirty tab (same pre-serialize sync as the web save
path, no thumbnail, serialized serially off the critical paint path)
into a dedicated opencadstudio-autosave OPFS directory, keyed by
drawing name, without touching the dirty flag, downloads, or recents.
On boot the web app scans that directory and offers any snapshot
newer than the last saved copy in a localized Recover Drawing modal
(Restore opens it as "name (recovered).dwg" through the standard
open pipeline; Discard removes it; Escape discards). Saving or
closing a drawing clears its snapshot; the boot scan also garbage-
collects stale entries. run_web now requests navigator.storage.persist
so settings and OPFS data survive browser eviction.
STLOUT/STEPOUT wrote via std::fs and IMPORTOBJ read via std::fs, all
of which fail on wasm32. The export pickers now mirror the PDF path
(pick_*_owned returns a name-only PathBuf; the browser download names
the file) and the write tail swaps fs::write for sys::download_bytes.
OBJ import reads the picked file's bytes directly, like the web open
picker.
Opening by drag-and-drop was dead on the web build: winit never emits
FileDropped there. A wasm-gated io::web_drop module installs DOM
dragover/drop listeners, filters .dwg/.dxf, reads the File bytes
async, and queues them; the existing 50 ms PollWebControl tick drains
the queue into the same open path the picker uses (progress overlay,
recovery, OPFS recent cache).
Also floors the model-space plot sheet: extents smaller than 10 mm
(or the 0x0 paper a fresh unsaved drawing carries) now fall back to
the standard sheet instead of producing a ~3 pt page.
- plot dialog: the web build defaults to 'Save to PDF file...' (its only
output choice); native dropdowns and defaults are untouched.
- on_plot_dlg_commit forces to_file on wasm so a stale page setup can no
longer route the web build into the (unreachable) printer path; both
preview branches now go through preview_page_pdf, which is identical to
the old inline temp-PDF + OS-viewer flow on desktop.
- Print ribbon / PRINTALL on wasm render the PDF and open it in a tab
instead of erroring; PrintAllPdf picks a virtual path and downloads.
- pdf_plot_options maps the dialog's Quality dropdown into PdfPlotOptions.
Constraint delete: a Properties-panel row delete button, viewport
click-to-select on a constraint glyph pill with Delete-key support, and a
right-click "Delete Constraint" menu item — all routed through one
undoable Scene::selected_constraint-aware path.
Fixes two named-parameter bugs found while testing that feature:
- DIMCONSTRAINT: a parameter named like a mode-switch keyword ("r", "d",
"a", "x", "y") could never be referenced, since the keyword check ran
before the parameter-name check.
- PARAMETERS modal Apply rebuilt Scene::named_parameters but never
refreshed the Properties panel, leaving renamed/deleted rows stale
until an unrelated refresh (e.g. a selection change) happened to occur.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A day of pan/zoom that had suddenly become unusable on a 1.2M-entity
drawing turned out to be no regression at all: an unattended NVIDIA
userspace update had been applied without a reboot, the Vulkan ICD could
no longer talk to the still-loaded kernel module, and wgpu silently fell
through to Mesa's llvmpipe. Nothing in the app said so. iced picks the
adapter and names it only under RUST_LOG, so the only evidence was 32
`llvmpipe-N` threads in /proc. The same silence covers WARP on Windows,
SwiftShader in browsers, and the case where wgpu finds no adapter at all
and iced quietly draws the chrome with tiny-skia while the shader widget
is dropped, leaving a blank viewport. This commit makes all of that
visible, on every platform, without new dependencies.
Detection (src/scene/pipeline/mod.rs)
-------------------------------------
`GpuStatus` is derived from two signals:
* `record_gpu_adapter`, run from `MultiPipeline::new` (once per device,
next to the existing uncaptured-error handler), reads
`Device::adapter_info()`, prints one `[gpu] adapter: <name> (<backend>,
<device type>, driver …)` line — stderr natively, `console.warn` on the
web, which has no stderr — and keeps a copy.
* `note_scene_draw`, run from `ViewportPane::draw`, counts how often the
viewport asked for a primitive. Every renderer makes that call; only
wgpu goes on to `prepare` and create a device. Three draws with no
device ever recorded therefore mean the primitive is being dropped.
The verdict is one of `Unknown`, `Hardware`, `Software(adapter)` or
`NoRenderer`. `Software` is `DeviceType::Cpu` — what Vulkan reports for
llvmpipe, DX12 for WARP and GL for a renderer string that gives it away —
plus a name match on the fallbacks browsers expose (SwiftShader, llvmpipe,
lavapipe, "Microsoft Basic Render"), because the WebGPU backend reports
every adapter as `Other` (gfx-rs/wgpu#8819). Metal has no software device,
so on macOS only `NoRenderer` can occur (a VM without a GPU); it is caught
by the draw count like everywhere else.
`gpu_status_if_changed` costs one atomic load per app message: a
generation counter moves only when a device is recorded or the draw count
crosses its threshold, so the app re-derives the verdict a handful of
times per session.
Notification (src/app/startup.rs, view/modal.rs, ui/statusbar)
-------------------------------------------------------------
`refresh_gpu_status` runs after every message. On a degraded verdict it
speaks through three channels, because many users never read the command
line:
* A popup, `ModalKind::GpuWarning`, queued once per verdict through the
startup-modal queue so it waits its turn behind the file open. It names
the adapter, states the consequence (slow navigation, unstable or
missing rendering) and gives the remedy that usually applies on the
running platform: a driver update not yet followed by a reboot on
Linux, a missing driver or a Remote Desktop / VM session on Windows, a
VM without a Metal device on macOS, hardware acceleration switched off
in the browser. "Don't show again for this device" stores
`GpuStatus::identity()` in `settings.gpu_warning_silenced`; keying it by
verdict means silencing "software rendering on llvmpipe" does not
silence a later, different failure.
* A status-bar pill in the theme's warning colours, `⚠ Software rendering`
or `⚠ No GPU renderer`. It is not a `StatusPill`, so it cannot be hidden,
and it stays for the session; clicking it reopens the popup, silenced or
not.
* A command-line entry through the new `CommandLine::push_warning`, which
is styled like an error but deliberately does not touch `last_error` /
`error_revision`: automation reads those to decide whether the command it
just ran failed, and a graphics warning is not a failed command.
`subscription()` requests frames only while the verdict is `Unknown` on a
document tab (the Start page draws no viewport), so the probe costs one
frame on a working GPU, three on none, then stops.
Verification
------------
* Live on the machine that had the incident, with the Vulkan loader
restricted to Mesa (`VK_DRIVER_FILES=…/lvp_icd.json WGPU_BACKEND=vulkan`):
popup → OK → pill → click reopens; "Don't show again" → restart → pill
only, no popup.
* `gpu_tests::the_adapter_is_reported_once_per_device` (ignored, needs an
adapter) asserts the classification on a real device; it was run on both
llvmpipe (`Cpu`) and an NVIDIA GT 1030 (`DiscreteGpu`).
* Unit tests cover the name heuristic, the verdict function, the identity
rule, the warning text with an adapter name that itself contains
parentheses and commas, the settings round trip and the command-line
semantics. New strings are in all 21 locales as the `gpu` Fluent group.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Builds from main reported only the release version (2026.36) even when
HEAD was hundreds of commits past the tag. build.rs now also emits
OCS_TAG_DISTANCE, OCS_COMMIT_DATE, OCS_BUILD_METADATA and
OCS_FULL_VERSION: a clean checkout of the release tag stays "2026.36",
anything else becomes "2026.36+194.gef189d77", with ".dirty" appended
for uncommitted changes and only the hash when no tag can be counted
from (shallow clone, tarball). OCS_APP_VERSION is unchanged so the
update check, donation gate, User-Agents and window title keep working.
The About dialog gains Build / Commit date / Profile cards, Copy Info,
the REPORT issue template and recovery reports carry the full version,
and `-V`/`--version` print it instead of Cargo's 2026.36.0. Release
workflows check out full history so release binaries see the tag.
Also fixes the build being marked dirty when there is no repository:
diff-index exits 128 there, not 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The MTP/M2P midpoint-between-two-points modifier existed as a typed transparent command only. Add it to the Shift+RMB snap override menu as an icon-only cell after Parallel (hover tooltip, no text) with a new osnap/mtp.svg marker, plus a context-menu entry while a point prompt is active.
value toggle, and redesigned constraint-ribbon icons
Properties panel:
- New "Parameters" section (shown with no selection) listing every named
parameter with inline name/formula editing, add, and delete, backed by
new PropValue::ParamRow/ParamAddRow and command_driver handlers
(resolve_named_parameter_edit, on_prop_param_commit/_delete/_add_new).
A fresh parameter's formula now defaults to "1", not "0" — a 0-length
seed value is a genuine solver singularity for a Distance constraint
driven by that parameter, with no defined direction to grow back out of.
- New "Constraints" section (shown for a single selected entity) listing
every constraint touching it as a clickable PropValue::EntityLink that
selects the linked entities in the viewport, danger-tinted when the
constraint conflicts.
Constraint pills:
- New Options > General toggle, "Show values and parameter names on
constraint markers" (on by default, persisted). Off, every pill shows
just its bare glyph instead of glyph+driven-value/parameter-name — the
glyph alone is enough to see a constraint is present, without the value
text covering nearby geometry on a dense sketch.
Constraint-ribbon icons:
- Replaced all 18 unicode/letter glyph icons (including the plain "F",
"L", "M", "S" letters) with proper SVGs under assets/icons/constrain/,
matching the Modify section's two-color style (#B4B6B9 white / #6DB7ED
blue, consistent stroke width). Tangent reuses the Draw section's
white-arc-plus-blue-dots motif.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The application has no support-file search path — the four folders it uses
are functions returning fixed paths, not settings. Rather than build a Files
page that pretends otherwise, this one answers the question people actually
open it to ask: where does this thing keep my plot styles. Each row shows the
location and opens it in the file manager, and a row whose path the platform
could not give says so with its button disabled.
General gains buttons through to the plugin manager, the keyboard shortcut
editor and the alias editor. Those three have good dialogs of their own and
are not absorbed; Options simply stops being a place you cannot reach them
from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ViewCube, the UCS icon, its at-origin placement and selection cycling all
have commands and status-bar pills, and all four kept their value only on the
app struct. Turn the ViewCube off with NAVVCUBE, restart, and it is back.
Nothing failed — the setting evaporated — so a checkbox for any of them would
have been a checkbox that forgets. They are persisted first, with a
round-trip test, and only then given controls.
The new 3D Modeling page carries the three view tools plus the drawing
variables that decide how solids are tessellated: ISOLINES, DISPSILH, SURFU,
SURFV, SURFTYPE, SOLIDHIST and SHOWHIST. Those are saved in the DWG rather
than in preferences, so the page says so beside each group and hides them
entirely when no drawing is open.
Changing them also rebuilds the geometry. The SETVAR path never did, which is
why the comment beside LTSCALE's invalidation exists — the same reasoning
applies here, and without it a new isoline count does not appear until some
unrelated edit rebuilds the solid.
Selection cycling joins the Selection page next to the other two selection
modes, since that is what it is.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two drafting preferences had no proper home. The Drafting Settings dialog
displays the drafting rotation and offers only a Reset button — there was no
way to type an angle short of `SETVAR SNAPANG`. The polar tracking increment
existed in exactly one place: a pop-up hanging off the status-bar pill.
The rotation is a text field over an edit buffer rather than the value
itself, so clearing it to retype does not snap the crosshair to zero on the
way through; the buffer is reseeded whenever the value is restored from
behind it. Angles render without a trailing `.0`, matching how the polar
pop-up has always shown them — same kind of number, same reader.
Snap modes, grid spacing and object snap stay in Drafting Settings, where
they belong; the page links to it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zoom direction and factor, whether TEXTEDIT ends after one object, whether
continued dimensions inherit the base dimension's style, which points QDIM
measures from, and which annotative objects pick up a newly set scale. All
six work today; none had a control.
Two of them needed their meaning worked out rather than copied.
ANNOAUTOSCALE stores the mode in the magnitude and on/off in the sign, and
the four modes are not documented anywhere in the app — the command prompt
just says `[-4..4]`. Reading `add_annotation_scale_to_objects` gives them:
1 skips layers that are off, frozen, locked or frozen in the viewport, 2
keeps the locked ones, 3 skips only locked, 4 takes everything. The dropdown
names them instead of showing bare numbers, and choosing Off negates rather
than zeroing, so the mode survives being switched off and back on — which is
the convention the status-bar pill already follows.
The QDIM origin priority had no system variable at all; it could only be
reached through a keyword inside the running command.
Drawing units stay in their own dialog; this page links to it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three settings that work, persist, and could only be reached by typing their
name: TEXTFILL, CLIPROMPTLINES and COMMANDLINEFADETIME. The last two govern
how much of the command window is prompt and how long its history lingers —
the kind of thing people fiddle with once and then leave, which is exactly
what a settings dialog is for.
One fix rides along. TEXTFILL is a process-global, so changing it invalidates
the text in every open drawing; the SETVAR path rebuilt only the active tab,
leaving the others rendering the previous setting until an unrelated edit
happened to rebuild them. Both paths now go through
`invalidate_text_everywhere`, so the checkbox and the command cannot disagree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
General had become three unrelated controls: a language picker, a save format
and a file association. The last two move to an Open and Save page, where
they join the two saving preferences that have never had a widget.
Automatic save has worked since the SAVETIME command was added and is
persisted; the only way to change the interval was to type it. Same for the
`.bak` copy from #205 and its ISAVEBAK command. Both are settings people
expect to find in a dialog, and neither was in one.
The page also carries a button through to the Plot dialog. Options holds the
preferences; the plot device, paper and plot styles stay where they already
work well. Note that the modal stack shows one dialog at a time, so the
button replaces Options rather than stacking on it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings in 54 commits from origin/main: a selection-performance overhaul,
surface thickening, GPU-native wide/tapered stroke rendering, continued
ocs_doc_api evolution, and assorted fixes/polish (block double-click
editing, DWG hatch/database repair, clipboard, collapsible Properties
sections).
Six real conflicts, all where this branch had independently re-applied
a fix upstream later revised or already carried natively:
- src/app/automation.rs, src/app/command_driver.rs: combined both
sides — kept this branch's sketch-constraint/named-parameter loading
on "new"/"open", adopted main's Scene::clear()/material_base_dir and
its newer rebuild_derived_caches() over the older bump_geometry().
Scene::clear() (src/scene/entity.rs) now also resets
sketch_constraints/named_parameters, so every caller gets this for
free instead of each call site repeating it.
- src/io/mod.rs, src/scene/render_graph.rs: adopted main's design
outright (commit 973af87d, authored by Hakan directly) — a large
MINSERT is no longer purged as "corrupt" at load, only its rendering
is bounded, preserving the source data. This branch's own earlier
design (reject at load) predates that revision.
- src/scene/pipeline/mesh_gpu.rs: adopted main's valid_rgba_size()
guard (a real robustness improvement over this branch's version).
- src/snap.rs: replaced wholesale with main's version — this branch's
copy was an explicit verbatim snapshot from an earlier upstream PR
(commit 4c264b8c) with zero constraint-system-specific content, and
main has since evolved it considerably further.
Also removed two now-dead duplicates the merge surfaced without a
conflict marker (both branches had independently fixed the same
things): the OPTIONS/OP command dispatch/autocomplete entries, and
automation.rs's open_finalizes_and_purges_like_the_ui_open_path test
(kept main's version, which covers strictly more: solid_models/
material_base_dir reset on "new").
Full workspace suite passes (812 lib tests + this branch's own 24
constraint/persistence integration tests) except the one pre-existing,
unrelated font-test failure already present on main. Zero clippy
findings in the hand-written merge resolution itself; the ~1,171
warnings clippy reports are pre-existing in the 54 absorbed commits,
not introduced here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New ConstraintKind variants — Concentric, CenterPoint, Colinear, Midpoint,
Fixed, PointOnCurve, EqualDistance, Symmetric — each built entirely from
existing ocs_gcs (FreeCAD planegcs) primitives, no new solver math needed.
build_constraint now returns Vec<Rc<dyn Constraint>> instead of
Option<Rc<...>>, generalizing the old Coincident-only "needs a second
system-level constraint" special case to every kind that needs one.
Wired into the ribbon (7 new buttons), a new point+pre-selected-entity
command for CenterPoint/Midpoint/PointOnCurve, a new 4-point command for
EqualDistance, and the existing DWG-native class-name mapping (all 8 reuse
the same generic "plain Geometrical node" path already built for
Coincident/Horizontal/etc.). Deliberately not modeled: Normal and
EqualCurvature, which for this app's Line/Circle-only entity model would
just be Perpendicular and Equal wearing different DWG class names.
Also makes DWG-native constraint persistence opt-in (Options > General,
off by default) rather than unconditional: a scope that doesn't yet have
the native graph is only given one when the setting is on, but a scope
that already has one — from an earlier save, or a file that came from
real AutoCAD — keeps getting it resynced regardless of the setting, so
toggling it off never freezes a stale graph in place.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Named parameters (docs/named_parameters_design.md, stages 3-4):
- ParameterTable (src/scene/named_parameters.rs): a document-wide table of
named formulas, each resolved through a shared expression evaluator with
cross-parameter references, cycle detection, and division-by-zero/parse
error reporting. SketchConstraint::driving_param becomes DrivingValue
(Literal | Named) instead of a bare f64, resolved at solve time by
sketch_solve::build_constraint via Scene::named_parameters.
- Persistence (src/scene/named_parameters_persist.rs): same lazy XRecord
model sketch_persist.rs already established, synced around open/save.
Bumps the sketch-constraint XRecord's own format version (1 -> 2) since
DrivingValue's shape doesn't line up with a version-1 blob's bytes.
- PARAMETERS command and its editor (src/ui/window/named_parameters.rs):
add/remove/edit rows, live validation, duplicate-name detection: Apply
defines the table and re-solves every constraint scope with a
named-parameter-driven constraint (command_driver.rs). Distance/Angle
constraint prompts (modules/draw/constrain/value.rs) now accept either a
literal or an existing parameter's name, mirroring how the constraint's
own value prompt already worked.
- Undo scope-down carried over from this project's own established pattern
(constraint-system stage 9): the geometry an Apply moves is one undo
step, but the parameter table edit itself has no undo entry yet — a
documented follow-up, not a silent gap.
UI scale setting (src/app/settings.rs, app/mod.rs, ui/window/options.rs):
a whole-interface scale percentage (50-200%), wired directly into iced's
own per-window scale_factor hook rather than per-panel font-size
plumbing, so it applies immediately to every panel, dialog, and the
ribbon at once. Persisted alongside the existing lineweight-display-scale
setting.
OPTIONS/OP command dispatch (app/commands/view.rs, app/commands/mod.rs):
OPTIONS was registered for autocomplete but had no dispatch arm — the
Start-tab welcome screen's own button was the only way to reach it.
Found live-testing the UI-scale setting, unrelated to that feature
itself. "OP" is AutoCAD's own short alias.
help/: a new self-contained static user-help manual (manual.md as the
authored source, tools/build_help.py splits it into the committed static
HTML pages plus search index) — not yet wired into the application.
64 new/updated tests across the named-parameters and constraint-prompt
changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Options → Selection already carried the pick box, the selection-area effect
and the grip visuals. Four settings behind it were finished, persisted and
reachable only by typing a system variable, and one more was a constant.
**Four that only needed a widget.** `SELECTIONEFFECT`, `SELECTIONPREVIEW`,
`PICKADD` and `PICKDRAG` are all wired end to end already. `SELECTIONEFFECT`
was the odd one: the card's own "Restore Defaults" button resets it, so it
could be turned off by a button and only turned back on from the command
line.
`SELECTIONPREVIEW` is a bitmask and gets one checkbox per bit, which is why
`set_preview_bit` exists rather than two inline expressions — the bits are
the thing this change is most able to get backwards.
The `PICKADD` checkbox is worded as AutoCAD words it, "use Shift to add to
selection", which is the *inverse* of the stored flag. That inversion is
commented at both ends; it reads as a bug otherwise.
**`GRIPOBJLIMIT`.** The limit past which a selection gets no grips arrived
with the selection work as a hardcoded 100, matching AutoCAD's default. It is
now a setting, a slider and a system variable, following `PICKBOX`'s path
exactly. AutoCAD's 0 means "no limit", which the old `>` comparison could not
express, so the read now guards on it — with a test, because zero is the one
value that would otherwise suppress every grip instead of allowing all of
them.
The slider covers 0..=1000, where every useful value lives; the sysvar takes
AutoCAD's full 0..=32767, and a larger value set from the command line parks
the handle at the end while the read-out keeps showing the real number.
**`SelectionPrefs`** replaces the `pick_box` parameter rather than adding
three more. `view_window` already took seventeen positional arguments, and
`pick_add` / `pick_drag_rect` are two adjacent `bool`s that would swap
silently at the call site.
**Two fixes found on the way.** `OPTIONS` and `OP` were registered for
autocomplete but never dispatched, so typing either answered "Unknown
command" and the dialog could only be opened from the start page. And the
`SELECTIONPREVIEW` doc comment had its bits the wrong way round — the code
reads `& 1` for the idle rollover and `& 2` for the in-command one, as
AutoCAD does — which would have put the two new checkboxes' labels backwards.
**Not done, deliberately.** `PICKFIRST` and `PICKAUTO` exist but are always
on, with no variable. `PICKSTYLE` is stored in the header and read by nobody
— group expansion decides from the group's own `selectable` flag — so an
"object grouping" checkbox would control nothing. Associative-hatch selection
does not exist as a selection behaviour. Each needs a behaviour change rather
than an exposure.
Of the ten new strings, en-US and el-GR are translated; the other nineteen
locales carry the English text. Composing them from existing keys, the way
el-GR composes the neighbouring labels, only works for one of the ten —
there is no existing key for "limit", "unlimited", "shift", "rectangle" or
"lasso", and "Grips: Objects" as the label of a numeric limit would be worse
than an untranslated string.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports FreeCAD's planegcs 2D geometric constraint solver to Rust as a new
crates/ocs_gcs workspace member, and wires it into the document/scene model
as a persistent SketchConstraintSet: Coincident, Horizontal/Vertical,
Parallel/Perpendicular, Equal, Tangent, Distance, Angle and Radius
constraints that re-solve live (including mid-drag, not just on release),
render glyphs and a DOF badge, flag conflicts with a one-click resolver
pill, and round-trip through save/load via an XRecord blob.
Also includes docs/command_reference.md (a statically generated reference
of every registered command and its prompt sequence) and
docs/named_parameters_design.md + docs/backlog.md, which hand off the next
queued feature (a named-parameter/expression table) and consolidate every
deliberate scope-down and open gap found along the way, so a fresh session
has a complete starting point.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>