- 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.
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.
Point input now keeps an `AcceptedSnap` alongside each point a command
collects: the paper point, the model point it came from, the viewport and
frame it was seen through, and the geometry identity the snap engine
attributed. Interactive picks record in the click handler, typed / dynamic /
headless points record in `feed_command`, and the list is cleared whenever a
command starts or the active space changes. Commands that don't care keep
consuming the plain paper point unchanged; PR2 reads the model point and
frame, PR3 the source.
Dimension association inference is suppressed when the dimension's points came
through a viewport. Inference matches geometry against the dimension's own
definition points in whatever space it is handed, so paper definition points
projected from a viewport would associate with unrelated paper-sheet geometry
at those coordinates, or match model geometry using paper coordinates. An
empty source list makes `attach_dimension_association` a no-op. PR3 replaces
this with real viewport-aware associations.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
**TEXTFILL marked the drawing modified.** It is an application preference —
it lives in a process global and is snapshotted into settings.json — but it
was missing from the list of names that persist, so setting it dirtied the
open drawing instead and never reached the settings file through that path.
**LTSCALE and CELTSCALE validated differently depending on the door.** The
standalone commands reject a value of zero or less, because a non-positive
linetype scale degenerates every dash pattern in the drawing. `SETVAR LTSCALE
-5` accepted it. A test drives both entry points for both variables and fails
on the SETVAR path without the fix.
**ISAVEBAK, SAVETIME and FILEASSOC were missing from the autocomplete
registry** although all three dispatch, so the command line never offered
them.
**`bg_color` and `paper_bg_color` were dead fields**, always written as `None`
and ignored on load since the model-space configuration superseded them.
Removing them is safe: the config is `#[serde(default)]` and ignores unknown
keys, so existing settings files simply stop carrying them.
One thing the survey flagged that turned out not to be a bug: on the Display
page the model background row hides itself outside Custom mode while the
paper and desk rows do not. That asymmetry is correct — the canvas mode
governs only the model background, and the other two apply in every mode.
Co-Authored-By: Claude Opus 5 <[email protected]>
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 <[email protected]>
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 <[email protected]>
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 <[email protected]>
Model space appearance now follows the active UI theme instead of a
hardcoded dark background. Canvas mode is user-selectable (Match Theme /
Classic CAD Dark / Custom RGB), with matching controls for the paper
sheet and desk surround backgrounds and grid opacity.
Implements industry-standard CAD system variables, all settable via
command name, SETVAR, and the Options dialog:
- COLORTHEME (0/1 dark/light UI scheme, preserves custom palettes)
- COLORSCHEME (interactive named-theme selection)
- BACKGROUND (THEME/CLASSIC/DEFAULT/DESK presets, RGB triplets)
- SELECTIONAREA, SELECTIONAREAOPACITY (translucent marquee fill)
- WINDOWSAREACOLOR/WINDOWAREACOLOR, CROSSINGAREACOLOR (ACI or theme)
- SELECTIONEFFECT, SELECTIONEFFECTCOLOR (GPU highlight pass, driven by
selection_generation invalidation — no retessellation)
- SELECTIONPREVIEW (bit 0 = idle rollover, bit 1 = in-command hover)
- GRIPSIZE, GRIPCOLOR, GRIPHOT, GRIPHOVER (grip overlay styling)
INSERT enumerated all block names in a single prompt string, causing unbounded overlay growth and full-screen occlusion in drawings with many blocks. Replace prompt enumeration with a ranked, incrementally filtered suggestion model: BlockPicker maintains precomputed lower/upper caches and usage ranks (MRU/frequency), filters via O(n) substring scan and partial sorting (select_nth_unstable) to O(k), bounded to CLIPROMPTLINES.
Introduce CLIPROMPTLINES (Integer 0-50, Registry, default 3) for the number of temporary prompt lines above the command window; persist in UserSettings, expose via SETVAR and direct entry, enforce in CommandLine::view. Suggestion count is in direct relation to CLIPROMPTLINES (limit = clip, capped to 50).
Make search live without requiring Enter: add CadCommand::on_live_input, implement for INSERT/MINSERT via picker.set_needle, and wire to command-line input paths (Message::CommandInput, on_command_append_char, on_command_backspace). Updates use upper/lower caches and partial sort for <0.2ms per keystroke on 10k blocks, with debounced block-usage persistence (MRU cap 20, freq cap 200) to avoid I/O thrash. Correct tri_count test helper to use display_from_solid.
Implement the AutoCAD compatible HATCHTOBACK (HB) command and enhance the interactive DRAWORDER command pipeline with comprehensive verb selection and viewport entity picking.
Key implementations and architectural details:
1. HATCHTOBACK command execution
* Isolates entities belonging to the active layout block record or global space.
* Filters out entities residing on locked layers using an efficient hash set lookup.
* Records targeted delta undo operations on SortEntitiesTable objects (~100 bytes) rather than snapshotting full document structures.
* Performs granular draw depth cache invalidation via Scene::invalidate_draw_depth, preserving existing geometry tessellations and spatial indices.
* Emits localized user notifications when no qualifying hatch entities are found.
2. Interactive DrawOrderCommand state machine
* Supports preselected entities as well as interactive on demand selection gathering.
* Implements subverb selection supporting single character shortcuts (A for Above, U for Under, F for Front, B for Back) and case insensitive keyword parsing.
* Enables interactive reference object picking directly in the viewport via needs_entity_pick and on_entity_pick callbacks.
* Supports hexadecimal handle specification directly from the command line interface.
3. Ecosystem and system integration
* Bumps DEFAULT_ALIASES_VERSION from 2 to 3 to register the HB alias while preserving existing user overrides.
* Integrates HATCHTOBACK and HB into dynamic autocomplete registries.
* Adds localized message strings across all 20 Fluent translation catalogs.
* Corrects hatch boundary serialization flags to ensure outermost boundary rings consistently carry the external attribute.
F8 reached ORTHO as an ordinary command, and dispatching a command tears
down whatever is running first: the active command dropped, its preview
cleared, its base point forgotten. So pressing F8 partway through a LINE
abandoned the line, and during a MOVE it took the dragged ghost with it.
A function key gets pressed mid-command — that is what it is for.
Toggles that only flip a flag now skip the teardown: ORTHO, GRID, SNAP,
POLAR, and the drafting-settings popup. None of them starts a command,
opens a document or reads geometry, so there is nothing for the teardown
to protect.
F7, F9 and F10 came through the same path and were broken the same way;
they are fixed with F8. F3, F11 and F12 already had messages of their
own and were never affected.
Closes#677
Co-Authored-By: Claude Opus 5 <[email protected]>
The status-bar units button wrote INSUNITS, which nothing on screen reads.
INSUNITS only tells the drawing how to scale content arriving from
elsewhere, so picking a unit relabelled the drawing and changed nothing
visible — the selector looked broken because the variable it moved was
invisible.
Point the button at LUNITS instead, the setting that decides how every
coordinate and distance is written, and give the three questions the word
"units" runs together a place each:
- The button offers the five length formats, each shown beside a sample of
itself, so Engineering and Architectural are told apart by sight.
- UNITS / DDUNITS opens a real dialog: length and angle format and
precision, the direction angle zero points, whether positive angles run
clockwise, and the insertion unit. Its sample comes from the same
formatters the drawing uses, so it cannot drift from the readout.
- DWGUNITS converts. Model geometry, viewport framing, the header's length
defaults and each dimension style's scale all move together in one undo
step; paper space is left alone, since the sheet is still the same sheet.
Locked layers are converted too — a half-converted drawing would be at
two scales at once.
The coordinate readout formatted at four decimal places whatever the
drawing asked for; it now goes through the same format_length the
properties panel uses. That made the angle formats reachable in turn, so
degrees-minutes-seconds now carries the d/'/" marks that say which
convention it is, and surveyor's units are written at all.
Typed input is still plain decimal: a length the readout writes as
5'-6 1/2" cannot yet be typed back. ANGBASE and ANGDIR are stored but not
yet applied to displayed directions.
Closes#668
Co-Authored-By: Claude Opus 5 <[email protected]>
Drawings arrive from elsewhere carrying someone else's layer names, and
folding them into a set of house layers meant renaming and merging by
hand, one layer at a time.
LAYTRANS reads a layer set out of another drawing and moves this one onto
it. Given a file it maps every name the two already share and translates
at once, which is all most incoming drawings need. Bare, it opens a
translator: the drawing's layers on one side, the loaded set on the
other, mappings built up between them and applied only on Translate, so a
mapping can be reconsidered and the whole translation is a single undo
step. Mappings can be saved and read back, objects can be forced to
ByLayer so the target layer's properties are what actually shows, and the
result can be written out as a log.
The move itself is the one LAYMRG already made for a single pair, so
LAYMRG now goes through it too rather than keeping its own copy.
Three things fell out for free or nearly so. A standards file is a
drawing, so .dws and .dwt need nothing but a place in the file filter.
The entity list is flat and holds block definitions, so objects inside
blocks travel with everything else instead of being stranded on a layer
that no longer exists. And a target layer that this drawing already has
is left alone: the drawing has an opinion about it, and restyling it
would reach past what was asked for -- only newly created targets take
the standard's properties.
Mappings are saved beside the drawing rather than inside a DWS. That is
where AutoCAD keeps them, but putting them there would mean inventing a
place in the drawing that nothing else reads back.
Closes#624
Co-Authored-By: Claude Opus 5 <[email protected]>
The ribbon offered four styles and dispatched each one's id as a command.
Two of those ids were not visual-style commands: "Shaded" carried SOLID,
which draws a 2D filled polygon, so choosing it started a draw command;
"Hidden" carried HIDDEN, which matched nothing at all, so it did nothing.
The intent had been for the button to fire its tool's event, but the
dropdown never reads one -- it dispatches the id.
Behind that sat two generations of the same feature. The render-mode
picker offered the seven styles a viewport can actually be drawn in;
everything else -- the ribbon, VSCURRENT, SHADEMODE, VISUALSTYLES and a
handful of bare verbs -- went through a binary wireframe-or-shaded flag
that could only approximate them, reporting "Hidden (shown shaded)" and
"X-Ray (shown as wireframe)" when asked for something it had no way to
draw. Four descriptions of one choice, each drifting on its own.
There is one list now. Each style names itself once -- mode, label, icon,
and the command that applies it -- and the ribbon, the picker, the
VISUALSTYLES verb and the interactive prompt all read from it, down to
the line that lists the choices, so what is offered cannot disagree with
what works. Keywords are the render modes' own names, since there is one
set of styles left to name.
The binary path is gone rather than kept alongside: its message, its
module event, the tool definitions that produced it, the ribbon's
special-cased highlight arms, the bare style verbs and the older keyword
spellings. X-Ray goes with it -- no render mode draws one, and it was
already coming out as a plain wireframe. Three files that had been left
holding a single icon constant each fold into the list.
Note: ModuleEvent::SetWireframe leaves the plugin API with it.
Closes#621
Co-Authored-By: Claude Opus 5 <[email protected]>
Only Previous and Last were on offer, so Window, Crossing, Fence, the two
polygons and All could not be reached at all -- and with no keyboard to
type them into, the buttons were the only way in.
Window and Crossing fix the sense of the next box. Dragging derives that
from the direction the corner travels and would overwrite it the moment
the corner moved, so the derivation stands down while a keyword holds it.
Add and Remove decide what a pick does for the rest of the round: Remove
subtracts the way Shift already does, rather than through PICKADD, which
decides whether a pick *replaces* the set and so would have wiped
everything gathered so far. Both are dropped when the next command
starts, since neither outlives the selection that asked for them.
Fence and the polygons are picked point by point, which TRIM and EXTEND
already ask for. That gesture -- the points, the rubber-banded dashed
preview, whether the path closes -- moves into one place both bind to,
and the two preview builders duplicated in trim.rs go with it.
A fence is not the polygon test with crossing set: that closes the point
list and counts whatever lies inside, so a line drawn past a row of
objects would take everything behind it too. It gets a hit test that only
counts a real intersection, and skips the area-based geometry entirely.
The lasso gathered its hits from wires, hatches, block hatches, meshes and
block meshes inline; the new modes need the same list, and a second copy
would drift. Both now come through `Scene::path_hit_handles`.
Closes#596
Co-Authored-By: Claude Opus 5 <[email protected]>
Add a searchable block palette with previews, file import, collision-safe nested definitions, and dependency/table-record import. Keep palette thumbnails current across drawing switches and block-definition edits.