Entering a thickness (DXF 39 extrusion) on a wide polyline now works end
to end:
- The properties Thickness field is a General-group common prop, so it is
routed to apply_common_prop (set_entity_thickness) instead of the
geometry setter, which ignored it — the value was silently dropped and
reverted to 0.
- A wide polyline extrudes its whole band into a 3-D tube (outer + inner
walls with top and bottom caps), not just its centreline.
- The tube is real 3-D geometry, not a flat draw-order hatch: it renders
solid in every view mode with true depth, so nothing shows through the
walls. The flat 2-D band fill is skipped for a thickened polyline.
- A fill whose triangles span depth keeps its real depth (no draw-order
bias), so the extrusion's own edge wires stay in front of the surface
and remain visible instead of only the silhouette.
- The corner and cap edge lines read black, framing the coloured tube.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route the MTEXT editor through the shared in-canvas modal frame with an
Apply-at-top toolbar (style-manager style): Apply commits and keeps the
editor open (creates on first apply, updates in place after), the ✕
cancels. Give every modal a bottom-right corner resize grip driven by a
shared grow-only `modal_resize` delta that resets with `modal_offset` on
open/close, so each dialog opens at its natural size and can be dragged
larger — style managers included.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A legacy leader's Lines & Arrows / Text / Fit rows are now editable
per-object overrides of its dimension style, stored in the standard
ACAD_DSTYLE XDATA record (new src/entities/dim_override.rs codec). Arrow
block, arrow size, dim-line lineweight, text offset, vertical text
position and overall scale each prefer an override over the style; the
renderer honours arrow size / block / overall scale and the dim-line
lineweight, so an edited leader redraws at its new arrow and weight.
XDATA edits go through a new dispatch::set_entity_xdata, which registers
the application in the APPID table (the DWG writer drops records for an
unregistered app) and drops that app's stale verbatim EED block (which
otherwise wins over the structured record on a DWG save), so edits and
clears round-trip. The hyperlink editor moves onto this path too.
Dim-line colour stays read-only: it would live in the leader's
override_color, which the file layer doesn't serialise, so making it
editable would silently lose the pick on save. Picking "Closed filled"
writes an explicit null-handle arrow override rather than clearing, so
it sticks even when the style's arrow differs; an unparseable numeric
entry is ignored instead of wiping the stored override.
Note: the DXF entity writer only emits XDATA for hatches, so these
overrides (and hyperlinks) currently persist on DWG save but not DXF —
an acadrust-side gap to close separately.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The General-group Hyperlink row becomes an editable text field; committing a
value writes the standard PE_URL XDATA record (an empty value clears it). Adds
scene::view::dispatch::set_common_xdata, which rebuilds an entity's XDATA to
replace or remove a single application record — ExtendedData only appends, so a
plain add_record would leave a stale duplicate the reader would pick first.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Render: annotative-ness is resolved centrally in scene::annotative::is_annotative
(per-object context dictionary, legacy XDATA, or annotative style) so the bake and
the properties panel agree. Text, dimensions, tables and blocks now display at the
current annotation scale in model space. An annotative block scales as one uniform
unit about its insertion point — its internal geometry and attributes are carried by
that scale instead of being scaled individually, fixing the block-attribute size
regression. Tables scale their column/row/margin geometry.
Properties: the handle- and flag-backed rows are now editable pickers — MLEADER
multileader style / text style / arrowhead / leader linetype, General Material, and
Plot style (named plot-style mode only; the color-dependent mode stays read-only,
which is correct). A picked name is resolved back to its handle in the update loop,
where the document is in scope. Annotative Yes/No and the annotation-scale row are
shown per object.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Typed values were quantized to the f32 grid before reaching the
entity (e.g. a length of 2000.8 committed as 2000.8020), worsening
with magnitude. The interaction point layer was all glam::Vec3 (f32).
Two-layer fix:
1. Coordinate backbone -> f64. last_point, last_cursor_world,
dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord,
the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance
paths, command-line coordinate entry and the coordinate readout all
compute in f64. Screen/GPU/alignment stays f32 (snap engine, render
geometry, projection, UCS-icon/viewcube drawing) with casts only at
those boundaries.
2. Per-command committed storage -> DVec3. on_point already received a
DVec3, but many command impls narrowed it back to f32 (let pt =
pt.as_vec3(); Vec<Vec3> fields) and re-widened at build, quantizing
the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN,
INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud,
wipeout, attdef, table, tolerance, array center, mview, plot_window,
plus the QDIM_PLACE handler and qdim_collect_points. Preview,
hit-test and rubber-band paths keep f32 at the boundary.
Hatch manual boundaries are left for a follow-up: HatchModel.boundary
is structurally Arc<Vec<[f32;2]>> and needs a model-level widen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dragging text by a grip hid it until release. The dragged entity is
hidden from the base wire set and shown as a preview overlay, but its
SDF glyph quads (text_verts) were never uploaded for the preview, so
the text vanished mid-drag.
Add a per-frame preview_text GPU buffer (sibling to preview_wires). A
rigid whole-entity move of pure text (TEXT/MTEXT, Square insertion grip)
slides the drag-start glyphs by the drag delta instead of re-shaping the
run every move; reshape grips (MTEXT width), dimensions and wire
entities re-tessellate and feed the same buffer.
Also fold preview_text into render_signature: a pure-text slide leaves
preview_wires empty, so without it the scene-render cache froze the
dragged text at its first frame (grip moved but text stayed, then
jumped to place on release).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Space now acts like Enter in the command line so commands advance
token-by-token (CAD convention). The whole value is handed to the submit
path, which tokenises multi-token lines, so a pasted or API-fed
`LINE 0,0 10,10` runs each space as a step separator instead of stopping
at the first token. A leading `>` switches to literal-space mode (for a
text string, a path, etc.) and is stripped on submit. All command-line
entry is shown uppercase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Current Vertex row is now a ◀ / ▶ stepper (new PropValue::Stepper)
showing "i / N". Stepping it moves the panel's focus through the
polyline's vertices — Vertex X/Y and the per-vertex start/end widths show
and edit the focused vertex instead of always the first — and the focused
vertex's grip is drawn hot (filled) in the drawing so it's visible while
navigating. Focus wraps around and resets to the first vertex when the
selection changes. Adds Area and Length to the LwPolyline Geometry group
(Polyline2D already had them). Applies to LwPolyline and Polyline2D.
The focused vertex is threaded to the per-entity property builder/editor
through a dispatch thread-local (like the curve-tolerance override), so
the PropertyEditable trait signatures are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Autocomplete suggestions came only from all_registered_command_names() —
the compile-time inventory registry returning &'static str — so
runtime-loaded plugin commands could never appear. Typing a plugin's
command prefix (e.g. `LS_`) showed nothing even with the plugin loaded
and its ribbon tab active; dispatch worked, only discovery was blind.
Collect each enabled plugin's ribbon ToolDef command ids plus its
manifest command_prefixes into a dynamic candidate pool
(OwnedRibbonGroup::command_ids → PluginManager::command_names →
plugin_command_names), refreshed in rebuild_ribbon_modules on startup
load, settings reload, and every enable/disable toggle. ranked_matches
now merges this pool with the static registry and returns owned strings.
A plugin.toml `commands = [...]` list for sub-verbs with no ribbon button
(e.g. LS_AUTOLABEL) stays a follow-up — it's a plugin-API surface change.
Closes#272
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Layer Manager table and the ribbon's quick layer dropdown both
showed layers in document (insertion) order. Default the panel sort to
Name so both list layers alphabetically; a header click still re-sorts
by any other column. `apply_sort` reorders the panel's layer vector in
place, which the ribbon dropdown reads, so one default covers both.
With alphabetical ordering a freshly created layer can land anywhere in
a long list, so give the table scrollable an id and scroll its row into
view after creation, keeping the rename prompt visible.
Closes#270, closes#271
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Layer Manager already applied colour/linetype/lineweight in bulk;
extend that to the rest:
- Visibility, lock and freeze toggles now act on every selected layer, not
just the clicked row. The clicked row's new state becomes the target for
all. Transparency edits apply to the whole selection too.
- Clicking a linetype/lineweight combo on a row that is already part of a
multi-selection no longer collapses the selection (the whole row is one
mouse_area whose plain-click select would otherwise reset it), so those
edits stay bulk.
- Ctrl/Cmd+A selects every layer row while the Layer Manager is open, and
all drawing objects otherwise.
Two now-unused Scene layer-toggle helpers are removed (the handlers set the
flags directly and bump once).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Typing a bare system-variable name (MIRRTEXT, FILLMODE, LTSCALE, …) now
echoes the current value and prompts "Enter new value for <NAME> <cur>:",
capturing the next command-line entry as the new value — Enter keeps the
current one, Escape cancels. The single-line "MIRRTEXT 1" form still works.
Mirrors the awaiting_vports pattern: a pending_setvar field on the app,
consumed at the top of on_command_submit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Color/linetype/lineweight/layer are baked into the cached wire geometry
at tessellation time, so a property edit only repaints if the handler
bumps the geometry epoch. The color path did; several siblings only set
tabs.dirty (a save flag) and never re-tessellated, leaving the viewport
showing the stale look.
Route every handle-based edit through invalidate_property_targets
(mark dirty + recolor meshes + bump geometry), and full-bump the
name-based LAYER COLOR change:
- ribbon layer / linetype / lineweight dropdowns applied to a selection
- CHPROP (color/linetype/ltscale/transparency/layer)
- LAYMATCH (MatchEntityLayer)
- LAYER COLOR <name> <aci>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Layer Manager only allowed one layer at a time. Now:
- Ctrl/Cmd-click toggles a layer in the selection; Shift-click selects
the range from the anchor; a plain click selects just one.
- Colour, linetype and lineweight changes apply to every selected layer.
- Delete removes all selected layers at once (reusing the #237 non-empty
warning, now pluralised); layer "0" and the current layer are skipped.
Selection is stored as row indices but re-resolved by name across sorts
and layer-table rebuilds, so it never points at the wrong rows. Modifier
state is tracked via a new SetModifiers message (shift + ctrl/cmd).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Deleting a layer only dropped the layer record, leaving its objects in the
file still tagged with the (now-gone) layer name. Now:
- Empty layer → deleted straight away.
- Non-empty layer → a warning modal ("Layer X is not empty — N objects;
deleting also removes them. Continue?") with Delete Objects / Cancel.
Confirming erases every object on the layer, then removes the layer, as
one undo step. The layer record is dropped before the erase so the
locked-layer guard can't keep its objects.
- Layer "0" and the current layer can't be deleted (reported on the
command line) instead of silently leaving a broken state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Double-clicking a locked object no longer opens its text / attribute /
in-place block editor.
- Clicking a locked object during a command's object-gather no longer
counts as completing the selection (don't set selection_just_completed
on the locked path).
- copy_entities skips locked entities.
- The attribute editor's Apply refuses to write when the block's layer was
locked while the dialog was open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Locked-layer objects were fully editable. Now they stay visible and
snappable but are otherwise protected:
- Not selectable: the five interactive pick paths (box, lasso, single
click, cycling, box-release) plus SELECTALL and QSELECT skip locked
entities.
- Not modifiable (defense-in-depth even if a handle slips through):
transform_entities, erase_entities, apply_grip and the Properties
commit all skip locked entities.
- Faded: entities on a locked layer render dimmed toward the background
(render_style + wire tessellation); toggling lock re-tessellates.
- Snap still works: locked entities are intentionally kept in the wire
set (visibility_ok untouched), so object snap keeps finding them.
- Feedback: a small padlock is drawn by the crosshair when hovering a
locked object, and clicking one prints "Object is on locked layer …".
New Scene::is_layer_locked / locked_layer_name helpers centralise the
check (mirroring layer_hidden).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grow the attribute editor into a three-tab enhanced editor:
- Attribute: the tag/prompt/value list; click a row to select it, edit
its value below.
- Text Options: the selected attribute's Text Style, Justification,
Height, Rotation, Width Factor, Oblique Angle, Backwards, Upside down.
- Properties: the selected attribute's Layer, Linetype, Color,
Lineweight (the colour list also surfaces a non-standard current
colour so it displays and round-trips).
Applying writes value, text-formatting and common-property edits back to
each attribute positionally (guarded on block name + count), with undo
and a repaint. OK/Cancel are replaced by a single Apply button in a top
toolbar that commits but keeps the dialog open, matching the other modal
windows — the frame ✕ closes and discards un-applied edits. The frame
adopts the shared style-window palette, tab styling and sizing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each row in the attribute editor now shows the attribute's prompt — the
text defined on the block's ATTDEF — falling back to the tag when the
block defines no prompt. The prompt is read from the block definition,
since attribute instances carry only tag + value.
Bumps the acadrust pin to pull the matching reader fix: its DWG object
reader was discarding the ATTDEF prompt, so every prompt came back empty.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Double-clicking a block reference that has attributes, or running ATTEDIT
on one, now opens an editor dialog listing every attribute (tag + editable
value) with OK / Cancel. OK writes the values back to the block, with undo
and a repaint; Cancel discards. A block with no attributes still enters
in-place block edit (REFEDIT) on double-click as before. (#192)
ATTEDIT opens the dialog directly when a suitable block is already
selected; otherwise it runs the pick command and the dialog opens once a
block is chosen. This replaces the earlier command-line, per-attribute
prompt flow, which is removed along with its __ATTEDIT__ sentinel path and
the now-unused attedit_set_attrs trait hook; the ATTEDIT command is reduced
to a plain block picker.
The dialog is a tab-scoped in-canvas modal (Plan B): its working copy holds
a document-local handle, so closing that tab or switching away dismisses the
editor rather than risk applying edits to another tab's document.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Selecting an INSERT with attributes now adds an editable "Attributes"
section to the Properties panel — each tag is a row whose value can be
changed inline (Enter commits, undo works, the attribute text repaints).
Previously attribute values could only be reached through ATTEDIT, which
didn't let them be changed (#192).
Attribute tags are runtime strings, so they can't ride the geometry
edit path whose field key is `&'static str`; a dedicated PropValue::
AttrText variant plus PropAttrInput/PropAttrCommit messages carry the
tag. Values are stored verbatim (no expression evaluation, since
attribute text is free-form).
Also guard the panel's in-progress edit buffer: it now only carries
across a rebuild when the selection is unchanged (a commit-triggered
refresh), so a typed-but-uncommitted value can't display or commit onto
a different entity — e.g. two title blocks sharing a REV1 tag. This
closes a latent stale-buffer leak that affected geometry fields too.
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>
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>
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>
The UCS icon now anchors to the projected UCS origin when it is on-screen
(falling back to the bottom-left corner), and becomes a selectable object:
hover highlights it, a click shows draggable grips, and dragging the origin
grip relocates the UCS while the X/Y tip grips rotate it. Drags snap to
geometry/grid through the existing snapper, and the new UCS is persisted on
release. The icon stays selectable while parked in the corner.
Also make the ViewCube compass (N/E/S/W) world-fixed: the cube body still
reorients with the active UCS, but the cardinals project through a camera-only
rotation and snap in world frame, so the directions no longer spin with the UCS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Break up the four giant files (~29k lines total) into focused submodule
directories, one concern per file. Pure mechanical moves — behavior is
unchanged.
- app/update.rs (9650) -> app/update/{mod,viewport,command,style,file,
dynamic,dialog,util}. The 384-arm update_inner match stays in mod.rs
with thin delegating arms; the 66 fattest arm bodies move to topic
methods. Arm set preserved exactly (384 -> 384).
- scene/mod.rs (9017) -> scene/{mod,entity,tess,hittest,layout,paper,
mspace,project,selection,modify,group_layer,preview}. The impl Scene
body is split across files via inherent-impl-per-file; struct, ctor,
fields and load-time helpers stay in the root. fn count 123 -> 123.
- app/commands.rs (6082) -> app/commands/* grouped by command family.
The 233-arm dispatch match becomes source-ordered per-family
dispatch_* handlers, preserving first-match precedence (233 -> 233).
- app/view.rs (4686) -> app/view/{mod,overlay,modal,viewcube,controls}.
Largest file drops from 9650 to 3343. Builds clean; app launches and
loads plugins with no panic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>