Commit Graph
167 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 123d4e9083 feat(properties): make read-only values selectable text instead of click-copy
Replace the click-to-instantly-copy read-only cell with a non-editable but
selectable text field: the user selects the value (drag / double-click /
Ctrl+A) and copies with Ctrl+C, the normal expected gesture. The field
carries the full un-truncated value, styled flat (no input box or focus
ring) so it still reads as read-only but with a visible selection colour.
Keystrokes route to Noop, so it can be selected/copied but never edited.

Removes the PropCopyValue message + handler and the mouse_area copy hook.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 08:14:37 +03:00
Hakan SevenandClaude Opus 4.8 c59a584d14 feat(properties): copy read-only property values to the clipboard
A read-only value cell is now click-to-copy: clicking writes the full,
un-elided value to the system clipboard (copy cursor on hover). One
change in render_ro_row, so it covers every read-only row for every
entity. Adds Message::PropCopyValue routed through iced::clipboard::write.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-03 08:06:00 +03:00
Karim Jerbi 73b05e1dec perf(core): cache ribbon_groups() with OnceLock, avoid per-frame tree allocations
Change CadModule::ribbon_groups() from -> Vec<RibbonGroup> to -> &[RibbonGroup],
backed by a per-module OnceLock cache. The ribbon group tree is static data
(&'static str, Copy enums) that was being fully reconstructed — Vecs, String
clones, enum discriminants — on every call, including all three per-frame
call sites (view(), dropdown_overlay(), style_combo_overlay()).

- 7 built-in modules + 2 plugin templates cache via function-local OnceLock.
  Safe because each is a unit struct with exactly one instance per process
  (plugins run in isolated child processes; see PluginProcess::spawn()).
- SharedCadModule avoids the static pattern entirely, storing groups in an
  instance field (owned.rs).
- IPC runner converts &RibbonGroup -> OwnedRibbonGroup directly via new
  From<&T> impls, avoiding an intermediate clone.
- render_small/render_large and the two make_tool_row closures now borrow
  (&RibbonItem, &[ToolDef]) instead of taking ownership, so the view loop
  doesn't need to clone items to satisfy the old by-value signatures.
- Various match-ergonomics deref fixes (*id, *default, *icon, etc.) from
  the &RibbonItem pattern change.
2026-07-02 21:00:57 +01:00
Hakan SevenandClaude Opus 4.8 d6e5b38847 fix(properties): close color picker when a panel dropdown opens (#235)
The Properties panel color picker is a custom bool-driven dropdown, while
Layer/Linetype/Lineweight are combo_boxes. Combo_boxes close each other on
blur, but nothing closed the color picker — so opening the Layer dropdown
while the color picker was open left both showing and overlapping.

Give every panel combo_box an on_open handler that collapses the color
picker, so at most one panel dropdown is open at a time.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 19:47:32 +03:00
Hakan SevenandClaude Opus 4.8 1614fd9ab2 feat(snap): dashed extension guide line for the Extension osnap (#238)
The Extension object snap worked but drew no tracking path, so it was
easy to miss — users had to keep the cursor exactly on the invisible
extension line without any visual cue.

When an Extension snap is active, draw a dashed guide line from the
endpoint it extends from, through the snap point and a little beyond,
and orient the three-dot marker along that direction. SnapResult carries
the endpoint screen position (filled once the winning snap is known);
the overlay renders the guide.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 19:39:21 +03:00
Hakan SevenandClaude Opus 4.8 1e9de924be feat(command-line): copy/clear history + selectable log (#232)
The command-line history dropdown was display-only, so users had to
screenshot and OCR the log to report output. Make it copyable:

- Render the whole backlog in one read-only text_editor so a single
  mouse drag selects across lines and Ctrl+C copies the span.
- Add Copy (whole log to clipboard) and Clear buttons to the dropdown.
- Gate the Ctrl+C/X/V accelerators on event Status::Ignored so a focused
  text widget's copy/cut/paste wins instead of firing COPYCLIP/CUTCLIP;
  the drawing's clipboard shortcuts still work when it has focus.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 18:17:46 +03:00
Hakan SevenandClaude Opus 4.8 ada9952f06 feat(layers): multi-select layers in the Layer Manager (#236)
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 <[email protected]>
2026-07-01 17:44:19 +03:00
Hakan SevenandClaude Opus 4.8 56e1c03ac2 feat(layers): enforce layer lock (select/modify/fade + cursor badge)
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 <[email protected]>
2026-07-01 13:10:39 +03:00
Hakan SevenandClaude Opus 4.8 7764b6cbf9 feat(statusbar): Select All/Clear All in filter popup, drop osnap settings row
The selection-filter popup now has the same Select All / Clear All header
as the OSNAP popup: Select All clears every exclusion (all types
selectable), Clear All excludes every present type. Each button disables
when it would be a no-op.

Also remove the "Object Snap Settings…" footer from the OSNAP popup.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 09:47:13 +03:00
Hakan SevenandClaude Opus 4.8 d8862ee9cf feat(statusbar): icon toggles, taller bar, centered pills (#216)
The bottom status bar was hard to read: size-10 text labels in a 26px
strip. Make it legible and consistent with the drawing tab bar:

- Height 26 → 30 to match the document tab bar; the command-line input
  pill matches too, so the three strips line up.
- Replace the drafting-aid toggle labels (ORTHO/POLAR/OSNAP/OTRACK/DYN/
  LWT and the OCS pills ISO/QP/FILTER/SC/CLEAN/TPY) with 17px SVG icons;
  the name stays in each pill's tooltip. Twelve new icons under
  assets/icons/status, drawn in the existing 24×24 stroke style.
- Vertically centre every element via center_y, and bump all pill text to
  size 12 with uniform padding plus larger chrome icons (menu, scroll
  arrows, osnap dropdown) so nothing reads smaller than the toggle icons.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 09:33:00 +03:00
Hakan SevenandClaude Opus 4.8 0b60bcc075 feat(attedit): enhanced attribute editor with 3 tabs
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 <[email protected]>
2026-07-01 09:06:03 +03:00
Hakan SevenandClaude Opus 4.8 0eb347bf47 feat(attedit): show attribute prompts in the editor
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 <[email protected]>
2026-07-01 08:23:54 +03:00
Hakan SevenandClaude Opus 4.8 86a59d6c7e feat(attedit): attribute editor dialog for blocks
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 <[email protected]>
2026-07-01 08:05:25 +03:00
Hakan SevenandClaude Opus 4.8 5f2e920b75 feat(properties): show & edit block attributes in panel
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 <[email protected]>
2026-07-01 07:42:50 +03:00
Hakan SevenandClaude Opus 4.8 3da81571a1 fix(ribbon): scrollable layer dropdown, stop mouse leak to viewport (#227)
The ribbon layer dropdown grew unbounded so a long layer list ran off the
bottom of the screen with no way to scroll. Cap the panel height and make
the list scrollable; short lists still shrink to fit.

While a dropdown was open, the 3D viewport beneath it kept tracking the
cursor over the panel's empty areas, and the OS cursor vanished there. In
iced 0.14 mouse_area/opaque only capture button presses, never CursorMoved,
so a higher stack layer can't swallow motion — gate it state-side instead:

- ignore viewport motion in on_viewport_move while ribbon.open_dropdown is set
- suppress the crosshair overlay's Hidden cursor (and crosshair draw) so the
  normal OS cursor shows over an open dropdown
- route all ribbon dropdown backdrops through a shared dropdown_backdrop()

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 00:45:23 +03:00
Hakan SevenandClaude Opus 4.8 57136b9eda fix(textstyle): live-update style manager preview (#102)
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 <[email protected]>
2026-06-29 17:29:04 +03:00
Hakan SevenandClaude Opus 4.8 f88c2f8327 style(ribbon): cargo fmt after PR213 merge
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 <[email protected]>
2026-06-29 01:19:05 +03:00
Karim Jerbi 63c13f0f63 refactor(ribbon): extract repeated inline color literals into named constants
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.
2026-06-28 22:59:14 +01:00
Karim Jerbi 1e8a98d2cd fix(ribbon): center section titles within their content width
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.
2026-06-28 22:23:01 +01:00
Kevin GriffinandClaude Opus 4.8 73eabc3aaa fix(shortcuts): honor Cmd as the accelerator modifier on macOS (#202) (#206)
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 <[email protected]>
2026-06-27 19:33:08 +03:00
Hakan SevenandClaude Opus 4.8 098585bb2d feat(viewport): RMB=Enter option + timed right-click
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 <[email protected]>
2026-06-27 16:16:08 +03:00
Hakan SevenandClaude Opus 4.8 63de137106 feat(cmdline): Enter runs closest command match
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 <[email protected]>
2026-06-27 14:45:51 +03:00
Hakan SevenandClaude Opus 4.8 3def2f2034 feat(statusbar): scrollable layout tabs + layout-list dropdown (#199)
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 <[email protected]>
2026-06-27 14:24:47 +03:00
Hakan SevenandClaude Opus 4.8 495b13ec4f refactor(viewport): migrate model-space tiling to iced pane_grid
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 <[email protected]>
2026-06-27 11:50:11 +03:00
Hakan SevenandClaude Opus 4.8 edcf24e324 feat(ucs): interactive UCS icon with origin-snap grips, world-fixed compass
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 <[email protected]>
2026-06-27 01:12:45 +03:00
Hakan SevenandClaude Opus 4.8 a502379048 refactor: group ui/ statusbar and window subdirs
Continue tidying the flat ui/ root:

- ui/statusbar/: statusbar.rs becomes statusbar/mod.rs (its
  crate::ui::statusbar::* paths are preserved) and absorbs
  statusbar_config.rs + statusbar_menu.rs.
- ui/window/: the standalone manager/dialog windows move here —
  about, layout_manager, page_setup, plugin_manager, shortcuts,
  layers, update_notice, open_progress. The crate::ui::LayerPanel
  re-export is repointed so callers are unchanged.

Call sites repointed to the new paths; no compat re-exports beyond the
existing LayerPanel one. ui/ root drops from 21 to 10 flat files with
five subdirs (ribbon, style, popup, statusbar, window).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:52:26 +03:00
Hakan SevenandClaude Opus 4.8 9cd0479047 refactor: group ui/ style+popup dirs, rename cmd_result driver
- Group the flat src/ui/ dir: move the 9 style editors into ui/style/
  (dimstyle, mleaderstyle, mlstyle, plotstyle, textstyle, tablestyle,
  point_style, style_list, style_manager) and the 6 popups into
  ui/popup/ (cycle, isolate, scale, selection_filter, snap, units).
  Call sites repointed to crate::ui::style::/crate::ui::popup::; no
  compat re-exports. ui/ root drops from 36 to 21 files.
- Rename app/cmd_result.rs -> app/command_driver.rs: the file is the
  StepInput->feed_command driver, not the CmdResult type (which lives
  in command.rs). Name now matches contents.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-26 23:34:10 +03:00
Hakan Seven e9cb9af924 Merge PR #180: TTF solid glyph fills, font fallback, Z-order fix 2026-06-26 00:20:59 +03:00
Hakan SevenandClaude Opus 4.8 f00c48bebb feat(viewcube): add compass ring, cardinals, and nav controls
Build the ViewCube out into a full navigation widget:

- Compass ring painted in the cube's ground plane (z = base) so it
  reads as a ground compass and foreshortens with the view.
- N/E/S/W cardinals painted flat onto the ring band; clicking one snaps
  to that side elevation. Added S/W glyphs to the bitmap atlas.
- Home button (top-down view), roll arrows (90° roll about the view
  axis), and four nudge triangles (90° tip/spin) as SVG iced controls
  laid out around the cube. New home.svg.
- WCS / named-UCS selector under the cube, switching the active UCS.

The cube hit area now shares one stack with the control buttons, so a
click on a button is caught by the button while a click on the cube
falls through. Face clicks snap to the highlighted region rather than a
cursor position the viewport's move handler can overwrite.

Camera gains home_view / roll_by / nudge_90; floating viewports drive
the same ops through mutate_active_viewport_camera, which re-encodes the
result to the stored view direction + twist.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 23:46:43 +03:00
Karim Jerbi 627761b629 fix(text): render TTF glyphs as solid fills, fix font fallback, correct Z-order
Text elements previously rendered as hollow wireframe outlines and sometimes
fell back to the wrong system font due to case-sensitive exact matching. Also,
the viewport background grid drew on top of solid entities.

Case-insensitive and partial-name fallback matching (e.g., mapping "arialn"
to "Arial Narrow") is now added to `sysfont::canonical_family_name`, ensuring
text resolves to the correct installed system font.

TrueType font contours are now triangulated into solid fills (`fill_tris`)
using `lyon_tessellation` in `ttf_glyph.rs`. To prevent the GPU from misclassifying
this 2D text as a 3D mesh (which broke flat shading), text tessellation splits
the output into separate outline and fill `WireModel`s.

The background grid is extracted from `SelectionCanvas` into `GridCanvas` and
moved beneath the 3D viewport to ensure proper layering. Finally, `DepthBiasState`
is enabled on all face/fill pipelines to apply a polygon offset, perfectly
resolving Z-fighting between coplanar wireframe lines and solid faces.
2026-06-25 18:17:01 +01:00
Hakan SevenandClaude Opus 4.8 73497850e2 fix(ribbon): float annotate style dropdowns so they aren't clipped (#153)
The text / dimension / multileader / table style combos rendered their
open list inline inside the ribbon row, so the popup was clipped to the
fixed ribbon-row height and got cut off even with only two entries. The
Draw-tab dropdowns and the layer combo avoid this by rendering as
floating overlays.

Render the style list as an overlay too: the combo now draws only its
button inline, and a new `style_combo_overlay` builds the list as a
floating panel that grows to fit its entries, positioned with the
existing `compute_dropdown_left` (which already accounts for
StyleComboGroup widths) and mirroring `layer_combo_overlay`.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 18:35:49 +03:00
Hakan SevenandClaude Opus 4.8 8b1e37e0da fix(web): render OSNAP tooltip caret as SVG, not a tofu glyph (#138)
The OSNAP settings tooltip read "Click ▾"; the ▾ (U+25BE) is absent from the
web font and showed as a tofu box. Tooltips are plain text and can't embed
SVG, so add a rich-body tooltip variant (tip_node) and use the existing SVG
caret (icons::arrow_down) — consistent with the SVG-icon approach the rest of
the chrome already uses for the web.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 14:11:06 +03:00
Hakan SevenandClaude Opus 4.8 69ea050124 feat(pan): interactive PAN mode with left-drag (no middle button needed)
The PAN command/tool dispatched a command nobody handled, so it did
nothing. Combined with no middle mouse button on a trackpad/web client,
there was no way to pan at all (issue 164).

PAN (and the "P" alias) now enters a pan mode: a left-button drag pans the
view, reusing the existing middle-button pan path. Exit with Esc or by
starting another command. While active the crosshair is hidden and the
cursor becomes a hand (open when hovering, closed while dragging). PAN/P
are also added to command-line autocomplete.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 10:03:54 +03:00
Sebastian d0003534a0 Fix 5: Allow backward-compatibility and add plugin-template-v2 2026-06-25 00:02:17 +03:00
Hakan SevenandClaude Opus 4.7 96aaf2a293 feat(snap,pick): eye-relative CPU projection — drop full view_proj at UTM
Route the remaining absolute-coordinate CPU projection through the
relative-to-eye path so it no longer multiplies by a view_proj matrix
carrying a ~1e7 translation (which cancels catastrophically in f32 after
world_offset removal):
- camera pick_on_plane / pick_on_target_plane delegate to
  unproject_on_plane_f64 (rotation-only inverse + f64 eye)
- snapper (world_to_screen), hit_test (click_hit / mesh / hatch), the
  scene mesh/solid hit methods, grid + UCS axes, and polar_constrain_near
  all take view_rot + eye and project relative-to-eye
- compute_grid_step measures the per-unit scale via the rotation-only
  matrix (no eye term needed)

view_proj remains only for conservative CPU frustum-cull / scissor and the
screen-space viewcube / UCS-icon directions. NOTE: SnapResult.world and
the command-point chain are still Vec3 (f32) — a ~0.5 m quantization at
extreme UTM zoom remains; the command layer goes f64 next.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-23 23:33:21 +03:00
Hakan SevenandClaude Opus 4.7 52882749eb feat(overlay): relative-to-eye CPU projection for grips and grid (P5)
Route the on-screen chrome that projects absolute world coordinates
through the f64 relative-to-eye helpers so it stays glued/jitter-free at
UTM scale after the world_offset removal: grips (grips_to_screen) and
grip hit-testing (find_hit_grip) now project from the f64 GripDef.world
via Camera::project_f64; the model grid and its UCS axes carry the camera
eye (DVec3) + rotation-only matrix and a f64 origin, projecting each line
relative to the eye. Snap-marker / OST-point / compute_grid_step still use
the f32 path (single points / spacing — negligible) and can follow.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-23 22:57:31 +03:00
Hakan SevenandClaude Opus 4.8 481adba66b fix(ucs): grid axis lines and grid snap follow the active UCS
Two leftovers from the UCS grid work:
- The coloured X/Y/Z axis lines (draw_axes) were drawn at the world
  origin along world axes; they now run through the UCS origin along the
  UCS axes, matching the grid lines.
- Grid snap rounded the cursor on the world XYZ lattice; it now rounds in
  the UCS grid frame (to-UCS, round, back-to-world) so it lands on the
  grid the user sees. The UCS origin (wire space) + rotation reach the
  snapper via DocumentTab::ucs_grid_basis; identity outside model space.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 11:27:34 +03:00
Hakan SevenandClaude Opus 4.8 88bf2e57dc feat(ucs): align the model grid to the active UCS
The grid ruled along world X/Y/Z through the world origin. It now rules
along the active UCS axes through the UCS origin (passed in render/wire
space), so it lines up with the user's coordinate system. Paper space
stays plain WCS; identity UCS reproduces the old world-aligned grid.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 10:03:35 +03:00
Hakan SevenandClaude Opus 4.8 4aa1e90dcf feat(ucs): central WCS↔UCS converter; adopt file UCS, drive readout + icon
Introduce one bridge between WCS (how geometry and the file are stored)
and the active UCS (the coordinate system the user works in), so every
UCS-aware system routes through it instead of re-deriving axis math.

- UcsXform (app/helpers.rs): the converter — to_wcs/to_ucs (points),
  vec_to_wcs/vec_to_ucs (directions), axes(), is_identity(). Orthonormal
  axes, so the inverse is the transpose. The old ucs_to_wcs / ucs_rotate_vec
  free fns now delegate to it (one implementation).
- DocumentTab::ucs_xform() builds it from the tab's active_ucs (None = WCS).
- DocumentTab::adopt_active_ucs_from_header() loads the document's saved
  current UCS (header model-space UCS) into active_ucs on open, so the file's
  coordinate system is live immediately; called from both load paths.
- Status-bar coordinate readout reports the cursor in the active UCS.
- UCS icon tripod projects the active UCS axes, so it rotates to the UCS
  instead of always showing world X/Y/Z.

First slice of routing the app's coordinate systems through the converter;
input/picking, grid, ViewCube, and the remaining readouts follow.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 09:57:39 +03:00
Hakan SevenandClaude Opus 4.8 26479b6faa fix(grid): independent, clipped grid per viewport
Grid (and grid-snap) state was shared through the global show_grid flag, so
toggling it in model space also turned it on in paper space, and a floating
viewport's grid painted across the whole paper — and beyond the 3-D view when
zoomed in.

- Store grid/snap per view: a model tile in model space, the active floating
  viewport or the layout's sheet viewport in paper space, round-tripped via the
  viewport status flags.
- adopt_view_display on layout switch and viewport enter/exit so the live
  toggle follows the view you are in.
- Render one grid per view (paper_viewport_grid_views): the sheet over the
  paper, each floating viewport clipped to its screen rectangle.
- Clamp the grid clip to the widget so an overflowing viewport rectangle never
  paints grid outside the 3-D view.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-22 00:18:01 +03:00
Hakan SevenandClaude Opus 4.8 d36cc9786f fix(web): render OSNAP menu symbols as SVG instead of font glyphs
The object-snap menu drew each mode's marker as a Unicode geometric glyph
(◻ △ ◯ …). The web build bundles only Fira Sans, which lacks them, so they
rendered as tofu boxes (#138). Add an SVG per snap mode under
assets/icons/osnap, map them via icons::osnap, and draw them with the same
tinted-SVG path the rest of the chrome uses — font-independent on every
platform.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 14:27:59 +03:00
Hakan SevenandClaude Opus 4.8 3fc51af41c fix(ribbon): anchor tooltips to the right of their button
Ribbon tooltips were centered below the button, so the cursor resting on
the button covered the tip text. Anchor them to the right instead, clear
of the cursor. (#143)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-20 11:59:20 +03:00
Hakan SevenandClaude Opus 4.8 36d5891572 fix(layers): freeze toggle in ribbon dropdown + sortable manager columns
Ribbon layer dropdown rows were a single button, so a click on the
visible/freeze/lock icon fell through to the make-active handler instead
of toggling that state. Split the row: each icon is its own toggle
button, only the swatch+label selects the active layer. Toggle handlers
now resync the ribbon so the icon updates while the dropdown stays open.

Layer Manager columns are now sortable: clicking a header sorts by that
column, clicking again flips direction (SVG arrow indicator). Sort is
reapplied on every panel sync so it survives edits, preserves the
selection by name, and keeps the ribbon dropdown order in step.

Fixes #133

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-19 20:39:09 +03:00
Hakan SevenandClaude Opus 4.8 9eb805df23 feat(ui): unified viewport control chip + per-tile grid rendering
Move grid and grid-snap toggles off the status bar (and its customization
menu) into the top-left viewport chip, now a single container holding the
horizontal/vertical split buttons, render-mode picker, and grid/snap
toggles, sized to the render-mode picker. Add grid + magnet (snap) icons.

Render the grid per model tile from each tile's own grid_on, in its own
bounds and camera, instead of a single grid in the active tile. This stops
the grid flickering on/off as the cursor crosses panes or leaves the
viewport, and lets each pane show its own grid independently.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 23:29:34 +03:00
Hakan SevenandClaude Opus 4.8 02b0fc4dcf chore: drop brand names and orphaned PICK_HIGHLIGHT colours
Neutralise vendor references in comments to plain behavioural descriptions
(object acquisition / object snap). Remove the PICK_HIGHLIGHT and
PICK_HIGHLIGHT_DIM wire colours: their only consumer was the stripped
storm-sewer add-on, and the retained plugin acquisition pathway draws the
ObjectPick snap glyph and per-command hover previews instead, so the
constants are dead.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 21:28:07 +03:00
Hakan SevenandClaude Opus 4.8 5a36494ed7 chore: clear compiler warnings
Remove accidental dead code (unused imports, the superseded paste tool()
helper, a never-read TORIENT handles field, a duplicate let binding, an
unneeded mut) and mark intentional-but-unwired scaffolding with
allow(dead_code): the web-only save_to_bytes, the plugin loadable() probe,
the planned PICK_HIGHLIGHT colours, the DynFieldSpec label, and the layout
ribbon helpers. Native build is now warning-clean apart from the upstream
nom/quick-xml future-incompat notices.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 21:18:09 +03:00
Hakan SevenandClaude Opus 4.8 cdd72c62ca fix(dyn): keep angle input box clear of the snap point near 0°
The DYN angle box was placed at full cursor radius along the mid-angle
direction. With a near-zero sweep that direction collapses onto the
cursor ray, planting the box on top of the cursor / snap point and
hiding it. Pull the box back along the ray and offset it to the side
opposite the distance box so the snap point stays visible. (#124)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 20:45:04 +03:00
Hakan SevenandClaude Opus 4.8 bf30e57186 fix(web): force Advanced shaping for dynamic-input canvas text
Dynamic-input value boxes, the prompt pill and the UCS axis labels are
drawn with `frame.fill_text`, whose `canvas::Text` defaults to
`Shaping::Auto`. Auto picks Basic shaping for ASCII-only strings, which
renders on desktop but draws nothing on the web (wgpu/webgl) backend, so
the all-digit distance/coordinate boxes and X/Y/Z labels were blank
(the angle box rendered only because its `°` upgraded it to Advanced).
Typed values landed correctly but were invisible. (#117)

Set `shaping: Advanced` on every canvas fill_text in the overlay.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 13:34:03 +03:00
Hakan SevenandClaude Opus 4.8 864dd6cef7 fix(web): render UI icons as SVG instead of font glyphs
UI chrome (dropdown carets, undo/redo, checkmarks, close/menu/osnap,
layer/file-menu icons) and ribbon tool icons were drawn as Unicode
glyphs. Desktop fills missing glyphs via system-font fallback, but the
web build bundles only Fira Sans with no fallback, so they rendered as
empty tofu boxes (issue #116).

Add a shared src/ui/icons.rs (SVG-backed, tintable) plus bundled assets,
and switch every glyph site and IconKind::Glyph tool def to SVG.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-18 12:29:32 +03:00
Hakan SevenandClaude Opus 4.8 157412419e feat(plugin): uninstall + clean upgrade/reinstall of external plugins
Add an Uninstall button on installed external plugin cards that deletes
the package folder (effective next restart — the library stays resident
for the session). Reinstalling or upgrading via the marketplace now
clears any stale native library with a different name before writing the
new one, so the loader always picks the freshly installed binary.

Part of the #100 extensibility epic (phase 2).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-17 15:57:44 +03:00