Commit Graph
1701 Commits
Author SHA1 Message Date
Hakan SevenandClaude Fable 5 bcd1fc15ea feat(shx): real SHX glyphs for text fonts and linetype shapes
The SHX reader now serves all three consumers instead of just SHAPE
entities:

- Complex-linetype shape elements stopped being skipped: a document
  linetype's Shape{number} resolves its style's shape file (shared path
  fallbacks) and draws the real glyph; `.lin` catalog references keep
  the file token and look the shape up by name. The converted-LFF
  substitute set remains the fallback for anything unresolved.

- Face::Shx renders resolvable on-disk .SHX text FONTS with their real
  stroke glyphs: character code → shape lookup, cap height normalised
  to the 9-unit space every stroke-font consumer expects, advance from
  the interpreter's final pen position, line spacing from the shape #0
  above/below header, TTF fallback per missing glyph.
  resolve_text_style resolves a style's .shx next to the drawing and
  passes the absolute path through; unresolvable names keep their LFF
  substitutes. Shapes-only files (no #0 header) are refused as fonts.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 06:44:48 +03:00
Hakan SevenandClaude Fable 5 1de4344280 feat(shape): render SHAPE entities from their compiled SHX shape file
SHAPE entities only drew a diamond placeholder — .SHX files were never
parsed. A new reader (scene/text/shx.rs) decodes the classic
`AutoCAD-86 shapes 1.0/1.1` container and interprets the full shape
bytecode — pen up/down, scale divide/multiply, position push/pop,
subshapes, signed XY runs, octant/fractional/bulge arcs, the 16
direction vectors and the vertical-only skip — into unit-space
polylines, cached per file and per shape number.

The entity resolves its STYLE by handle (name fallback), the style's
shape file with the shared path fallbacks (as stored → relative to the
drawing → basename next to it; traded files carry foreign absolute
paths), then places the glyph with size × relative X scale, oblique
shear and rotation through the OCS mapping. load_file now records
doc.source_path so render-time resolution knows the drawing's folder.
A missing or unreadable shape file keeps the diamond placeholder.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 03:53:41 +03:00
Hakan SevenandClaude Fable 5 f4f8c0687c fix(image): stop darkening rasters; X only for broken references
Image textures were created Rgba8UnormSrgb: sampling decoded sRGB to
linear, but the surface isn't sRGB, so every raster (images, OLE, PDF
underlays) rendered visibly dark. Rgba8Unorm passes the texels through
in the same space as every other colour in the app.

The frame wire also always carried the X diagonals — that's the
broken-reference placeholder, and it scribbled over resolvable images.
The diagonals now draw only when the reference doesn't resolve.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 03:41:52 +03:00
Hakan SevenandClaude Fable 5 1e913e9e78 feat(underlay): render PDF underlays end-to-end
PDF underlays only drew their clip outline / insertion cross; the page
itself never appeared. The chain now runs: the definition's file path
resolves with the raster-image fallbacks (as stored, relative to the
drawing, basename next to the drawing — attach-time relative paths go
stale), the page rasterises through the system pdftoppm (poppler) at
150 dpi — no new crate; missing tool degrades to the outline — cached
per (path, page) including negative results, and the pixels feed the
existing image pipeline as an ImageModel: 1 underlay unit = 1 PDF inch
(px/dpi × entity scale), rotated about the insertion, fade as opacity,
the clip polygon (non-inverted) as the textured region. Draw-order,
layer visibility and per-viewport freezes ride the generic image path;
in-place edits rebuild through the shared image seed.

When the page renders, the wire path draws its frame with an interior
pick surface instead of the lone cross, so selection covers the visible
page. wasm keeps the outline placeholder (no external process).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 03:41:41 +03:00
Hakan SevenandClaude Fable 5 ac0a6ac150 fix(mleader): honour the baked context — anchors, rotation, dogleg, frames
The annotation context is AutoCAD's baked display state; the renderer
now trusts it instead of re-deriving:

- text_attachment_point (L/C/R) is the stored horizontal anchor — the
  leader-side guess shifted centre/right-attached texts by their width.
- ctx.text_direction/text_rotation apply as stored; forcing the
  Horizontal angle type to 0 un-rotated angled leaders. The conn.y
  baseline snap now applies only to unrotated text.
- The dogleg runs along the leader root's stored direction (angled
  leaders drew it world-horizontal), and vertically-attached leaders
  draw no dogleg at all.
- Underline attachments (BottomLine / BottomOfTopLineUnderline…) draw
  the leader-coloured rule under the attached line(s), full text width.
- Vertical text flow (ctx.text_flow_direction) feeds the shared
  vertical layout; frame/background boxes wrap the laid-out glyph
  bounds padded by ctx.landing_gap, and run-less decoration strokes +
  inline \C colours reach the output instead of being dropped.
- The wrap-width grip rides the shared flow_grip_axis() (rotation, flow
  and attachment side), and its drag projects on the same axis.

acadrust bump: every MULTILEADER decodes (has_ds peek), MTEXT
top-to-bottom direction reads correctly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 02:58:17 +03:00
Hakan SevenandClaude Fable 5 6e225e068c feat(text): real top-to-bottom layout; shared flow-aware width grips
Vertical (top-to-bottom) text used to reuse the horizontal line stack —
every line landed on the same baseline and overlapped. The layout now
stacks each character in its own 1.4h cell down the column, one column
per paragraph (1.2h apart), with word-boundary column wrap against the
defined width (which measures COLUMN LENGTH for vertical flow — the
horizontal pre-wrap is disabled there). Justification anchors place the
column block from wrap-accurate metrics: the pre-pass simulates the
render's word wrap exactly, so right/centre/bottom-attached blocks stay
put as columns grow. Layouts also report pre-rotation local_bounds so
frames and fills can box what was actually drawn.

Width grips route through one shared helper, flow_grip_axis(): the flow
axis (rotated baseline, or down the column) plus an attachment-side
factor — a right-attached text forms LEFT of its insertion, so its grip
sits there too; centre attachments use ±half. MTEXT's grip and drag
projection both use it, covering all 9 attachments × both flows × any
rotation from a single table.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 02:58:02 +03:00
Hakan SevenandClaude Fable 5 f1c494b81e fix(solid): skip display-cache wires when ACIS parses; real centres
The embedded solid display-cache wires are body-local (unplaced), and
R2013+ AcDs-backed solids used to misparse them into garbage points near
the origin — a clipped region rendered a stray fragment at (0,0,0). When
the ACIS payload parses, the mesh pipeline (fill + feature edges +
isolines, all body-placed) is the authoritative render, so the wires now
only serve as a last resort for unreadable ACIS data.

acadrust bump brings the decoded wireframe section and real reference
points: solids/regions report the file's stored bounding-box centre (or
a geometry-derived one) instead of (0,0,0), which feeds OCS grips,
Position properties and insertion snaps.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 01:13:43 +03:00
Hakan SevenandClaude Fable 5 94609fc54a fix(text): bake draw-order depth into glyphs so fills can't cover text
Fill triangles (DIMTFILL backgrounds, MLEADER masks, hatches) carry the
entity's draw-order rank, but SDF glyph quads were pushed at neutral 0.0
— any entity in the front half of the draw order had its own background
fill painted over its text. gather_text_verts now adds the host wire's
wire_draw_depth to each glyph: a text background lands at the same
biased depth (glyphs draw after fills and win the LessEqual test), and
text respects draw order against other entities' fills too.

The #403 glyph-reuse fast path gains a rank-stability gate: adding or
removing an entity shifts every sibling's bias (the rank denominator
changes), so reuse is only safe across all-Modified edits.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 01:13:33 +03:00
Hakan SevenandClaude Fable 5 f407835543 feat(xclip): draw the clip boundary frame when XCLIPFRAME is on
Clipped INSERTs cut their content correctly but never showed the
boundary itself. When the header XCLIPFRAME is 1 or 2 (visible), a
closed ring over the world-space clip polygon now rides the insert's
resolved colour and line weight. The frame is deliberately not clipped —
it is the boundary — and carries no snap points.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 01:13:24 +03:00
Hakan SevenandClaude Fable 5 0a3862c54b fix(block): don't draw the tag for empty constant attributes
A constant ATTDEF renders as block content, but with an empty default
value there is nothing to draw — the ATTDEF tessellator would fall back
to its tag-placeholder preview (meant for standalone definitions), so
blocks showed the literal tag text where AutoCAD shows nothing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-23 01:13:17 +03:00
Hakan SevenandClaude Fable 5 bf06a8e330 fix(leader): derive hookline for DWG leaders; size it from the dim style
DWG stores no "hookline exists" flag (only the direction bit), so every
DWG-loaded leader kept hookline_enabled=false and drew no hook. Derive
it: a text-annotated leader hooks when its final segment leaves the
horizontal by more than 15°, shared by the render and snap/grip paths.

Hook length comes from the dim style like the arrowhead (DIMASZ ×
DIMSCALE), and with DIMTAD "text above" it extends under the annotation
to the far edge of the MTEXT's laid-out extents — newly exposed by
acadrust (lock bumped); the old text-height heuristic stays as the
no-style fallback.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 23:36:25 +03:00
Hakan SevenandClaude Fable 5 48e8988528 fix(spline): honour end tangents on a two-fit-point spline
fit_spline_polyline bailed to a straight chord below three fit points,
ignoring the stored begin/end tangents — a short tangent-clamped spline
rendered as a line. The C² solver handles n=2 as-is (both end rows are
clamp or natural conditions), so only the guard drops to n<2; tangentless
two-point splines still solve to the straight chord.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 23:36:15 +03:00
Hakan SevenandClaude Fable 5 22a7c4490c fix(solid): read SAB transform Position tokens for body placement
The SAB reader tokenizes an ACIS transform record's matrix rows and
translation as Position triplets, while SAT text yields 13 individual
floats. body_transform() collected only float tokens, so every binary-SAB
solid/region (R2013+ AcDs payloads) lost its placement and rendered at
the body-local origin. Flatten Position tokens into the numeric run.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 23:36:07 +03:00
Hakan SevenandClaude Fable 5 805da6428a fix(hatch): finite GPU sentinel for boundary loop separators (#386, #416)
Hatch/wipeout boundary sub-loops were separated by NaN vertices that the
fill shaders detected with `p.x == p.x`. Intel Mesa folds that NaN
self-compare to `true` under fast math, so separators read as real
vertices, the ray-cast crossing count broke, and fills bled outside
their boundary — only on Intel iGPUs. GPU uploads now rewrite the
separators to a finite 1e30 sentinel (GPU_BOUNDARY_SEP) and the shaders
test `abs(x) < 1e29`, which every driver evaluates identically. The
CPU-side NaN representation is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 21:45:37 +03:00
Hakan SevenandClaude Fable 5 0c578b03f3 feat(select): PICKADD/PICKDRAG command-line toggles (#226)
PICKADD 0 = click/box/lasso replaces the selection (Shift toggles),
empty click clears; PICKADD 1 (default) keeps today's accumulate.
PICKDRAG 1 = press-drag spans a rectangle marquee through the box
machinery; PICKDRAG 0 (default) keeps the freeform lasso. Both persist
in settings.json. Also re-points the start-page gate test at the
start_allowed fn it moved into (#388/389 refactor left it broken).

Closes #226

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 21:35:37 +03:00
Hakan SevenandClaude Fable 5 90fd69df06 feat(matchprop): match every style-affecting property (#281)
MATCHPROP copied only the general properties (and text style/height),
so matching dimension-to-dimension or hatch-to-hatch looked like it did
nothing — the visible differences live in type-specific style fields.

General set gains transparency. match_special_props transfers every
STYLE-affecting field the destination supports — never content or
placement:

- Text: style, height, width factor, oblique (style+height cross
  TEXT <-> MTEXT); MText adds line spacing, drawing direction and the
  background fill block.
- Dimension / Leader / Tolerance: the dim style name crosses all
  three, and the ACAD_DSTYLE override record is replicated through
  set_entity_xdata (cleared on the destination when the source carries
  none, so the match is exact). Restyled dimensions drop their baked
  *D block and repaint immediately.
- Hatch: pattern definition, type, angle, scale, solid flag and
  gradient — the boundary stays.
- LwPolyline: constant width (vertex widths included) and linetype
  generation.
- MultiLeader: style plus all style overrides (path type, line
  color/type/weight, landing/dogleg, arrowhead, text style/color/
  frame/height, overall scale, override flags); leader geometry, text
  and block content stay.

Closes #281

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 20:24:53 +03:00
Hakan SevenandClaude Fable 5 e7eb84b514 feat(pedit): convert prompt, join, fit/spline/decurve; join fixes (#263)
PEDIT reworked around a mode machine:

- Picking a line/arc asks "Object is not a polyline. Turn it into one?
  [Yes/No] <Y>" — Yes converts in place (arc keeps its bulge) and
  editing continues on the result. Pickfirst: an already-selected
  polyline skips the select step, a selected line/arc goes straight to
  the convert prompt.
- Join gathers with the normal selection system (single picks AND
  window/crossing boxes) and merges through the JOIN machinery.
- Fit replaces every segment with a classic equal-parameter BIARC: two
  arcs per segment meeting tangentially at an inserted knee, both
  segments at a vertex sharing that vertex's tangent — the whole run is
  tangent-continuous (verified: zero tangent jump on L/closed/zigzag/S
  shapes). Spline resamples the vertex frame as a uniform cubic
  B-spline (8 samples per span); Decurve straightens every bulge.
- Earlier hardening kept: undo snapshot before the mutation, immediate
  repaint, polyline-only pick validation.
- Vertex editing lives on grips, so no Edit-vertex submode; instead the
  vertex grip hover menu gains Break — a closed polyline opens at the
  vertex, an open one splits into two (interior vertices only),
  handled driver-side as an entity replacement.

JOIN itself: segs_of now emits one segment per span of an OPEN
polyline (bulges kept), so polylines merge with their neighbours —
PEDIT Join's set always contains the target polyline and used to abort
every time; plain JOIN had the same gap. JOIN also honours pickfirst:
launched with two or more objects selected it joins them immediately.

Closes #263

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 20:06:04 +03:00
Hakan SevenandClaude Fable 5 b32a9b4083 fix(plan): show options on activation instead of flipping the view (#326)
PLAN acted like a ViewCube TOP click the moment it ran — already on Top
meant an immediate flip to Bottom, and the options never appeared. It
now prompts "[Current ucs / Ucs / World] <Current>:" right on
activation and only changes the view once an option runs: Current =
plan of the active UCS, World = world XY, Ucs = prompts for a named UCS
(case-insensitive lookup). The new plan_snap goes straight to the plan
view with no flip-to-opposite cube behaviour.

KeywordCommand gains with_default(): a bare Enter at the verb step
dispatches the default verb (PLAN's <Current>) instead of cancelling.

Closes #326

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:53:32 +03:00
Hakan SevenandClaude Fable 5 b998082c82 fix(props): angle rows format via AUNITS, not LUNITS (#297)
Every angle row went through the linear formatter, so switching LUNITS
to Architectural showed a block's Rotation as feet-and-inches — and the
string wouldn't parse back, wedging the field until it was cleared and
retyped. Only the decimal/fractional LUNITS survived by accident.

New edit_angle row helper formats angles with format_angle
(AUNITS/AUPREC: decimal degrees, DMS, grads or radians), applied to all
17 angle rows (rotations, obliquing, arc start/end, hatch pattern
angle, multileader text rotation). parse_angle_deg converts every
AUNITS display form back to degrees ("30°"/"30d", DMS 30°15'20",
33.33g, 0.52r) and parse_f64 falls back to it, so the displayed value
round-trips through an edit.

Closes #297

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:42:14 +03:00
Hakan SevenandClaude Fable 5 51f07f1208 feat(snap): Shift hard axis lock during point picks (#312)
Holding Shift while a command asks for a point captures the nearest
polar increment (or ortho axis, UCS-aware) from the rubber-band base
toward the cursor and pins the pick to that ray. Every candidate —
osnap hits included — projects onto the locked direction, so a snap far
off-axis contributes only its along-axis component; the click commits
exactly what the preview shows. The direction stays fixed while Shift
is held and drops the moment it releases. Grip drags get the same lock
from the grip origin.

Guards: engages only when the active command wants a point — never
during entity picks (Shift there is the TRIM/EXTEND swap), tangent
picks, selection gathering or window-corner picks.

Closes #312

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:34:47 +03:00
Hakan SevenandClaude Fable 5 ba73c48aec feat(start): list tutorial videos newest first
The playlist page lists oldest-first; reverse before persisting so the
cached listing and offline launches share the order.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:24:34 +03:00
Hakan SevenandClaude Fable 5 c879b4294a fix(dxf): building axis lines still hidden — LAYER off misread (#314)
Bump acadrust to a87435f: DXF encodes an off layer as a negative
code-62 colour index; the reader took 70 bit 2 ("frozen by default in
new viewports") as off instead. The AEC sample's GRIDLINES layer —
carrying that bit while fully visible in the DWG — imported hidden, so
every building axis line on it disappeared; genuinely off layers
imported visible. Second and final cause of #314, after the LTYPE
74/75 swap.

Closes #314

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:20:11 +03:00
Hakan SevenandClaude Fable 5 a7558eb75f fix(dxf): grid lines missing when loading DXF (#314)
Bump acadrust to 6455610: the DXF LTYPE reader treated element code 74
as the shape number instead of the element-type flags and materialized
complex data on every plain dash — dashed linetypes like the AEC
sample's GRIDLINE turned "complex" with a null Shape{0} on each
element and rendered nothing. The DXF writer's swapped 74/75 emission
is fixed with it.

Closes #314

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:08:38 +03:00
Hakan SevenandClaude Fable 5 ae83b82c96 fix(circle): TTT with concentric circles (#318)
The TTT solver's circle-pair difference equation loses its cx/cy terms
when the two circles are CONCENTRIC (a ring + a line through it, the
issue's drawing): lx = ly = 0, the equation only pins r, and the 2x2
center solve hit a zero determinant and returned no candidates.

The degenerate branch now recovers the solution directly: r comes from
the pinning equation (for a ring exactly (R_outer - R_inner) / 2), and
the centers from intersecting the remaining equation — a line in cx/cy
once r is known — with the first circle's distance constraint
|C - P| = |r + eps*cr|. Concentric pairs inside three-circle picks ride
the same branch. Candidate selection is unchanged, so the circle
nearest the three pick points wins.

Closes #318

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 16:02:31 +03:00
Hakan SevenandClaude Fable 5 9b28170d4b fix(undo): grip-drag history entry + mid-command Ctrl+Z steps (#332)
Grip drags mutated the document without ever pushing history — dragging
a polyline vertex (or any grip) could not be undone. The release-commit
now swaps the pre-drag backup in, snapshots that PRE state as a "GRIP"
entry, swaps the dragged result back and clears the redo stack; the
Esc-cancel path is unchanged and a no-move engaging click records
nothing.

Ctrl+Z during a drawing command steps the command instead of the
document undo swallowing the in-progress object (which also killed the
active command): new CadCommand::on_undo_step hook, consulted before
undo_active_tab. PLINE pops its last vertex (shared with the U option),
LINE reverts the last committed segment and keeps drawing from the
previous point (rides its existing U/UndoDocument path), and ARC
Continue reverts the last arc and resumes from the prior anchor with
the correct tangent via a new anchor history. With nothing placed yet
each falls through to the document undo.

Also drops the two warnings the #336 work left behind (unused Entity
import, dead Snapper::override_active).

Closes #332

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 15:48:22 +03:00
Hakan SevenandClaude Fable 5 9b40881ab1 feat(trim): TRIM/EXTEND options — edges, fence, crossing, edge, erase (#336)
Both commands gain a shared option mode machine:

- Cutting/Boundary edges (T/B): pick which objects act as the boundary
  (picked edges highlight yellow, the hovered candidate half-strength);
  Enter rebuilds the boundary set, empty = everything (quick mode).
- Fence (F): draw a dashed fence; every object crossing it is trimmed /
  extended at each crossing, re-applied to the surviving pieces so a
  fence across both overhanging ends clears both. Live red/cyan result
  preview while placing points.
- Crossing (C): crossing-selection semantics — the dashed green window
  picks objects crossing its edges AND wholly inside; runs through the
  fence machinery with a synthesized inside click.
- Edge (E): boundaries extrapolate (lines run past their ends, arcs
  close to full circles). Cuts that land on an implied extension draw a
  dashed yellow guide from the boundary's drawn end to the cut point,
  so the user sees which edge causes the cut. Extend previews also show
  the added tail as a bright dashed slice of the true curve.
- Erase (R, TRIM): picks delete without needing an intersection, with a
  red hover preview.
- Shift+click swaps trim<->extend per pick (new set_shift plumbing into
  the entity-pick and hover paths); the hover preview follows the held
  state. Project is out of scope for the 2D pipeline.

The per-type pick bodies moved to shared free fns (pick_trim_at /
pick_extend_at) used by direct picks, hover previews and the fence
pass; EXTEND's snapshot bookkeeping unified with TRIM's placeholder
flow. Option keywords now take precedence over the hex-handle reading
in feed_active_cmd ("F"/"C"/"E" are valid hex), and fence geos carry
sentinel handles — probing with NULL against NULL-handle geos excluded
the whole fence. Regression test: fence_trims_x_arm.

Closes #336

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 14:53:50 +03:00
Hakan SevenandClaude Fable 5 5f08b77ebc feat(web): copy/paste in text fields via the browser clipboard (#346)
iced's clipboard rides window_clipboard, which has no wasm backend — a
focused text_input captured Ctrl+C/V on the web and did nothing.

Ctrl+V (captured, wasm only): read the async browser clipboard (the
keypress is a user gesture, so it's permitted) and replay the text as
synthetic KeyboardEvents on the winit canvas — the only route into a
focused text_input, since iced exposes no insert-text operation.
Control characters are dropped and the length is capped. The MText and
inline-TEXT editors keep their dedicated paste paths (no double feed),
and an unfocused Ctrl+V still runs PASTECLIP.

Ctrl+C (captured, wasm only): a widget operation pairs each text_input
with its focus state (iced fires both callbacks back-to-back on the
same widget) to collect the focused field's visible text, then writes
it with navigator.clipboard.writeText. Whole-field text, not the
selection — iced doesn't expose the selected range.

Native behaviour is unchanged.

Closes #346

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 12:47:19 +03:00
Hakan SevenandClaude Fable 5 dd40e1f632 feat(snap): Shift+RMB one-shot snap override menu (#337)
Shift+right-click in the viewport opens a cursor-anchored grid of snap
icons (names as hover tooltips). Picking one engages a one-shot
override: only that snap applies — even with running osnap off — until
the next point pick commits, which restores the saved configuration.
Esc drops an unconsumed override; an outside click closes the menu.
The status-bar snap toggles are untouched — this is a separate,
temporary mechanism.

Covers items 1 and 2 of #337 (mid-between-two-points still pending).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 12:27:27 +03:00
Hakan SevenandClaude Fable 5 d2be0c9bea fix(pline): Convert to Arc follows the cursor and seats on click (#339)
Three fixes to the polyline arc-segment grip:

- bulge_from_midpoint keyed the sign off which chord side the midpoint
  lies on — the opposite convention of BulgeArc::from_bulge, so every
  grip-drag frame flipped the arc to the other side and the radius
  jumped wildly ("snaps to a restricted range of radii"). It now
  derives sign and sweep from the p0 -> mid -> p1 traversal
  orientation, verified as the exact inverse of BulgeArc's midpoint.

- The arc segment's mid grip dragged in Translate mode, feeding
  per-frame deltas through the recomputed arc midpoint — the drag
  deadlocked the moment the cursor crossed the chord. Arc grips now
  drag in Absolute mode: the arc re-fits through the cursor itself.
  Straight-segment grips keep the Translate stretch.

- Picking Convert to Arc in the grip menu now engages that segment
  grip (the Add Leader placement mechanic), so the new arc follows the
  mouse immediately and the next click seats it.

Closes #339

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 12:15:36 +03:00
Hakan SevenandClaude Fable 5 41f7e2becf fix(extrim): polyline cutting boundaries and polyline targets (#340)
Boundary: a picked polyline explodes into per-segment Line geos, and
extend_line_geos then blew every one of them up to +-TRIM_EXTENT — a
closed rectangle boundary became a grid of infinite lines and the
parity side-test broke. Only an actual LINE entity boundary is treated
as infinite now.

Targets: the sampled trim path sampled polylines at their vertices
only, so a straight segment crossing the boundary was cut at the wrong
place — or not at all when both endpoints were on the kept side.
insert_boundary_crossings splices the exact line_seg_ts intersections
into the sample list and extrim_sampled classifies per segment
midpoint (a vertex exactly on the boundary can't flip the parity
test). Ellipse and spline targets gain the exact cut points too. The
removal preview subdivides straight polyline segments so the red/blue
split follows the boundary instead of jumping at vertices.

Closes #340

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 12:04:30 +03:00
Hakan SevenandClaude Fable 5 95fcabaacb feat(open): drag & drop files onto the window (#344)
Dropping a supported file (dwg / dxf / bak / sv$, case-insensitive)
opens it through the existing open flow: an already-open drawing
switches to its tab, a load in progress queues the drop behind it
(multi-file drops arrive one event each), anything unsupported reports
on the command line instead of failing silently in the parser.

Platform support follows winit 0.30: Windows, macOS and Linux/X11
(incl. XWayland) deliver FileDropped; the Wayland backend does not
implement file drag & drop yet, so drops are inert there until winit
gains it.

Closes #344

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:58:39 +03:00
Hakan SevenandClaude Fable 5 4c99a5c70d fix(dxf): multileaders invisible when loading DXF
Bump acadrust to c5a579f: the DXF MULTILEADER reader now parses the
nested CONTEXT_DATA / LEADER / LEADER_LINE sections (leader lines and
text content used to come back empty, so DXF multileaders never
rendered), and the entity-level code map matches AutoCAD's real output
on both read and write (170 path type vs 172 content type).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:46:46 +03:00
Hakan SevenandClaude Fable 5 4792760a69 fix(dxf): MTEXT position wrong in R2018 DXF (#345)
Bump acadrust to 1e79519: the DXF reader now skips the R2018+
"101 / Embedded Object" section of MTEXT (and multiline ATTRIB/ATTDEF)
instead of letting its group codes overwrite the entity's already-parsed
insertion point, rotation, attachment and line spacing.

Closes #345

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:30:37 +03:00
Hakan SevenandClaude Fable 5 1870b8420f feat(pline): Undo option removes the last segment (#352)
U / UNDO (typed or via the prompt button) pops the last placed vertex
without ending the command, repeatably down to and past the start
point. The exit tangent of the remaining last segment is recomputed
from its bulge so a following Arc segment stays tangent-continuous.
New CmdResult::RemoveLiveEntity takes the live polyline out of the
document when Undo drops it below two vertices; the next point
re-creates it under the same undo unit.

Closes #352

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:17:01 +03:00
Hakan SevenandClaude Fable 5 e009d2d7a3 fix(rename): BLOCK type, stepped prompts, table re-keying
- RENAME gains the BLOCK type (stepped button + inline arm) via the
  shared Scene::rename_block.
- Stepped command front-ends (Rename, UserReg, Keyword, TwoValue,
  SelectThenTwoValue) returned None after consuming a typed token, and
  the driver treats None as "not consumed" — the same text was offered
  again and advanced two steps at once (RENAME dispatched old==new,
  SETVAR "X" became "SETVAR X X"). Consumed-but-prompting inputs now
  return NeedPoint.
- Symbol renames re-key the name-keyed Table (remove -> rename -> add)
  instead of mutating .name in place, which left the map keyed by the
  old name so later lookups silently failed. References (entity
  layer/linetype/style fields, layer linetypes, header current-* names,
  dim style refs on Dimension/Leader/Tolerance) are chased
  case-insensitively; layer 0/Defpoints and the built-in linetypes are
  protected; the undo snapshot is taken before the mutation so undo
  actually restores the old name.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:11:20 +03:00
Hakan SevenandClaude Fable 5 b5dbef5776 feat(props): editable block Name row + collapsible XYZ groups
- Block reference Name (Misc) is editable: the dropdown re-points the
  insert to another definition; typing a new name renames the definition
  everywhere (record re-key, Block marker, every INSERT reference).
  Anonymous (*) and xref blocks stay read-only, with the same guards
  added to Scene::rename_block.
- Custom single-control dropdown (text field + caret) built on the
  shared floating_below overlay so the list always opens downward,
  unlike iced's space-based menu placement.
- Consecutive "<Base> X/Y/Z" rows (Position, Scale, Start, Center, ...)
  collapse into one expandable summary row across all entities; the
  expand state persists across rebuilds and selection changes.
- The BEDIT block tab no longer offers the layout Rename/Delete
  context menu.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:11:10 +03:00
Hakan Seven 47f3409588 chore(release): v0.8.6 v0.8.6 2026-07-22 00:16:43 +03:00
Hakan SevenandClaude Fable 5 b7457ba09d fix(ribbon): highlight follows each tool's mechanism; start-tab dimming (#355)
Tool highlights used to stick: every click set active_tool, but only
interactive commands and a hand-maintained per-modal list ever cleared
it, so one-shots (Cleanup/AUDIT, view actions) and unlisted dialogs
(Customization/ALIASEDIT, CUI, Plugin Manager…) stayed blue forever.

Now the rule is mechanical: state toggles light from state only (the
click highlight drops immediately), interactive commands stay lit
while running, dialog openers stay lit until the modal closes (generic
clear replaces the per-modal list), and one-shots never keep it — the
dispatch site turns the highlight off when nothing stayed running.

On the Start tab, tools whose command the start gate refuses now render
dimmed (grey icon + label) across all button styles, dropdowns and the
quick-access strip; start_allowed() is the single shared authority.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 00:13:48 +03:00
Hakan SevenandClaude Fable 5 7a93ba82b3 feat(dyn): locked input fields constrain the preview and the click (#356)
A typed dynamic-input value (TAB-locked angle or distance) now shapes
the rubber-band: the cursor point is routed through dyn_resolve_point —
the same resolution Enter commits — in the mouse-move preview and the
click commit, so a locked 30° pins the line's direction while the free
field keeps tracking the cursor. The lock wins over osnap/ortho/polar.
TAB (and the comma advance) reshape the preview immediately instead of
waiting for the next mouse move.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:58:05 +03:00
Hakan SevenandClaude Fable 5 3dd27c6789 feat(layers): search boxes in the layer dropdown and Layer Manager (#343)
The ribbon layer dropdown gets a live search field above the list
(cleared when the dropdown closes, panel height tracks the filtered
count), and the Layer Manager toolbar gets one too. Both filter by
case-insensitive substring while toggle/rename messages keep the
original row indices, so acting on a filtered row hits the right layer.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:41:27 +03:00
Hakan SevenandClaude Fable 5 87f40e7544 feat(layers): draggable Name-column width in the Layer Manager (#359)
A thin divider after the Name header resizes the column by drag
(60-640 px, session-persistent), riding the shared modal drag flow.
The name elide budget follows the column width, so widening the
column reveals long layer names; the hover tooltip stays for overflow.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:34:02 +03:00
Hakan SevenandClaude Fable 5 261b699dee feat(edit): MATCHPROP stays active for repeated targets (#362)
Applying to one target used to end the command. The apply now clears
the consumed selection and keeps the command in its target phase, so
properties can be painted onto object after object; Enter or Escape
finishes (prompt updated accordingly).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:22:27 +03:00
Hakan SevenandClaude Fable 5 3207cb391b feat(view): bare ZOOM prompts with mode options
ZOOM alone used to jump straight into window zoom. It now offers
Window / Extents / All / In / Out / Scale as clickable keywords
(Scale collects the factor in a second step); ZW/ZE/… shortcuts and
the spelled sub-forms still run directly.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:18:18 +03:00
Hakan SevenandClaude Fable 5 9690d37d03 fix(view): window corner picks exempt from Ortho/Polar (#363)
ZOOM WINDOW's opposite corner was pinned to an axis with Ortho on,
collapsing the window to a line. Extend the #291 window_corner_pick
exemption to ZOOM WINDOW, PLOTWINDOW and WIPEOUT's rectangular mode
(polygon mode keeps the constraints — axis-aligned edges are a
legitimate use).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:14:08 +03:00
Hakan SevenandClaude Fable 5 ea860cab72 fix(view): ZOOM Extents fills the window at the real aspect (#364)
fit_to_bounds fitted the bounding box's circumscribed DIAGONAL into the
viewport height regardless of shape, so a wide drawing filled only a
small central patch. Fit against the actual viewport aspect instead —
required half-height = max(|y|, |x|/aspect) over the view-space corners
— and trim the border margin from 20% to 10%. ZOOM EXTENTS/ALL and
ZOOM WINDOW both pass the live pane aspect.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:09:37 +03:00
Hakan SevenandClaude Fable 5 ac14e0041d fix(styles): re-seed missing Standard styles on open; titled modals (#366)
Deleting/renaming Standard was already blocked in the five style
managers, but a file saved without the built-ins (foreign or damaged)
opened with broken style dropdowns and nothing to reference — re-seed
missing Standard text/dim/table/mleader/mline styles on every open.

Every modal dialog now shows its name in the title bar: centred at
15px across the dialog width, with the move grip and close button
overlaid at the right edge. The bar takes an explicit width from the
caller — a Fill child inside the Shrink frame would blow the dialog
out to the full screen.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 23:00:36 +03:00
Hakan SevenandClaude Fable 5 bd27ee31d0 fix(trim): arc-arc intersections tested against wrong circle (#370)
cc_angles returns angles on the TARGET circle, but the arc-boundary arm
of arc_seg_ts filtered them against the BOUNDARY arc's angular span —
wrong circle, so arc×arc (and circle×arc, EXTEND-to-arc, bulge-polyline
segment) intersections were dropped and TRIM neither highlighted nor
cut. Re-express the intersection point as an angle on the boundary
circle before the span test, matching line_seg_ts/ellipse_seg_ts.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 22:45:28 +03:00
Hakan SevenandClaude Fable 5 dc10c5dfba feat(edit): STRETCH crossing window selects objects implicitly (#338)
Bare STRETCH used to need a separate object-selection round before the
crossing window. Now the window itself picks the objects: with no prior
selection the command emits StretchWindow, the host gathers entities
whose world AABB touches the window (locked layers excluded) and
restarts at the base-point step. Over-selection is harmless since only
points inside the window move. The noun-verb flow (select first, then
STRETCH) is unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 22:35:35 +03:00
Hakan SevenandClaude Fable 5 5383c64d5d fix(edit): STRETCH moves dimension definition points (#372)
The STRETCH apply loop had no Dimension arm, so dimensions fell into
the generic skip — points never moved, and a reopened file's stale
baked *D block made the result snap back visually. Move every
definition point inside the window (grip-equivalent set plus pinned
text, sparing the zero auto-placement sentinel), refresh the stored
measurement, and drop the stale *D block so the stretch survives
render and save.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 22:24:20 +03:00
Hakan SevenandClaude Fable 5 3205f7a2b9 feat(purge): nested fixpoint purge + option prompt (#375)
PURGE now repeats until a pass removes nothing, so definitions freed by
a purged block (nested blocks, layers, linetypes, styles) go in one run.
Reference scan keeps layer linetypes, ATTDEF/ATTRIB text styles, and
dimstyle DIMTXSTY + arrow blocks; the undo snapshot moves before the
first removal so one UNDO restores everything. Bare PURGE prompts with
clickable category options (aliases B/LA/LT/ST run directly), and
pinned prompts drop their "[A / B / …]" listing when the same options
render as buttons.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-21 22:15:33 +03:00