The Snap and Grid tab showed static grid values with dead checkboxes, and every grid line stroked identically.
Grid X/Y spacing and Major-line-every are now editable inputs (validated 2-100 for majors, dirty-tracked, applied with an error message on bad input); Adaptive-grid and Display-beyond-Limits checkboxes are wired to live state. Every Nth line partitions into major_segments and strokes subtly brighter (1.5x alpha, 0.75px) for the reference hierarchy. GRIDMAJOR state persists to UserSettings. Covers partition, invalidation, and parse tests.
The viewport grid ignored the configured spacing and forced square grids: compute_grid_step hardcoded a 1.0 base, a single shared step drove both line families (including the tangent derivative used for gap measurement), and the display always clipped to LIMITS despite the beyond-Limits option.
Grid spacing now scales per-axis from GRIDUNIT via compute_grid_steps (adaptive 5x growth, fixed passthrough), GridParams carries step_x/step_y, snap tolerances follow the visible steps, and the LIMITS clip is skipped when beyond-Limits is set. Also fixes a per-axis overflow fallback that returned the X base for Y. Spacing state (X/Y, adaptive, beyond-Limits) is stored on the app and persisted to UserSettings at defaults; dialog wiring follows separately.
Show the styled dimension and its value while placing a dimension, using
the same builder the command commits with. Each creation command
(DIMLINEAR, DIMALIGNED, DIMANGULAR, DIMRADIUS, DIMDIAMETER, DIMARC,
DIMJOGGED, DIMORDINATE, DIMCONTINUE, DIMBASELINE, QDIM) reports the
dimensions it would commit at the current stage through
`CadCommand::dimension_preview`. The application applies the current
creation style, or the base dimension's style for DIMCONTINUEMODE=1,
and the same viewport measurement scale as commit, then tessellates the
transient entity through the normal dimension renderer.
While the second definition point is being acquired, a hovered model
snap supplies a provisional viewport scale; once both points are fixed,
hovering another viewport does not change the value. Angular dimensions
never receive length compensation. Degenerate geometry, an invalid
scale, or conflicting viewports fall back to the plain rubber-band
preview so no misleading value is shown.
Grid snap locks to SNAPUNIT instead of magnetizing; snapped 3D points keep elevation instead of flattening to z=0. New SnapTypes Vertex/EdgeMidpoint/FaceCenter/Knot/FacePerpendicular/NearestFace with their own F4 master toggle and mode set, tiers, and markers, fully independent of the 2D master. Solid B-rep corners/edge-midpoints/face-centers from mesh edges and kernel faces, spline knot points, and live perpendicular/nearest-to-face on mesh triangles merged priority-aware in move/click paths. Drafting Settings 3D Object Snap tab is fully live (was dead placeholders); Nearest-to-face off by default. TDD: behavioral LINE-to-solid tests plus engine/scene unit tests.
Properties… next to the printer used to open the CUPS web interface in a
browser. It now edits the driver options of the *selected* printer the
way LibreOffice's Properties… does, without leaving the plot dialog:
- CUPS platforms (Linux, any desktop; macOS): an in-line editor lists the
printer's PPD options as `lpoptions -l` reports them — media type,
colour mode, quality, duplex, trays, scaling — with readable choice
labels, Reset / Cancel / Apply. Apply keeps only the choices that differ
from the driver defaults, per printer, in the plot config, and every job
for that printer passes them as `lp -o key=value`. Where CUPS cannot
answer, the desktop's printer settings panel opens instead of a browser.
- Windows: the driver's document-properties sheet (`DocumentPropertiesW`
with prompt, owned by the main window) and, on OK, the result stored as
the user's printing preferences for that printer (`SetPrinter` level 9),
which the job honours. `printui /e` (printing preferences) replaces the
device-properties sheet as the fallback.
The desktop print portal was tried first and dropped: GTK 4's print
dialog loses the preselected printer when the default queue is listed
before it, so the dialog always opened on the wrong printer.
Dialog layout: the modal grows to 940×620; Printer / plotter and Paper
size share a line with their pickers (long queue and media names now fit);
Copies + Properties… and Custom… sit beneath; the custom-sheet editor
labels each margin (Left / Right, Top / Bottom) and shows the unit.
Also recognises `none_device`, AutoCAD's stored spelling for "no plotter",
and writes it back unchanged.
Tests: `lpoptions` parsing and keyword humanising, the editor flow (load,
stale answer ignored, set, apply, remember, reset, error, cancel), device
name round-trips including `none_device`.
Co-Authored-By: Claude Opus 5 <[email protected]>
AutoCAD lets a plotter configuration carry user-defined sheets; a drawing
then names them like any other media (`Roll_24_(609.60_x_1500.00_MM)`).
The plot dialog now defines such sheets inline, under the Size row:
name, width/height, unit, and the printable margins in that unit. Added
sheets are selected at once, offered for every device after the device's
own list, remembered in the plot config across sessions, and written to
the layout with their own margins (a printer that reports the same size
still wins with its hardware margins). A selected user-defined sheet can
be removed; the sheet itself stays selectable in the drawing that uses it.
`CustomPaper` / `Margins` live in the paper catalogue with serde support;
`PlotDevice::margins_mm` consults the user's definitions. The Plot
dialog's form helpers (button and field styles, dividers, `label :
control` rows) move to `ui::style::form` so the next dialogs share them
instead of copying.
Tests: draft validation and seeding, catalogue naming of user sheets,
device margins with user definitions, and dialog round-trips (add,
select, persist, write, replace, remove, refuse an invalid size).
Co-Authored-By: Claude Opus 5 <[email protected]>
Replace the provisional PDF margins with the values AutoCAD's Plotter
Configuration Editor lists for `DWG To PDF.pc3`. They depend on the sheet
family and variant, not the size: ISO sheets 5/17/6/18 mm (left, bottom,
right, top), ISO expand 5/10/6/11 mm, ISO full bleed 0/1/0/1 mm; ANSI and
ARCH sheets 0.23/0.70 in, expand 0.23/0.42 in, full bleed 0.03 in all
round. Inch sets are stored in millimetres like the sheet itself.
Co-Authored-By: Claude Opus 5 <[email protected]>
A page setup names its plotter, and AutoCAD looks that name up in its own
configurations: `DWG To PDF.pc3` for PDF output, the spooler name for a
system printer, `None` when nothing is assigned. Drawings saved here used
to carry the dialog's label ("Save to PDF file…"), so AutoCAD reported a
missing device on every layout plotted to PDF from Open CAD Studio.
- New `io::plot_device`: `PlotDevice { None, Pdf, Printer }` with AutoCAD's
spellings, a reader for stored names that keeps any plotter
configuration AutoCAD wrote (an HP `.pc3`, `DWF6 ePlot.pc3` — plotted to
PDF here but preserved for AutoCAD) and upgrades the legacy label.
- Printable areas: the PDF driver's margins per sheet variant (full bleed
edge to edge; standard / expand insets provisional until read off
AutoCAD's plotter configuration), and for CUPS printers the media and
margins the queue reports over IPP (`ipptool` `media-col-database`),
parsed once per printer and cached. Windows enumeration follows with
native GDI printing.
- Page setups now store the device in AutoCAD's spelling and the margins
of the chosen device/sheet, keeping the drawing's own values while
neither changed (AutoCAD's `PAPERUPDATE` behaviour). The paper-space
printable-area rectangle follows.
- Plot dialog: the default entry names the printer it resolves to; a
printer that answered lists its own sheets with a note; the media query
runs in the background and answers for a since-deselected printer are
dropped.
Tests: device names, stored-name resolution, PDF margin table, IPP media
parsing (sizes, margins, borderless folding, default sheet), PWG names,
plus dialog ↔ layout round-trips for PDF, legacy label, preserved plotter
configuration, sheet change → margins, and printer selection.
Co-Authored-By: Claude Opus 5 <[email protected]>
AutoCAD stores a layout's sheet as the plotter driver's canonical media
name (`ISO_A4_(210.00_x_297.00_MM)`, `ANSI_full_bleed_B_(11.00_x_17.00_Inches)`),
so a drawing saved here has to carry the same spelling for AutoCAD to find
the sheet in its DWG To PDF list, and a name that arrives from AutoCAD must
resolve to its dimensions and go back out untouched.
- New `io::paper_catalog` replaces the eight-entry `paper_sizes`: ISO A/B,
ANSI, ARCH (incl. E1) and JIS series with full-bleed / expand variants,
Letter / Legal / Tabloid, millimetre and inch units, a parser that
reconstructs any well-formed AutoCAD name (custom or driver-specific)
from its embedded dimensions, driver / legacy-config aliases (`A4`,
`Letter`, `Ledger`), and dimension matching that prefers the plain sheet.
- The Plot dialog stores the canonical name and shows the sheet as
`ISO A4 (210 × 297 mm)`; a sheet that only exists in the drawing is
listed first so it stays selectable.
- A sheet the user did not change keeps the drawing's own media name
(`paper_name_for_settings`), so a Windows printer's `A4` is not rewritten
to `ISO_A4_(…)` just by passing through the dialog.
- Drops the unused `PlotFormat` / `PlotOrientation` messages,
`paper_label_from_dims`, and three duplicated `match "A4" | …` tables.
Tests: catalogue spelling, parser round-trip, aliases, dimension matching,
display formatting, plus dialog ↔ layout round-trips for driver-named,
catalogue and unknown sheets.
Co-Authored-By: Claude Opus 5 <[email protected]>
Drafting Settings dialog gets text inputs for Snap X/Y spacing plus a working Equal X and Y checkbox (mirrored inputs, Y follows X on apply). Snapper gains fixed snap_spacing_x/y (persisted in UserSettings, default 10) decoupled from the adaptive visible-grid step. Grid snap no longer aperture-gated: with Snap On it rounds every point to the grid, object snaps still override by tier. Adds parse/lock regression tests.
- IMAGEEMBED: pack raster images into OLE2FRAME entities so a .dwg is
self-contained; 'Embed Image' entry in the XREF attach menu; control/MCP
op embed_image for external tools; dxf_name for OLE2FRAME queries.
- Scene: populate_images_from_document now rebuilds OLE2FRAME and PDF
underlay models too (they used to vanish after any XREF op).
- Printing: add PRINTER_ENUM_NETWORK pass when listing printers; GDI
spooler fallback (hayro raster at 300 DPI + StretchDIBits) for hosts
whose PDF handler registers no print verb; DEVMODE orientation with a
90-degree raster rotation fallback for v4 drivers; fix the fit-scale
precedence bug that printed blank pages.
- PDF export dialogs on Windows use the async file dialog so the native
overwrite confirmation no longer deadlocks the UI thread.
- Plot dialog: persist the chosen printer (saved on selection) and the
picked plot window across sessions; page setups with no device no
longer reset the output device.
- Community fonts: detect unresolvable .shx references on open and offer
a download from the repository's fonts/ folder (contribution guide in
fonts/README.md); custom font source URL setting for intranet font
servers; downloaded fonts resolve from the per-user fonts directory.
Port press-to-capture dialog safeguards to the alias editor: transient top draft row with explicit accept, duplicate-alias and unknown-command danger styling plus persistent banners, reset-to-defaults with inline confirmation, Apply and Apply-and-Exit, alias count, Esc-cancels-draft-first, and unsaved-changes guard on close.
Refresh associations after complete SpaceMouse camera updates and recognize imported paper-space owners without Layout objects. Use kernel containment, intersection, perpendicular and spline tangent solving; pin cadkernel 7ce27f2 and add regressions.
Row menu Select New Path and Find and Replace now act on the right-clicked row via dedicated XrefRowPathPick / XrefRowFindReplacePrompt messages (prefill + command-line focus, XOPEN output included). Rows use a RightClickArea wrapper so right-click selects the row even though ContextMenu captures the press, and opening or acting on the menu resets the Change Path Type flyout state.
The Change Path Type row shows a right-triangle affordance and reveals the Make Absolute / Make Relative / Remove Path submenu beside it. The flyout opens on hover only and a shared submenu-row style removes dead code from the earlier iterations.
Row right-click menu is exactly Open, Attach..., Unload, Reload, Detach, Change Path Type group with Make Absolute, Make Relative, Remove Path, Select New Path, Find and Replace. Change Path Type renders as indented group. Open uses OCS for DWG and OS default for images and PDFs. Preview decodes on selection change, not only at refresh. Details shows placed display width and height for image instances.
Detach and Bind on nested rows report the CLI-worded per-entry error instead of skipping silently. Adds GUI-path regression tests for every row operation on direct and nested rows, plus Reload All, Overlay, Change Path, and Find and Replace coverage.
Right-click actions bound to the row under the cursor, split-button toolbar groups with shared borders and hover states, and a notice when an operation needs a selection first.
Removes PR3's `viewport_frame` seam in `viewport_ref.rs`; the frame is
PR1's camera-derived one everywhere.
The five dimension commands (DIMLINEAR / DIMALIGNED / DIMRADIUS /
DIMDIAMETER / DIMANGULAR) now record a real association when they
measure through a viewport. The commit path's one association gate
(`dimension_association_allowed`, added when PR2 was stacked) gains an
else-branch: where PR2 committed the dimension unassociated and PR1's
inference guard returned an empty source list, `Scene::attach_viewport_
dimension_association` now stores the whole chain — dimension ->
viewport -> block path -> entity -> feature — from the AcceptedSnaps PR1
captured, in definition-point slot order.
* `Scene::dimension_association_slot_points` names that order, including
the radial dimensions `dimension_reference_points` does not cover.
* `OpenCADStudio::viewport_dimension_snaps` builds the slot array. A snap
whose paper point is no longer the definition point it fills has its
model point recomputed from that definition point through the frame:
the object pick records one click for both extension origins, and a
rotated DIMLINEAR projects its origins onto the measuring axis.
* Radial dimensions take their own path inside
`attach_viewport_dimension_association`. Their single reference is an
osnap type 10 carrying the radial marker (which circle / which polyline
arc segment) and the angle around it; `source_reference` would hand
back the generic curve-parameter marker -2, which
`radial_source_for_marker` cannot find.
One measurement rule, documented in `viewport_ref::measurement_rule`:
`actual_measurement` is always the RAW measurement in the dimension's own
space (paper, for a viewport dimension), and the viewport compensation
lives only in the magnitude of the negative DIMLFAC override, applied
once by the formatter. PR2's persistence already followed it; PR3's
refresh did not — it substituted the model distance times
`model_factor()`, which made a refreshed dimension read `user_lfac` times
too much versus an identical freshly-created one. Refresh now writes the
paper measurement (a debug assert pins resolved paper points to the model
points through the frame), and `axis_distance` goes with it.
Block sub-entity snaps: verified that `block_cache` stamps the top-level
INSERT's handle onto every expanded and clipped sub-wire, so
`wire_source` can only ever report the INSERT — nested inserts included.
`OpenCADStudio::resolve_source_block_path` now descends from that INSERT
at accept time (using the object-pick path's existing
`resolve_measurable_entity`, so arbitrary depth, not just one level) and
fills `SnapSourceRef.block_path` with the real inner entity. Where the
descent cannot identify one — a non-planar-pickable type such as text,
hatch, spline or solid, or nesting past eight levels — the source stays
the INSERT with an empty path, which is coarser but never wrong. Both the
limitation and where it bites are documented on
`Scene::resolve_block_snap_source` and `SnapResult::source`.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Removes PR2's two seams now that PR1 (viewport snapping) is underneath.
* `Scene::viewport_frame` is PR1's camera-derived implementation. The
viewport-record version is gone; PR1's returns `None` for oblique /
perspective viewports, which is what we want — every entry point in
`viewport_dim_seam` then declines rather than guessing a planar
mapping. `viewport_dim_seam` is now just the dimension object-pick
path, and its module doc says so.
* There is one accepted-snap store: PR1's `OpenCADStudio::accepted_snaps`.
PR2's parallel `dim_accepted_snaps` (and its command-name ownership
tracking, which PR1's clear-on-command-start/end already does) is
deleted, along with the `record_dimension_accepted_snap` seam.
`accepted_snap_for_point` reads PR1's list.
Two things layer on top:
- the explicit object pick is not a point step, so it still records its
own snaps, now through `push_accepted_snap` into PR1's list, and now
carrying the picked entity handle + block path as a `SnapSourceRef`
so PR3 can associate it;
- a typed coordinate carries no snap, so `measure_snap` re-attaches the
content viewport under a snap that is free *and* source-less. That
keeps `DIMLINEAR` with typed coordinates inside a 1:10 viewport
measuring the model, without ever reinterpreting a real paper-sheet
snap that happens to sit over a viewport.
* One association gate, not two. `dimension_association_allowed` is the
single predicate, derived from the same `dimension_measure_space`
classification that decides whether to compensate the measurement, so
the two can never disagree. `infer_dimension_sources_guarded` delegates
to it, and the commit path's "commit unassociated when measured through
a viewport" condition is now that predicate rather than a second,
independently-computed bool. It also covers the explicit-pick and
explicit-source-list branches, which the old inference-only guard did
not. Because the classification ignores the trailing dimension-line
placement click, an ordinary paper dimension whose placement click lands
inside a viewport rectangle keeps its association.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Paper-space hit testing only sees sheet entities, so DIMLINEAR / DIMALIGNED /
DIMRADIUS / DIMDIAMETER / DIMANGULAR in "select object" mode found nothing
when the user clicked the model geometry a viewport displays.
A pick that lands inside a content viewport is now mapped into model space
through the viewport frame and tested against that viewport's resident model
wires - the same tessellation it draws, so the pick matches what the user
sees. Ordinary block instances are descended into, with the instance
transform (recursively, up to eight levels) baked into the extracted
geometry, so a line inside a block measures its placed length.
The picked entity is handed to the command already projected onto the sheet,
so all five commands keep working entirely in paper coordinates and unchanged;
the pick is recorded as a viewport snap, so the committed dimension is
compensated exactly like a point-input one. A miss falls through to the
ordinary paper-space pick.
Also narrows mixed-space classification to the measuring points: the final
click of each of these commands is the dimension-line placement, which lands
on bare sheet for viewport measurements and inside a viewport rectangle for
paper ones, and says nothing about what is being measured.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Paper-space commands could not pick model geometry displayed by a layout
viewport: `hit_test_wires` deliberately excludes viewport content in PSPACE,
and that has to stay true for selection. This adds the capability as a separate
query rather than by changing those semantics.
`OpenCADStudio::paper_viewport_snap` walks the current layout's displayed
content viewports top-most first, skips those whose display is off or whose
clip boundary excludes the cursor, and for each one runs the EXISTING snap
engine against that viewport's model wires using the viewport's own camera and
screen rectangle (`viewport_edit_frame_for`). The accepted model point is
mapped back onto the sheet with `ViewportFrame::model_to_paper`, so everything
downstream keeps working in paper coordinates — LINE still draws at the
projected paper point. Grid snap and the paper-space rubber-band origin are
suppressed for that pass; results outside the clip are rejected so no snap
lands on a clip cut. Locked viewports snap like unlocked ones.
No projected geometry is rebuilt per pointer event: the wire set is the
renderer's resident per-viewport Arc (`model_wires_for_viewport_arc`) and the
broad phase reuses the shared interaction index over it.
The result is merged with the ordinary paper-sheet snap by `snap::merge_snap`,
which applies the engine's own priority-then-distance rule, in both the
cursor-move and the click paths.
`SnapResult` gains Copy fields `viewport: Option<Handle>` and
`source: Option<DimensionAssociationSource>`; the engine fills `source` from
the owning wire wherever it knows it. `AcceptedSnap::from_snap` converts a
result plus frame into the shared PR2/PR3 contract type.
Co-Authored-By: Claude Fable 5.1 <[email protected]>