Bare BACKGROUND now opens an interactive colour prompt (via
ValuePromptCommand, same idiom as PDMODE/LTSCALE) instead of just
printing usage, so the command works both step-by-step and as a
one-shot `BACKGROUND <colour>`. Options reordered to
Default/Black/DarkGray/Gray/LightGray/White; DEFAULT restores the app
default, now unified to rgb(33,40,48) across startup, load and reset.
Also drop the unreachable OPTIONS arm in dispatch_display.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entities inside a block definition that sit on layer "0" must take on the
properties of the layer the block reference is inserted on; every other
layer is "sticky" and keeps its own identity. OpenCADStudio resolved a
block child's ByLayer color/linetype/lineweight against the child's own
layer in all cases, so layer-0 content stayed layer-0 (white) instead of
inheriting the instance layer.
Resolution now follows the standard rule for block children — explicit
wins, then ByBlock → the insert's own style, then layer-0 + ByLayer →
the insert's *layer* style, else the child's own layer. The insert's
layer style threads through the whole block-expansion chain (and chains
correctly through nested inserts), with the child's own transparency
preserved.
Covered across every block-render path:
- wires (line/arc/circle/polyline/text) via the block cache + the
explode fallback + insert attributes
- hatch / solid fills (also fixes the PDF/print export path)
- 3D solid block children (per-instance mesh recolor)
- Dimension / Table baked-block sub-entities
New shared helpers in scene::view::render: InheritStyle, layer_render_style,
lineweight_to_px; render_style_for_block_sub gained the layer-0 branch.
Adds unit tests covering the layer-0, sticky-layer, ByBlock, explicit and
transparency cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modify commands invoked with nothing pre-selected (ERASE, MOVE, COPY,
ROTATE, SCALE, MIRROR, ARRAY*, STRETCH, EXPLODE, GROUP, BLOCK, the
clipboard cuts, LAYOFF/FRZ/LCK/ULK, ...) ran the command on the FIRST
picked object and terminated. You could not build up a selection.
SelectObjectsCommand now keeps gathering: each pick accumulates into the
set (Shift removes), the prompt shows the running count, and the command
fires only when the user presses Enter or right-clicks. Reuses the
existing JOIN gather pattern, so no viewport/host changes are needed.
LAYMCUR acts on a single object's layer, so it keeps the immediate
first-pick behaviour via the new SelectObjectsCommand::instant().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The preview box rendered a hardcoded `text("AaBbCc 0123")` that ignored
every edited property. Replace it with a canvas that tessellates the
sample string through the same stroke path the text entities use
(`lff::tessellate_text_ex`), so font, width factor, oblique angle and the
backward/upside-down flags are reflected live as fields change. Effective
font follows the entity rule (TrueType name wins, else stroke font file);
the result is fit to the preview box with Y flipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The v0.7.0 web (GitHub Pages) release failed to compile under
--no-default-features. Two symbols leaked into the wasm build:
* `solid_section` (SECTION command) used truck-meshalgo directly
(`triangulation`/`extract_interference`), but that crate is gated
behind the `solid3d` feature which is off on wasm. Gate the function
with `#[cfg(feature = "solid3d")]` and add a stub for builds without
it, mirroring `solid_model::boolean`.
* `register_as_handler` was `#[cfg(not(wasm32))]` while its caller
`set_default_app_blocking` was not, so wasm couldn't resolve it. Drop
the wasm gate; the existing non-win/lin/mac arm already returns `Ok`
on wasm, matching `unregister_handler`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gaps in the autosave recovery files:
1. A brand-new, never-saved drawing was never autosaved — on_autosave only
wrote tabs that already had a path. A shared autosave_target() now writes
saved drawings to <file>.sv$ and unsaved ones to the system temp dir
(OpenCADStudio_<name>.sv$).
2. Closing a file (tab) left its .sv$ behind, which also defeated the
program-close cleanup (it only scans still-open tabs). on_tab_close and the
unsaved-discard path now delete the closing tab's .sv$, and Save/Save-As
drop the temp copy a drawing used while it was still unsaved.
Program-close cleanup (exit_app -> cleanup_autosaves) already worked.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The .sv$ recovery copies only need to survive a crash, so they are removed on
a clean save (Save and Save-As both delete the file's .sv$ after writing) and
on a clean exit — a new exit_app() runs cleanup_autosaves over every open tab
before quitting, and all iced::exit() paths now funnel through it. A leftover
.sv$ therefore signals an unclean shutdown, i.e. something to recover.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A periodic subscription (every SAVETIME minutes — default 10, 0 disables)
writes a <name>.sv$ recovery copy of each dirty, already-saved drawing at its
own DWG version. Non-destructive: it never touches the original file or the
dirty flag, and skips unsaved (path-less) tabs. The SAVETIME command
sets/queries the interval and persists it. Recovery files (.sv$ and .bak) open
through the normal Open dialog — the loader detects DWG vs DXF from the leading
magic bytes, and a "Backup / Autosave" filter lists them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the every-launch (re)registration thread. File association is now set up
once via the first-launch prompt (Yes → register + set default; No → decline,
persisted as file_assoc_enabled) and managed afterwards by the FILEASSOC
command. set_default_app now registers the handler entries (.desktop / mime /
ProgIDs) itself before asking the OS to default to us, since boot no longer
does — so the default never points at a missing handler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`FILEASSOC 1` (re)registers the app as a .dwg/.dxf/.bak handler; `FILEASSOC 0`
unregisters it (removes the .desktop + CAD mime package on Linux, the per-user
registry keys on Windows); bare `FILEASSOC` reports the current state. The
choice persists (file_assoc_enabled) and gates the every-launch
re-registration, so turning it off stays off across restarts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Save: overwriting an existing file first copies it to a sibling <name>.bak
(default on; toggle with the ISAVEBAK command, persisted in settings).
- Open: .bak is a first-class type — the Open dialog lists it and the loader
detects DWG vs DXF from the file's leading magic bytes (the extension hides
the real format).
- File association registers .bak too, every launch (so users who associated
earlier get it automatically): Windows via the registry; Linux via a
high-priority shared-mime-info magic that resolves DWG/DXF content under the
.bak extension, with the generic *.bak glob claimed only once the app is the
default DWG handler — so it never hijacks other apps' backups.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two in-canvas confirmations on the Save-As path:
- AEC/Civil drop warning: when the drawing carries unsupported AEC/Civil
(application) objects that survive only as verbatim source-version bytes,
saving to a different version or to DXF would silently drop them. The
dialog reports the count and offers "Save in source version" (keep) vs
"Save anyway" (drop). Same-version DWG saves are lossless and skip it.
- Overwrite confirmation: when the chosen filename already exists in the
target folder, "Replace" / "Cancel" before clobbering it.
The two chain (AEC first, then overwrite) via per-flow acknowledgement flags
reset whenever the Save dialog opens or closes. Detection is a new
io::dropped_on_save_count over the document's Unknown (raw-passthrough)
objects/entities.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the command line holds a typed-but-unentered command (or option
keyword / value), a viewport right-click now runs it, mirroring Enter.
Routed through CommandFinalize so the MText preview and grip-popup
guards apply and a non-empty line forwards to the submit path. All
other right-click behaviour is unchanged and applies only when the
command line is empty: right-drag orbits, the first right-click during
an active command acts as Enter, a second opens the context menu, and
an idle right-click opens the menu.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- FLATSHOT: project the selected solid's edges to 2D Line entities at Z=0
- CONVTOSURFACE: convert the selected solid(s) to Surface entities
Both reuse the cached B-rep's edge wires. COMMANDS.md: 219/262.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Package the drawing plus its referenced files (xrefs + raster images)
into a sibling "<name>_archive" folder via std::fs, reusing the
existing block_records / entities iterators. COMMANDS.md: 217/262.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Convert selected closed polylines and circles into Region entities,
one wire loop each (reuses acadrust Region/Wire). COMMANDS.md: 215/262.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Six more standard commands by reusing existing infrastructure:
- CLOSE: close the active tab (Message::TabClose)
- HIDE / VISUALSTYLES: per-viewport render mode (Message::SetRenderMode)
- QUICKCALC / QC: alias the CAL arithmetic evaluator
- HYPERLINK <url>: attach a hyperlink via the PE_URL XData record
- XOPEN: open a selected xref's source file (Message::OpenRecent)
COMMANDS.md updated to 214 done / 15 partial / 33 missing of 262.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integrate a large batch of standard CAD commands and fix several
name/behaviour mismatches, with no product references in code.
3D solids (reusing the truck B-rep pipeline):
- SLICE, INTERFERE, PRESSPULL, POLYSOLID, PYRAMID, SECTION
- 3DROTATE, 3DMIRROR, 3DALIGN (cached-solid transforms)
Annotation / text:
- ARCTEXT, SPLINEFIT (Catmull-Rom to Bezier), DIMJOGGED, OBJECTSCALE
(XData markers honoured by the existing geometry-gen / tessellator)
Blocks / misc:
- NCOPY (block-content extraction via explode_entity)
- OPTIONS, SYNCPVIEWPORTS, THICKEN, plus earlier toggles, aliases,
system variables, and layer/style/view commands
POINTCLOUD/RECAP and UNDERLAYLAYERS/UOSNAP are intentionally left out:
they require a proprietary binary / PDF-layer parser, a GPU point
renderer, and real test files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #213 introduced rustfmt drift in tip_style / swatch indentation and an
inline if-expression in two previously fmt-clean files. Format pass only;
no behaviour change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consolidates 21 occurrences of identical Color literals scattered across widgets.rs and mod.rs into 9 named constants (COMBO_BG, COMBO_HOVER_BG, COMBO_OPEN_BG, COMBO_BORDER, COMBO_ACTIVE_BORDER, COMBO_ARROW, SWATCH_BORDER, TIP_BG, HIST_INACTIVE_BG). -85 net lines, zero behaviour change.
Section labels (Draw, Modify, Annotation, …) were left-aligned with a small padding. Set align_x(Center) on the parent column so the label container — still sized to its text width — is centred within the column, which itself remains sized by the tool buttons above. Sections keep their natural content-based width, no wasted space.
The recursion-depth guard fixes the parser stack overflow, but user input
actually flows through eval_to_string (command line, viewport, dynamic
input — synchronous on the UI thread). Its embedded-expression fallback
re-tokenises every (start, end) window, so cost is ≈O(n³): a few-KB paste
of non-evaluable text or deeply nested parens froze the app for seconds to
minutes even though eval_number fast-returns None in microseconds
(measured: "x"×3200 → 16 s, 4 KB of parens → 51 s).
Cap the fallback scan at MAX_EVAL_SCAN_LEN (256) — whole-string numbers
and expressions of any length still evaluate via the fast paths; only the
mixed free-text scan is bounded, returning long input unchanged. Clarify
that MAX_RECURSION counts parser frames (256 ≈ 41 nested parens, not 256
levels). Add regression tests: the 41/42 nesting boundary, unary-chain and
function-arg-nesting rejection, and an eval_to_string over-cap promptness
test (50k junk + 100k nested parens return instantly).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- expand_defn: dedup the <1px baseline LOD branch the same way the <5px
greek branch does, so colour-split MTEXT no longer stacks N overlapping
baselines (one per \C segment) over the same OBB.
- sysfont: memoise canonical_family_name process-wide. resolve_font calls
it once per word on the MTEXT measure hot path and Face::resolve re-runs
it right after; the fontdb query + linear family scans were uncached.
- dimension/multileader/table: reword the 6 fill_tris_low FIXMEs. They
cited a stale line and the wrong consumer — these fills render on the
top-level path (panic-safe .get().unwrap_or), not the block cache, so
they cannot trip emit_wire's debug_assert. State the real status:
latent f32-precision debt, not a crash. Also fix the mis-indented
text-fill WireModel in dimension.rs.
- tests: add a host-independent colour-split test (two \C segments in a
block keep ≥2 distinct wire colours — guards the per-wire colour fix)
and a TTF-gated UTM fill test (paired, non-zero fill_tris_low). Note the
arial-style test's LFF-fallback limitation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
emit_wire reconstructed fill verts via a raw `fill_tris_low[idx]` index,
guarded only by a debug_assert. Slice bounds checks are not gated by
debug-assertions, so a Leader with a filled arrowhead nested in a block
(populated fill_tris, empty fill_tris_low via the low-level tessellate
early-return) panicked in release as well as debug.
Use the panic-safe `.get(idx).copied().unwrap_or([0.0; 3])` — the same
"empty = all-zero low half" contract the other fill consumers
(face3d_gpu, xclip) already honour. The debug_assert stays as a tripwire.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Colour-split MTEXT produces one outline wire per inline \C/\c override. At <5px zoom each outline independently emitted the full OBB greek rectangle — N overlapping boxes with last-colour-wins. Track last_lod_obb and skip duplicate substitutes.
Also corrects the fill-only LOD bypass: suppress fill-only wires at <5px instead of sending them unconditionally through emit_wire. The outline sibling's baseline or greek box suffices at sub-pixel zoom.
dimension.rs (x2), multileader.rs (x3), table.rs (x1) emit fill_tris
without a corresponding fill_tris_low. These will trip the debug_assert_eq!
added in the preceding commit when the entity appears inside a block
definition. Each site is marked with a FIXME referencing block_cache.rs:1397
and the correct fix pattern (split_ds on each vertex).
Three related bugs in tessellate_sub_local and expand_defn:
1. Wire folding lost per-segment colour. Multi-wire tessellator output
was concatenated (NaN-separated) into a single LocalWire using the
first wire's colour. Inline \C/\c MTEXT colour overrides inside block
definitions rendered in the entity colour instead of the override colour.
Fix: tessellate_sub_local returns Vec<LocalWire>; each wire from the
tessellator becomes its own LocalWire with its own colour.
2. fill_tris_low was never forwarded. The double-single precision path
was silently degraded on cached wires. Fix: fill_tris_low forwarded
from WireModel through to LocalWire and BatchEntry. debug_assert_eq!
added at emit site to catch future invariant violations.
3. Fill-only wires hit the text LOD ladder. MTEXT outline/fill pairs
produce separate LocalWires; the fill-only wire was routed through
emit_text_baseline/emit_greeked_text which ignore fill_tris (invisible
double-draw). Fix: is_fill_only field on LocalWire bypasses the LOD
ladder. Also fixes StyleKey::fill_only being hardcoded false at both
emit_wire call sites (latent batch misclassification).
resolve_font previously returned &str, passing raw stems like "arial" verbatim to Face::resolve. System font lookup requires the canonical family name ("Arial"), so inline \f/\F overrides from block-nested MTEXT were silently unresolvable and fell through to no geometry.
Change resolve_font to return Cow<'a, str>:
- no override -> Borrowed(base), zero allocation
- LFF built-in -> Borrowed(font), zero allocation
- system font resolved -> Owned(canonical), one allocation
- unresolvable -> Borrowed(base), falls back to style font
All 4 call sites updated to &font_name (&Cow<str> derefs to &str).
Adds two unit tests covering the fallback and TTF-stem-to-family-name paths. Adds integration test verifying block-nested MTEXT produces geometry and correctly separates outline/fill wires.
- Save As now defaults the version dropdown to DWG/DXF 2018 (latest)
instead of the loaded file's version; the selected version is written
as-is (removed the source-version fallback for version-locked data).
- Pin acadrust 55a2caa: allocate_handle respects header.handle_seed as a
floor, fixing the corruption where opening a file and saving it
silently overwrote existing XRECORD/DICTIONARY objects (handle reuse)
and AutoCAD refused the result.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Broad pass over the command set: integrate missing commands, wire
previously-unreachable ribbon/menu buttons, and correct name/behaviour
mismatches. Every UI command token now resolves (real handler where the
infrastructure exists, an honest "not yet implemented" status otherwise).
New commands:
- Draw: 3DPOLY, 2D solid (SO/SOLID2D), HELIX, TRACE, SKETCH, DIMCENTER
(DCE/CENTERMARK)
- Modify/inquiry: REVERSE, MEASUREGEOM (MEA), DBLIST
- Data/manage: BASE, OVERKILL (exact-duplicate removal), AUDIT (read-only
integrity report), SETVAR (system-variable get/set), SCRIPT (.scr runner),
FINDNONPURGEABLE
Behaviour fixes:
- LINE and SPLINE gain Close/Undo; AREA computes area/perimeter in f64
(correct at large coordinates)
- ATTEDIT/ATE unified so the alias and the ribbon button open the
interactive editor; -ATTEDIT is the command-line form
- Linear/aligned/radius/diameter dimensions gain a text override (Text) and
text angle (Angle) option
- INSERT gains Scale/Rotate options; COPY gains an Array option
- LINETYPE SET sets the current entity linetype; SETBYLAYER resets overrides
- UNDO accepts a step count
Integrations: standard dimension/block aliases (DLI/DRA/DAN, BMAKE,
EXPORTPDF, DDIM), drafting-aid and screen toggles (GRID/SNAP/POLAR/OSNAP/
DSETTINGS/CLEANSCREEN/QUICKPROPERTIES), viewport tiling
(CASCADE/HORIZONTAL/VERTICAL/VPJOIN), and customization entry points
(CUI/ALIASEDIT).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bare `ZOOM` had no dispatch arm — only the sub-keyword forms (ZOOM
EXTENTS/IN/OUT/WINDOW/…) were handled, so typing `ZOOM` fell through to
an unknown-command error. It now starts the interactive window zoom.
Add the missing Zoom Window ribbon button: the command existed but had no
tool()/icon, so it was reachable only by typing. Wire it into View ▸
Navigate with a new dashed-rectangle magnifier icon.
Closes#210
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Editing tools (MOVE, COPY, STRETCH, ROTATE, OFFSET, SCALE, MIRROR) and
other interactive commands left their working set selected after they
finished, so the next edit silently reused it — confusing especially for
copy-producing tools. Drop the selection centrally in apply_cmd_result
when a command ends (active_cmd Some->None), excepting Relaunch/Dispatch
which hand a deliberate selection to a chained command. Pure-selection
commands run without an active command, so their result is preserved.
Closes#207
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Civil3D/AEC objects (and raw MLEADER/Surface records, EED) can only be
written losslessly in their source DWG version. On save, fall back to the
source version when the chosen version is an incompatible encoding family
(acadrust CadDocument::has_version_locked_data), with a command-line
notice, across every save path (Save, Save As, save-before-close/quit)
via a shared dwg_save_version helper. Default the Save-As format dropdown
to the loaded file's version so a round-trip preserves it.
Picks up acadrust DWG-writer conformance fixes (3dbae05) so a Civil3D
R2004 drawing round-trips through OCS and opens in AutoCAD with no errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the bake↔live dimension parity from the audit:
- terminators now reproduce the style's actual arrow block: dim_metrics
resolves arrow1/arrow2 via arrow_from_block (DIMTSZ tick, DIMBLK /
DIMBLK1+DIMBLK2 per DIMSAH, else closed-filled), and dim_terminator bakes
each kind (None, filled/blank triangle, tick, open, dot, origin, box,
datum) as Line + SOLID — no more every arrow degrading to a filled triangle
- short dimensions flip their arrows outside with stubs (DIMSOXD honoured)
when the arrowheads don't fit between the extension lines (DIM-ARROWS-OUTSIDE)
- DIMFXLON fixed-length extension lines via ext_endpoints (DIM-FXL)
Refs #181. Remaining (annotative viewport scale at non-1:1, the ~1deg angular
arrowhead-tangent cosmetic, and DIMTFILL text-break gap) are niche/cosmetic
and tracked separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings the save-time *D-block bake into parity with the live render so a
dimension no longer changes when the file is saved and reopened (the reload
draws from the block). Per a bake-vs-live audit:
- text is now the live render's own Text/MText entity (alignment, MText
codes, text style, alignment point) — fixes every label shifting ~half a
glyph on reload
- extension lines carry the DIMEXO start gap + DIMEXE overshoot, and slant
by ext_line_rotation (DIMEDIT Oblique)
- terminators follow the style: oblique tick when DIMTSZ>0, else a closed
filled SOLID arrow (was a generic open stroke)
- angular dims bake the swept arc + tangent terminators, flush extension
lines (matching live exactly)
- radius/diameter bake the leader leg; diameter runs edge-to-edge through
the centre; centre mark honours negative DIMCEN (centre lines)
- per-element colours/lineweights (DIMCLRD/E/T, DIMLWD/E), DIMSE/DIMSD
suppression, DIMDLE tick overshoot
- consistent arc sweep at exactly 180 degrees
Refs #181. Remaining (custom DIMBLK arrow shapes, DIMFXLON, arrow-outside
fit, annotative viewport scale) tracked separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keyboard accelerators (copy/paste/save/undo/new/open) were gated on
`Modifiers::control()`, which on macOS matches only the physical Control
key. Mac users pressing Cmd+C/V/S/Z got nothing, contrary to platform
convention and every other Mac app.
Switch the gate to `Modifiers::command()`, which iced resolves to the
Logo (Cmd) key on macOS and Ctrl on Windows/Linux. On non-mac targets
this expands to exactly `control()`, so behavior there is unchanged.
Also make the Keyboard Shortcuts reference window display the
platform-correct modifier name (Cmd vs Ctrl) so the help matches the
keys the user actually presses.
Closes#202
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
OCS wrote an app-specific "OpenCADStudio_Camera_Model" entry to the VIEW
table and preferred it when restoring the model view. That record polluted
the file for other CAD programs and its DXF round-trip was broken, so a
drawing saved in TOP reopened from DXF at an isometric-ish angle.
The model view now persists and restores entirely via the standard *Active
VPORT entry (view direction / target / twist), which round-trips natively in
both DWG and DXF. The custom View record is no longer written; existing ones
in older files are left untouched. Paper space already used the standard
sheet viewport and keeps a read-only fallback for very old files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-pins acadrust to the dimension DXF round-trip fixes (rotation/text/oblique
degree<->radian units, radial/angular/ordinate group codes, ordinate X/Y bit)
and drops the OCS-side fix_dxf_dimension_rotations dimension arm, which now
lives in the acadrust DXF reader (keeping it here would double-convert).
Completes the dimension audit (Batch 5). Refs #181.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch 4 of the dimension audit (command features).
- QDIM now works: the relaunched QDIM_PLACE stage decomposes the selected
geometry into endpoints, picks the axis from their spread (not Z), and
builds a continuous chain of linear dimensions at the picked line. Was a
no-op that errored with 'Unknown command'. (DIM-007/008/032)
- DIMSPACE steps parallel dimensions along the in-plane perpendicular
instead of Z (which never moved the dim line). (DIM-021)
- DIMBREAK / DIMJOGLINE no longer report success while doing nothing — they
state honestly that they are not yet implemented (need a break-gap / jog
field on the dimension model). (DIM-019/020)
Refs #181.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch 1-3 of the dimension audit (19 issues). The dominant class was the
baked *D block diverging from the live render: a reloaded dimension renders
from that block, so on save+reopen it silently changed.
Bake now matches the live render:
- text value/format via the shared style-resolved path (DIMDEC/DIMLFAC/
DIMPOST/units/<>), correct height + rotation ladder, suppression
- angular dims bake their swept arc (no longer collapse to a V)
- diameter draws edge-to-edge through the centre (live + bake), centre mark
- radius/linear/aligned bake arrowheads
- group-10 (base.definition_point) synced from the edited geometry so an
edited dim no longer shifts on reload
Editing / properties:
- aligned no longer corrupts ext_line_rotation when a coordinate is edited
- DIMTEDIT pins the moved text (text_user_positioned)
- angle properties shown/edited in degrees
- non-linear text respects the user-positioned gate
Commands / geometry:
- DIMBASELINE stacks outward (not toward the points) and its preview no
longer tilts over non-level points
- DIMCONTINUE/DIMBASELINE inherit the base dim's text rotation (UCS chains)
- angular extension lines reach the arc (no gap)
- text grip sits on the text for auto-placed dims instead of the origin
Refs #181. (acadrust DXF round-trip + QDIM/DIMBREAK/DIMJOGLINE/DIMSPACE/
DIMEDIT to follow.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the opt-in `rmb_enter` setting (and `RMBENTER` command) with
always-on behavior. While a command is active, the first viewport
right-click acts as Enter (commit / close) and a second consecutive
right-click opens the context menu instead. Idle right-click still opens
the menu; right-drag still orbits.
The cycle is tracked per tab (`right_click_entered`) and restarts on any
other interaction — a left-click pick, a new command, an orbit, or
leaving the viewport — so the next right-click is Enter again.
Refines #185.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a configurable right-mouse-button behavior without disturbing orbit
or the context menu.
- New persisted setting `rmb_enter` (command `RMBENTER [ON|OFF]`, default
off): when on, a right-click while a command is active acts as Enter
(commit/close); when idle it still opens the context menu. Right-drag
always orbits, so nothing is lost.
- Right-click vs orbit is now time-based: orbit engages only after the
button is held >=150ms and the pointer moves, so a quick right-click is
no longer swallowed by slight pointer jitter (was an 8px distance gate).
- The right-click Repeat menu now lists commands from every source
(command line, ribbon, context menu, shortcuts) via a unified
recents list recorded at the dispatch choke point, instead of only
typed commands.
Closes#185
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Typing a partial command (e.g. `bac`) and pressing Enter with no active
command ran into an "Unknown command" error. Now the top autocomplete
match is highlighted and Enter runs it (`BACKGROUND`), matching standard
DWG command-line behavior.
The fallback only fires when no command family matches the typed verb, so
complete aliases that resolve through a dispatch arm but are absent from
the autocomplete registry (`LT`, `ZO`, …) still run as typed. Programmatic
dispatch (ribbon, plugins, headless automation) is unaffected.
Closes#198
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Keep the right-side status-bar tools from being pushed off the bar when a
drawing has many layouts: the layout tabs now live in a horizontal scrollable
that only takes the space left between the menu and the tools, with ‹ › arrows
(and the mouse wheel) to scroll them.
Repurpose the leftmost hamburger — which previously fired a no-op MENU command
— to open a dropdown listing Model and every layout (scrollable, current
highlighted); picking one switches to it, an outside click dismisses it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Swap the two "Free and open source" / "Community driven" text cards on the
start page for the intro-video thumbnail with a play badge; clicking it opens
the video in the system browser (new Message::OpenUrl → sys::open_url). The
bundled thumbnail Handle is built once via OnceLock — Handle::from_bytes mints a
fresh unique id per call, so rebuilding it per view re-decoded and re-uploaded
the JPEG every frame and the image only appeared after a long delay.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the custom model_tiles tiling with iced 0.14 pane_grid as the layout
source of truth, while paper-space floating viewports are left untouched (they
overlap/free-place, which pane_grid cannot model).
Each Model pane is its own shader widget rendering its camera into the pane
bounds; the renderer Primitive carries a base_slot so several shader widgets
share the type-keyed MultiPipeline storage on disjoint slots. A second pane_grid
of transparent mouse_areas (plus on_resize) layers above the crosshair overlay
to receive input — the overlay's Hidden cursor interaction otherwise levitates
the cursor and starves any layer beneath it. The active pane follows the cursor
(hover-activate), and grid / crosshair / UCS icon render per pane while the
ViewCube and render-mode picker stay on the active pane only.
Split / close / VPORTS presets and the VPort table round-trip now drive
pane_grid (guillotine reconstruction on load). Adds drag-to-swap via a controls
bar handle with a ghost-card preview, visible pane dividers from the pane_grid
split regions, a default model background of RGB(33,40,48), and removes all the
superseded custom-tile code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reduce the ViewCube 30% (VIEWCUBE_PX 120→84) and tighten the nav region
(NAV_INSET_F 2.2→2.0) so the home / roll / nudge controls hug the cube in the
corner instead of floating in a large dead area, with sizes scaled to keep them
from overlapping. Bump the home and roll buttons back up slightly, shift the
UCS picker down below the region, and give it a fixed width so it centres
exactly under the cube centre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>