Commit Graph
1070 Commits
Author SHA1 Message Date
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 <noreply@anthropic.com>
2026-06-25 14:11:06 +03:00
Hakan SevenandClaude Opus 4.8 df0e283eba feat(commands): interactive value prompts for PDMODE/PDSIZE/LTSCALE/CELTSCALE (F4)
Bare PDMODE / PDSIZE / LTSCALE / CELTSCALE now enter a generic
ValuePromptCommand front-end that prompts for the value and delegates to the
existing inline `<name> <value>` handler via CmdResult::Dispatch, so they
prompt step-by-step like UCS. A bare Enter reports the current value;
`PDMODE 3` still works as one typed / headless line.

Only pure single-value setting commands are converted. Dialog-opening
commands (LAYER, PURGE, DIMSTYLE, STYLE …) intentionally keep their
bare-name behaviour (open the panel), and subcommand-tree commands keep
their inline handlers — all already typable with arguments since F1.

Adds headless test value_prompt_commands_inline_args. Fourth phase of the
unified step driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:35:25 +03:00
Hakan SevenandClaude Opus 4.8 52f6aed2f2 feat(cmdline): type whole command lines with spaces; unify with headless (F1, #169)
Space is now a literal character in the command line, so a whole command
line — `UCS Z 90`, `LINE 0,0 10,10`, `PDMODE 3` — is typed before Enter
instead of the first Space submitting the buffer. Enter tokenises the line
and runs it.

The command line and the headless automation feeder now share one runner,
`run_command_line` (moved to the non-wasm-gated cmd_result.rs): a single or
inline-argument command dispatches as-is; a multi-token line whose first
word starts an interactive tool feeds the rest as steps; plugin commands
get the whole line first (#162). The shared feeder is now UCS-aware —
typed coordinates are interpreted in the active UCS in both the GUI and
headless. `run_headless` is a thin wrapper over it.

A free-form text command (TEXT / MTEXT / a name with spaces) still receives
the whole line as one input, via the existing `wants_text_with_spaces`
guard. Unfocused Space still repeats the last command.

Closes the command-line half of #169 generically for every command, not
just UCS. Third phase of the unified step driver: every input source funnels
through feed_command.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:16:28 +03:00
Hakan SevenandClaude Opus 4.8 36d2f33301 feat(ucs): interactive UCS command — UCS Z 90 typable everywhere (F2, #169)
Bare `UCS` now enters an interactive front-end (UcsCommand) that gathers the
option then its value as separate steps, instead of the command line
submitting `UCS` alone on the first Space. The front-end delegates execution
back to the existing inline `UCS <option> …` handler via a new
`CmdResult::Dispatch(String)`, so the coordinate-system math and the
save/persist path are untouched.

Now `UCS Z 90` works the same from the command line (Space-separated),
headless automation, and any source that feeds the active command — closing
the input half of #169. Inline `UCS Z 90` (one-shot / paste) still hits the
inline handler directly.

Second phase of routing input sources through the unified step driver: the
interactive command is driven by feed_command like any tool. Adds headless
test ucs_interactive_inline_args.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:36:49 +03:00
Hakan SevenandClaude Opus 4.8 5fdd212592 refactor(commands): unified StepInput → feed_command step router (F0)
Introduce the single place every input source funnels through to drive the
active command's step machine: a `StepInput` vocabulary (one variant per
`CadCommand::on_*` method) and `OpenCADStudio::feed_command`, which routes
the input to the matching `on_*` and applies the result. Sources keep their
own classification (raw input → which StepInput); only the routing is
centralized.

Migrate the headless automation feeder onto it: feed_active_cmd builds a
StepInput, the terminator sends StepInput::Enter. No behavior change —
automation_ops_round_trip still passes.

Also fixes headless inline-argument loss for plugin commands (#162):
run_headless now offers the whole line to plugin dispatch first (plugins
parse their own inline args, e.g. `HC_PIPE 2B 2C 1.25 0.013`), falling back
to the first-word + fed-tokens path for built-in interactive tools.

First phase of routing all input sources (command line, dynamic input,
plugin API, viewport clicks) through one driver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 12:24:17 +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 <noreply@anthropic.com>
2026-06-25 10:03:54 +03:00
Hakan SevenandClaude Opus 4.8 efade01896 fix(offset): pick offset side by containment for closed polylines
OFFSET derived the side from the first segment alone: which half-plane of
that edge's infinite line the pick fell on. For a closed loop that misreads
a pick placed beside the shape — outside the loop yet on the inner side of
the first edge's line — so a counter-clockwise rectangle (RECTANG's winding
when dragged bottom-left to top-right) offset outward by a side pick
collapsed inward instead.

Decide the side for a closed polyline with a point-in-polygon test mapped
through the loop winding (left normal points inward for a CCW loop): inside
→ inward, outside → outward, regardless of winding. Open paths keep the
first-segment heuristic. Adds a regression test covering both windings and
both directions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:49:28 +03:00
Hakan SevenandClaude Opus 4.8 d641ecfea2 fix(viewport): frame paper viewports on the robust cluster centre
Floating viewports on a survey-coordinate drawing rendered blank: the
model camera was correct, but content viewports whose saved view was
rejected auto-fitted onto empty space.

Two causes, both in model-space extents handling:

- compute_model_space_extents tested vertex finiteness in f64 but stored
  f32. A long RAY/XLINE endpoint is a huge-but-finite f64 that overflows
  to inf in f32, poisoning the extents to (inf, inf). Also, the wire/mesh
  AABB paths checked only x while folding y in, so a vertical ray's
  infinite y leaked through. Now every component is validated after the
  f32 cast.

- camera_for_viewport framed its overlap test and auto-fit on the raw
  extents centre. When a drawing has a second, far cluster (a small-
  coordinate legend beside a UTM survey), that centre sits in the empty
  gap, so a valid saved view fails the overlap test and the auto-fit aims
  at blank space. cluster_extent_from_centers already computed a robust
  centre (median of entity centroids) but discarded it; surface it as
  Scene.local_center and frame on that instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 09:31:22 +03:00
Hakan SevenandClaude Opus 4.8 dfdf8f6702 build(deps): bump acadrust to twist-aware VPort view fix
Pull in acadrust 88ea9f0, which fixes the DWG VPort zoom-extents safety
net to be twist-aware. A twisted saved view on a survey-coordinate
drawing no longer has its camera flung to empty space on save/reopen
(the view_center was double-rotated about the origin).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 08:57:38 +03:00
Hakan SevenandClaude Opus 4.8 b4093724f2 fix(plugin): f64 pick coords + harden out-of-process lifecycle
Follow-ups after merging the out-of-process plugin isolation work:

- Convert the out-of-process interactive adapter's `on_point` /
  `on_entity_pick` to `glam::DVec3`, matching the `CadCommand` trait,
  which moved to f64 pick coordinates. The branch predated that change
  and no longer compiled against the trait.
- Contain plugin constructor panics in `export_plugin!`: the constructor
  runs across the C ABI boundary in `ocs_plugin_register`, where an
  unwinding panic is undefined behavior. Catch it and return null, which
  the loader already treats as a failed registration.
- Reap killed runner processes. The spawn-timeout and disconnect paths
  called `child.kill()` without `wait()`, leaving a zombie on Unix until
  the host exited. Route both — and `shutdown()` — through a shared
  `reap()` that kills and waits on a detached thread.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 00:16:08 +03:00
Hakan SevenandClaude Opus 4.8 deec2a8fd3 fix(xclip): keep clipped hatch loops in world_origin-relative space
`clip_hatch_boundary` lifted each loop into the clip polygon's world
space (`+world_origin`) to run the Sutherland–Hodgman clip, but returned
the result in absolute world coordinates. The renderer adds the per-hatch
`world_origin` anchor back in the vertex shader, so clipped hatches with a
large origin (e.g. UTM drawings) were drawn double-shifted and lost f32
precision. Subtract the origin again on the way out so the output stays in
the same precision-preserving anchor space as the input.

Also drop a stale third argument from two xclip tests left over from the
world-offset removal, which had broken `cargo test` compilation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 00:15:58 +03:00
Sebastian d268170790 Fixing plugin_host.rs to double precision 2026-06-25 00:02:17 +03:00
Sebastian d0003534a0 Fix 5: Allow backward-compatibility and add plugin-template-v2 2026-06-25 00:02:17 +03:00
Sebastian 2b898d6f22 Fix 4: shared-memory 2026-06-25 00:01:36 +03:00
Sebastian a93d425002 Fix 3: optimize shutdown times 2026-06-25 00:01:36 +03:00
Sebastian 8eb199bfc7 Fix 2: optimize src 2026-06-25 00:01:36 +03:00
Sebastian d430981da3 Fix 1: ribbon icon not deactivated 2026-06-25 00:01:36 +03:00
Sebastian dcfaed0182 Initial commit 2026-06-25 00:01:36 +03:00
Hakan SevenandClaude Opus 4.8 125b89d8da feat(save-dialog): extend drive navigation to macOS and Linux
Issue #170's fix added a "This PC" volumes root above the drive
letters on Windows. Generalize it so the Save As dialog can switch
volumes on every platform:

- parent_folder() now climbs the filesystem root into the volumes
  list on all platforms (`/` -> Computer), not just Windows.
- available_drives() is platform-specific: Windows probes A:-Z:,
  macOS lists `/` + /Volumes/*, Linux lists `/` + the removable
  mounts under /run/media/$USER, /media/$USER and /mnt. Other Unix
  and wasm fall back to `/`.
- drives_root_label() shows "This PC" on Windows, "Computer" elsewhere.
- read_dir_entries() lists the volumes at the drives root on every
  platform.

Also gitignore the dist/ web build output so the wasm binary is never
committed by accident.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:45:46 +03:00
Kevin GriffinandClaude Opus 4.8 ef229f5d85 fix(save-dialog): allow navigating to other drives (#170)
The custom Save As dialog navigated up the tree solely via
`Path::parent()`, which returns `None` at a drive root (`C:\`).
Navigation therefore dead-ended on the starting volume, so other
drives and USB sticks were unreachable.

Add a synthetic Windows "This PC" drives root above the drive
letters. `parent_folder()` now climbs `…\sub → C:\ → This PC`, and
`read_dir_entries()` lists the available volumes (probed A:–Z: by
stat, no new dependency) when at that root. The dialog's up-button
and path bar route through the same helper and show "This PC".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:45:30 +03:00
Hakan SevenandClaude Opus 4.8 8020286553 chore(release): v0.6.6
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.6.6
2026-06-24 23:22:43 +03:00
Hakan SevenandClaude Opus 4.8 bd1c51c682 fix(viewport): correct ViewCube flip + activation/border for off-screen viewports
ViewCube "already there → flip to opposite" tested the main (paper) camera,
which always looks straight down, so inside a floating viewport clicking
"top" landed on "bottom". Route the check through active_gaze_dir() — the
gaze of the camera the cube actually reflects.

Off-screen viewports:
- The active-viewport outline clamped only its origin while keeping full
  width/height, shifting the whole border inward. Clip it to the visible
  canvas (origin AND size) so it hugs the real viewport edge.
- Double-click activation tested each viewport's full paper rect, so a
  click on empty space beside a viewport that runs off-screen matched its
  partly-off-canvas rect and switched to it. Activation now keys on the
  visible on-screen rect (viewport_at_screen_point), picks the smallest
  (topmost) on overlap, and accepts the border-wire pick only when the
  click also lands inside that viewport's visible rect — so a far
  viewport's edge passing near the cursor can't steal the activation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:14:46 +03:00
Hakan SevenandClaude Opus 4.8 7f36cf512f fix(viewcube): snap a paper viewport square to the UCS, not WCS
Clicking the ViewCube inside a floating viewport set only view_direction,
so the rebuilt camera fell back to WCS-up and the view opened tilted
relative to the UCS-oriented cube the user clicked. Build the full
UCS-aligned orientation (snap_to_direction with the ViewCube UCS) and
decode it to the viewport's stored (view_direction, twist_angle).

twist can't come from -Camera::roll(): camera_from_view rebuilds rotation
with its own yaw convention (atan2(x,-y)) and roll = -twist, which differs
from the camera's internal sync_yaw_pitch. Instead reproduce the decoder's
zero-twist basis and store twist = -signed_angle(up0 -> desired_up).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 22:17:44 +03:00
Hakan SevenandClaude Opus 4.8 3a4d842a7b fix(viewcube): hover/click hit-test must match the drawn cube under UCS
The hover hit-tests used the bare camera rotation (no UCS) and the main
camera even inside a floating viewport, so the highlighted face diverged
from the cursor whenever a UCS was active or in a paper viewport — while
the snap path was correct. Route hover, click-consume and snap through
active_view_rotation_mat(), and compose the ViewCube UCS in its viewport
branch so all three match ViewportData's drawn cam_rotation exactly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:38:51 +03:00
Hakan SevenandClaude Opus 4.8 2507a8fe55 refactor: unify eye() to a single f64 accessor (drop eye_f64)
The whole pipeline is relative-to-eye now, so the f32/f64 split is
vestigial. Merge eye_f64 into eye() returning DVec3; direction-only
callers take .as_vec3().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:38:51 +03:00
Hakan SevenandClaude Opus 4.8 bbf148f601 refactor: remove block_cache local_offset (fully absolute + double-single)
With the relative-to-eye double-single render path, the per-defn
`local_offset` precision origin is redundant: block geometry can be stored
absolute and stays precise via the high/low residual. Remove it
end-to-end:

- Drop the Pass-1 centroid computation, the BlockDefn.local_offset field,
  and the `defn_lo` parameter threaded through emit_wire /
  emit_greeked_text / emit_text_baseline / transform_tangent and the AABB
  helper (transform_offset_aabb_xy → transform_aabb_xy), plus the nested
  parent/child offset re-basing.
- Store text_obb_local as absolute f64 (was offset-relative f32) so the
  greek rectangles stay precise; emit splits to double-single. Give the
  greek-baseline points a low residual too.

Geometry is now absolute + double-single throughout the block cache,
matching the top-level path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:37:52 +03:00
Hakan SevenandClaude Opus 4.8 62c7e20f42 refactor: drop the discarded offset from world_offset_from_centers
The function computed a median-centroid offset that is now always
discarded (geometry is absolute + double-single). Rename it to
cluster_extent_from_centers, return only the cluster span used for camera
fit / cull, and drop the dead centroid computation. Rename the companion
WORLD_OFFSET_SANE_EXTENT const to CLUSTER_SANE_EXTENT.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:22:54 +03:00
Hakan SevenandClaude Opus 4.8 04c93df2c4 refactor: remove the legacy full view_proj (CPU cull now relative-to-eye)
The GPU draws exclusively via the rotation-only `view_rot` + eye double-
single; the full view-projection survived only to project AABBs in the
CPU frustum-cull / scissor / LOD passes. Migrate those to the same
relative-to-eye math (project `point - eye` through `view_rot`) — pixel-
equivalent but precise at UTM scale — then drop the dead uniform:

- pipeline cull/scissor/LOD helpers take (view_rot, eye) instead of the
  full view_proj; render.rs reconstructs eye from the uniform's
  eye_high/eye_low and passes the cropped view_rot.
- Remove the `view_proj` field from the shared Uniforms struct and all six
  shader structs that only declared it for layout (none read it), and
  delete Camera::view_proj(). The struct shrinks by one mat4 on both
  sides, keeping the Rust / WGSL layouts in lock-step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:14:41 +03:00
Hakan SevenandClaude Opus 4.8 5a8ec35f4f fix(blocks): restore per-defn precision after world_offset removal
The world_offset removal stripped two precision mechanisms in the block
cache that were masked while geometry was offset-relative (small coords)
but broke once coordinates became absolute UTM:

- build_defn no longer subtracted the defn's `local_offset` from the
  tessellated wire (it was passed as tessellate's world_offset). The defn
  was stored absolute while expand still re-added local_offset → block
  lines shifted. Restore the subtraction (with a double-single re-split)
  locally in tessellate_sub_local.
- Block fill triangles (greek-text rectangles, block solid fills) were
  batched with f32-only `fill_tris` and an empty `fill_tris_low`, so at
  UTM scale the absolute f32 quantized to ~0.5 m and the greek rects
  sheared. Plumb `fill_tris_low` through BatchEntry and emit the
  double-single residual from emit_wire and emit_greeked_text.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 20:04:41 +03:00
Hakan SevenandClaude Opus 4.8 cc43969ce0 refactor: remove remaining world_offset vestiges in app/
Clean up the last zero-valued world_offset locals outside the scene
tessellation core: the UCS wire-affine / grid-basis origin shift
(document.rs), the coordinate readout (view.rs), grip absolute-apply
(update.rs), the dead properties-edit re-offset guard, the MTEXT preview
glyph-box shift (mtext_editor.rs), and the cross-drawing paste correction
(commands.rs / cmd_result.rs) — including the now-dead
`clipboard_world_offset` field. Coordinates are absolute throughout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 19:01:25 +03:00
Hakan SevenandClaude Opus 4.8 ef8d5b2610 refactor: remove the last world_offset zero-locals
Clean up the remaining `let [ox,oy,oz] = [0,0,0]` bindings and their
zero-valued add/subtract arithmetic across scene/mod.rs (mesh LOD resplit,
model AABB, wide-polyline fills, wipeout boundary, hatch/solid models,
insert/entity AABB), table, multileader and xclip. Coordinates now read as
the plain absolute values they are. No world_offset reference remains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:55:00 +03:00
Hakan SevenandClaude Opus 4.8 5b1a5a71a0 refactor: finish OST f64 integration; drop vestigial _f64 suffixes
- Object-snap-tracking points now project through the relative-to-eye path
  (view_proj_rte + eye) instead of the full view-projection, so they stay
  precise at UTM-scale coordinates — the last consumer of the legacy full
  view_proj outside the CPU frustum-cull / scissor path.
- Rename Camera::project_f64 → project and unproject_on_plane_f64 →
  unproject_on_plane: the f32 twins are gone, so the disambiguating suffix
  is vestigial. eye_f64 keeps its name — the f32 eye() still builds the
  view matrices.
- Remove stale #[allow(dead_code)] from eye_f64 / project / unproject_on_plane
  (all live now).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:41:25 +03:00
Hakan SevenandClaude Opus 4.8 29415cac20 refactor: drop residual zero-subtraction from world_offset removal
In the files where ox/oy/oz came solely from the now-zero offset
destructure (tessellate, block_cache, dimension, leader, text_support),
remove the `let [ox,oy,oz] = [0,0,0]` binding and the `- ox` arithmetic so
the coordinate math reads as the plain absolute value it now is.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:38:23 +03:00
Hakan SevenandClaude Opus 4.8 56d507fd88 refactor: remove dead world_offset plumbing (RTE migration is complete)
Now that geometry reaches the GPU/CPU as absolute coordinates via the
double-single relative-to-eye path, world_offset is always [0,0,0]. Strip
the parameter that was threaded through the whole tessellation / block-
expansion / fallback / camera-decode chain and the ExpandCtx field that
carried it — ~340 references across 20 files, all subtracting zero.

- truck_tess: to_local / to_local_low / tessellate_* drop the offset arg;
  to_local is now a pure double-single split.
- tessellate: offset_to_ds → points_to_ds (pure DS split); tessellate(),
  tessellate_entity(), fallback_geometry(), solid_wire_fallback(),
  entity_aabb(), expand_insert(), expand_block_meshes(), the dimension
  helpers (vec3_local, dimension_snap_pts, …), text_support, leader,
  multileader, image_model, xclip and camera_from_view all lose the param.
- block_cache: ExpandCtx loses its world_offset field.
- Remove the now-dead offset_snap_pts and the unused set_grid_snap.

Behaviour is unchanged (the offset was zero everywhere); the compiler
verifies every call site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:35:02 +03:00
Hakan SevenandClaude Opus 4.8 02d40bd2ec fix(viewport): clip pick/snap to the viewport rect (no off-screen hits)
The GPU scissors floating-viewport content to the viewport rectangle, but
the CPU hit-test/snap wire set runs past it (the frustum cull keeps a
margin and lines extend beyond the rect), so box selection, lasso, click
and object snap could reach geometry clipped out of the viewport.

Reject candidates whose projected screen point falls outside `bounds` (the
pane rectangle), mirroring the scissor. `bounds` is the full canvas in
model space, so every change is a no-op there.

- snap: reject out-of-bounds points in try_pt, the grid block and tangent.
- click_hit / click_hits_all: no hit when the cursor is off-pane.
- box_hit: clamp the selection box to the pane rectangle.
- poly_hit: drop wire points projected off-pane.
- hatch click: no hit when the cursor is off-pane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:45:33 +03:00
Hakan SevenandClaude Opus 4.8 ed711671c6 fix(viewport): keep MSPACE camera target in f64 to stop UTM jitter
camera_from_view computed the camera target in f32 (view_target cast to
f32 before folding view_center), quantizing it to ~0.5 m at UTM scale.
Panning/zooming inside a floating viewport nudges view_target by sub-metre
f64 steps, so the camera snapped along the f32 grid and the drawn content
jittered — while the model view (f64 target) stayed smooth.

Compute the target in f64 (axis directions stay f32 — orientation only),
matching the model camera's precision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:35:26 +03:00
Hakan SevenandClaude Opus 4.8 295d3b1b5d feat(ucs): per-viewport UCS applied inside floating viewports
Extend UCS support to in-viewport (MSPACE) editing through the existing
single-converter structure: one active_ucs drives every UCS-aware system,
now in the Model tab AND inside a floating viewport.

- Single predicate DocumentTab::editing_model_space() (Model tab or active
  viewport) replaces the model-only gate in ucs_wire_affine,
  ucs_rotation_angle, ucs_grid_basis, viewcube_ucs_mat, the coordinate
  readout and the cursor-plane pick.
- Per-viewport UCS: refresh_active_ucs() sets active_ucs to the pane's UCS
  on every transition (the entered viewport's own UCS from its
  ucs_origin/x_axis/y_axis/ucs_per_viewport fields, the header model UCS in
  the Model tab, none in plain paper); persist_active_ucs() writes changes
  back to the viewport entity or the header so they round-trip. Wired into
  EnterViewport/ExitViewport, layout switch, load and the UCS command.
- Visuals: grid_views now returns each pane's handle so every content
  viewport's grid aligns to its own UCS; the UCS icon draws inside the
  active viewport projected through the viewport camera.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:23:29 +03:00
Hakan SevenandClaude Opus 4.8 a66d873e51 feat(viewport): unify in-viewport editing on the model-space path
Replace the leaky paper-projection approach to MSPACE editing with one
coordinate adapter that routes all interaction through the viewport's own
camera — the exact camera+rect the GPU draws the content with.

- Add Scene::viewport_edit_frame(canvas) -> (camera_for_viewport,
  viewport_screen_rect): the unified adapter. Inside a floating viewport
  the viewport is the active pane, so snap/pick/grip/preview/DYN run in
  model space and land on the pixels the GPU draws. Tracks pan/zoom/twist
  exactly (twist handled by camera_from_view); crop is pixel-preserving so
  full-rect projection + cursor offset matches.
- Route snap-move, click-pick, grip-drag, grip hit-test, grip render,
  hover (HoverDwellTick), selection (single/box/poly/cycle) and command
  entity-pick through it via pick_view / cursor_model_point helpers and an
  edit_cam-aware "active pane = viewport rect" override, so the existing
  tile offset plumbing just works. Fixes wrong-object hover/selection and
  stale snap after pan/zoom.
- DYN: project anchors with the viewport camera and shift to canvas space
  so the guide/labels sit correctly (also fixes a latent tiled-model bug).
- Separate grid snap from object snap entirely: new Snapper.grid_snap_on
  flag, Grid removed from the osnap mode set, evaluated independently of
  the osnap master. Object snap never catches grid; grid corners are
  picked only when grid snap is on, at lowest priority. Default off.
- grip.rs: project_rte + grips_to_screen_rte / find_hit_grip_rte taking
  (view_rot, eye) so grips project through the composed view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 17:06:59 +03:00
Hakan SevenandClaude Opus 4.8 4aa8e6681f wip(viewport): render UTM viewport content; _f64 cleanup; paper hit-test
Checkpoint on the way to unified in-viewport editing.

- Drop the _f64 transition suffixes: pick_on_plane / pick_on_target_plane /
  paper_to_model are single f64 functions (wrappers removed).
- Paper-space hit-test/snap projects viewport content with points_low, and
  projects its aabb / key_vertices / snap_pts into paper coords so click
  picking and snap candidates land on the visible sheet geometry.
- camera_for_viewport + paper_to_model + EnterViewport agree on an absolute
  drawing-centre view (normalize_active_viewport_view) so UTM viewports
  render instead of aiming at the origin.
- model_to_paper inverse; dynamic-input anchors map model→paper before the
  paper camera projects them, so the guide no longer tessellates a 512 MB
  buffer (clicking the first in-viewport LINE point) — crash fixed.

Known-incomplete (next: unify on the model-space path via one coordinate
adapter): in-viewport snaps stale after pan/zoom (paper_projected_cache not
keyed on the viewport view), grips don't show, command preview misplaced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:53:38 +03:00
Hakan SevenandClaude Opus 4.8 f9b27e7b62 fix(viewport): aim paper-space viewports at the absolute drawing centre
Floating viewports rendered blank on UTM drawings. camera_for_viewport
centred both the saved-view overlap test and the auto-fit target on the
ORIGIN — correct only while world_offset re-centred the model there. With
world_offset removed the model sits at absolute (~5.7e6) coordinates, so a
stale (0,0,0) saved view failed the overlap test AND the auto-fit aimed at
empty origin, leaving the viewport empty.

Both now use the absolute drawing centre from model_space_extents, so saved
views that frame the model are kept and the auto-fit falls back onto the
real content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:01:42 +03:00
Hakan SevenandClaude Opus 4.8 60d4712179 fix(paste): UTM-precise paste/block base via f64 centroid
entities_centroid summed the f32 points (double-single HIGH half) alone, so
at UTM scale (~5.7e6) it both quantized each term ~0.5 m and lost low bits
across the running total — drifting the paste base and PASTEBLOCK's block
base metres off, which placed the wrapped block at the wrong spot/scale.

It now reconstructs each vertex's absolute f64 from points + points_low and
accumulates in f64. clipboard_centroid is DVec3; the PASTECLIP commit delta
is fully f64; define_block_from_owned_entities takes an f64 base so the
block-local fold (Translate(-base)) stays precise. The interactive ghost
anchors (PasteCommand, InsertBlockCommand) still take the f32 cast — display
only; the committed geometry is f64.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 10:42:58 +03:00
Hakan SevenandClaude Opus 4.8 fe5ff376e5 fix(pick): UTM-precise click / box / lasso selection
Selection projected wire vertices from points (the double-single HIGH half)
alone, ignoring points_low, so at UTM scale every selection test ran against
coordinates ~0.5 m off — box/crossing/window edges and click hit-tests missed
or grabbed the wrong entities, worsening with zoom.

hit_test now reconstructs each vertex's absolute f64 from points + points_low
(wp64) before projecting, across click_hit, click_hits_all, box_hit and
poly_hit (lasso). Hatch hit-tests use the f64 world_origin instead of an
f32-cast, and mesh hit-tests (click/box/lasso face picking) reconstruct from
verts + verts_low. world_to_screen now takes the f64 point directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 10:33:42 +03:00
Hakan SevenandClaude Opus 4.8 e5c8eddedb fix(camera): centre ortho depth range so deep zoom doesn't clip the drawing
Zooming in close made the drawing vanish. The orthographic near/far were
near = distance*0.001, far = distance*1000, which parks the target plane at
ndc-z ~= 0.001 — flush against the near plane. The shared draw-order depth
bias shifts clip-z by up to DRAW_ORDER_BIAS (0.001), so a front-biased
entity landed exactly at z = 0 and dropped out as soon as f32 rounding (which
worsens at high zoom) tipped it past the plane.

ortho_depth_range() now returns a symmetric near/far (distance ± r) that
centres the target plane at ndc-z ~= 0.5, giving the bias half the depth
buffer of headroom on each side. Perspective is unchanged. Applied to both
view_proj and view_proj_rte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 10:22:44 +03:00
Hakan SevenandClaude Opus 4.8 dd1f2e49bb fix(render): UTM-precise UCS icon + fallback outlines (hatch etc.)
Two UTM-scale rendering defects.

- UCS icon jittered: it projects only axis DIRECTIONS but went through the
  full view_proj, whose ~5.7e6 translation cancels catastrophically in f32.
  Feed it the rotation-only view_proj_rte instead (translation dropped).

- Fallback outline geometry (hatch boundary, viewport/insert/ole2frame
  frames, Solid3D edge wires) built absolute f32 points with an empty
  points_low, so at UTM the outline sat ~0.5 m off its own fill (the hatch
  fill renders precisely via the world_origin-relative HatchModel pipeline).
  FallbackGeometry now carries absolute f64 points + key vertices; the
  tessellate dispatcher splits them into the double-single high/low pair the
  relative-to-eye wire pass needs, so outlines land exactly on their fills.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 10:09:19 +03:00
Hakan SevenandClaude Opus 4.8 ef55b49db5 refactor(paste): one finalize path; carry XCLIP through all three pastes
Consolidate the duplicated paste logic and extend XCLIP-carry to every path.

- finalize_paste(): single shared step for PASTECLIP and PASTEORIG —
  merge deps + block defs + xdictionary graphs, add clones (optionally
  transformed), tessellate solids. A new cross-drawing concern is now wired
  once instead of re-implemented per command (the gap that lost the clip on
  PASTEORIG in the first place).
- recreate_clipboard_ext_roots() + recreate_ext_subtree(): PASTEBLOCK now
  recreates each entity's xdictionary graph and stamps the new root onto the
  wrapped entity before defining the block, so the block's nested insert
  keeps its spatial filter.
- block_cache: a nested insert's clip is baked into NestedRef.clip_poly at
  build time (the spatial filter isn't reachable at expand time) and applied
  on expansion — the clipped wires are finalized in an isolated batch set,
  clipped, and carried out via Batches.extra_wires. Single-instance only;
  arrays of clipped nested inserts fall through unclipped.

All three paste paths (PASTECLIP / PASTEORIG / PASTEBLOCK) now preserve an
XCLIP clip across a cross-drawing paste.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 09:53:28 +03:00
Hakan SevenandClaude Opus 4.8 6667042372 fix(xclip): precise clip + carry clip across cross-drawing paste
Clipped blocks were broken at UTM and lost their clip when pasted into
another drawing.

- Clip geometry: world_clip_polygon_f64 keeps the boundary in absolute f64;
  clip_wires now clips in a frame relative to the boundary's first vertex
  (small coords → exact f32 Sutherland–Hodgman) and re-splits the result
  into the double-single high/low pair the relative-to-eye renderer needs,
  so the clipped block draws correctly and no longer poisons ZOOM Extents.
- ZOOM Extents (fit_all): the outlier reject compared absolute UTM
  coordinates against a centre-RELATIVE span (local_extent_max), rejecting
  the whole drawing → no-op. Made the reject distance-from-median-centre.
- Cross-drawing paste: clipboard now snapshots each copied entity's whole
  xdictionary object graph (ClipboardDeps.ext_objects) and recreates it on
  paste with fresh handles + remapped references, re-pointing the pasted
  entity's xdictionary at the new root. Wired into PASTECLIP and PASTEORIG.
  Fixed handle allocation (allocate_handle, not the non-advancing
  next_handle) so the dictionary chain doesn't collapse onto one handle.
- Updated hit_test / plugin_host tests for the eye-relative + DVec3 signatures.

Known gaps: PASTEBLOCK does not yet carry the clip (clip would need to live
inside the new wrapper block definition); hatch-clip path still f32.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 09:33:55 +03:00
Hakan SevenandClaude Opus 4.8 bca8958025 fix(snap): f64-precise computed snaps + UTM-tolerant cull + f64 previews
Computed snaps (midpoint/nearest/perpendicular/intersection/extension/
apparent/grid) reconstructed the snap point from f32 `wire.points`
(absolute high half), so at UTM-scale they landed ~0.5 m off the geometry
while endpoint/center (sourced from f64 key_vertices/snap_pts) stayed
correct. Now they rebuild each vertex from points + points_low (the
double-single low residual) and run the geometry in f64:
- snap() cursor + geometry helpers (nearest_on_segment, perp_foot,
  seg_intersect_xy, extension_snap) are DVec3; wp_f64() reconstructs each
  vertex; midpoint uses f64 key_vertices directly (was downcast to f32).
- snap callers pass the precise DVec3 cursor.

Snap capture at deep zoom: the wire-AABB cull compared a sub-millimetre
snap radius against f32 bounds quantized by ~1 ulp (~0.7 m at 5.7e6),
wrongly excluding wires under the cursor. Pad the cull by the bound's own
f32 quantization so it never rejects an in-range wire.

Rubber-band previews (line/circle/arc/ellipse/rect/polygon) built f32
absolute points with an empty points_low, so the wire pass (relative-to-
eye) drew them ~0.5 m off the cursor at UTM. Add WireModel::solid_f64,
which splits f64 points into the double-single high/low pair; previews now
track the cursor exactly.

Known: snapping at extreme zoom on large drawings spikes CPU (per-move
snap cost) — deferred.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 04:26:05 +03:00
Hakan SevenandClaude Opus 4.8 4f7a98a2ad feat(f64): command layer compiles on DVec3
Whole command/snap/grip/cursor chain now builds on f64 DVec3 end to end,
so picked points, snaps and grip drags stay precise at UTM-scale
coordinates instead of quantizing to ~0.5 m through f32.

- All ~80 command files migrated: draw (line/circle/arc/ellipse/shapes
  in full f64; others via point methods), modify, annotate, insert, view,
  inquiry, layers, groups, clipboard, layout.
- Entity apply_grip impls updated for GripApply::{Absolute,Translate}(DVec3).
- Construction sites (EntityTransform/CmdResult/DynAnchor) emit DVec3;
  wire-preview boundaries cast to f32 only at the GPU edge.
- Modify/annotate commands keep internal Vec3 geometry where precision is
  non-critical, casting at the DVec3 boundary; key draw commands store f64.

Builds clean (no warnings). Visual verification of crosshair/snap/grip at
extreme UTM zoom pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:51:50 +03:00
Hakan SevenandClaude Opus 4.8 3da71698b1 wip(f64): convert command layer to DVec3 (in progress)
Migrate the command/snap/grip/cursor chain from f32 Vec3 to f64 DVec3 so
picked points, snaps and grip drags stay precise at UTM-scale coordinates
(f32 quantizes to ~0.5 m there).

Done so far:
- command/mod.rs: EntityTransform, CmdResult points, CadCommand point
  methods, TangentObject already DVec3 (radius f64).
- snap: SnapResult.world DVec3; world_to_screen/try_pt DVec3.
- GripApply -> DVec3; grip find/state (GripEdit) f64; camera
  pick_on_plane_f64 / pick_on_target_plane_f64; scene paper_to_model_f64.
- update.rs click + mouse-move + grip-drag pipelines route precise DVec3
  to on_point/on_preview_wires/on_entity_pick; ortho/polar cast at the
  UcsXform boundary only.
- cmd_result dispatch, transform helpers, dimension/arc/circle/multileader
  apply_grip, circle/arc/ellipse/shapes draw commands, array modify.

Tree does NOT compile yet (~236 errors remain across modify/annotate/
insert command files + entity apply_grip impls); committed at user request
to checkpoint progress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:32:11 +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 <noreply@anthropic.com>
2026-06-23 23:33:21 +03:00