Keep new vertices in grip placement until click and restore the original entity on Escape. Correct reverse direction and segment metadata across polyline variants.\n\nRefs #610
Keep Layout output on the selected physical sheet and preserve hatch lineweights and plot styles through paper, viewport, and nested block paths.\n\nRefs #607
Use the same area pipeline as model space while keeping Layout bound to the physical sheet and printable margins. Share scale, rotation, clipping, and viewport fills across preview, PDF, and printer output.
Cancel stale interaction state across space changes and keep live previews and zoom extents in their active paper/model context.\n\nRefs #539\nRefs #540\nRefs #541
Avoid full document, cache, and GPU rebuilds for entity deltas so add, delete, history, selection, and live polyline edits stay responsive on large drawings.
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
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]>
Xref merge now inserts entities in the source block's chain order and
carries SortEntitiesTable objects across (remapped to merged handles).
draw_depth_map values become [depth, half] so exploded block/xref fills
and wide-polyline band wires both compose child depth as
insert_depth + in-block rank x half, recursing into nested inserts with
the same divisor - bands and fills from one block interleave by the
block's internal order instead of stacking at the insert's single depth.
Band batches split per rank via WireModel.depth_override; thin wires
stay merged. The wire arena falls back to a full rebuild when a slab
carries mixed depths so refreshes don't flatten band overrides.
Co-Authored-By: Claude Fable 5 <[email protected]>
EXTEND's pick dispatcher covered lines, ellipses, polylines and
splines but never arcs, though TRIM already handled them (#409).
extend_arc gathers every boundary crossing around the arc's full
circle (reusing arc_seg_ts against all boundary kinds) and walks the
clicked end to the nearest one — CCW for the end angle, CW for the
start. The hover preview draws the extended result like the other
types, and the replace-snapshot patches the new Arc's handle.
Co-Authored-By: Claude Fable 5 <[email protected]>
GRADIENT silently degraded to a plain hatch: add_hatch rebuilt the
render model from the DXF entity but never encoded the gradient on it,
so the committed fill lost its gradient entirely. The entity now
carries the full gradient record (name, angle, colour stops,
solid-fill flag), and the standard shape set became selectable:
Linear / Cylinder / Spherical / Hemispherical / Curved plus an Invert
toggle, offered as prompt buttons on the GRADIENT command and as a
Gradient type dropdown + Invert box in the properties panel. All nine
standard DXF names round-trip (inverted linear persists by swapping
the colour stops), and the shaders shape each kind distinctly —
mirrored cylinder, eased curve, dome-profiled hemisphere — on both the
native and web pipelines without changing the GPU struct sizes.
Co-Authored-By: Claude Fable 5 <[email protected]>
OFFSET now opens with "Specify offset distance or [Through] <last>":
a typed value locks the distance, bare Enter / Space accepts the last
one (also offered as a prompt button), and T switches to through mode
(#418). After each offset the command loops back to the object pick at
the same distance until Enter / Esc finishes — Space acts as Enter
throughout, where the old flow cancelled. The pick-first path (#422)
keeps working: pre-selected objects jump from the distance step
straight to the side click.
Co-Authored-By: Claude Fable 5 <[email protected]>
A PLINE point landing back on the first vertex (endpoint snap) now
closes the polyline instead of stacking a duplicate vertex there —
including an arc closing segment, whose bulge is already computed for
the last-to-first span (#421). Line mode requires two real segments
first so a doubled-back line isn't treated as closed. Setting
Closed: Yes in the properties panel on a polyline whose last vertex
already sits on the first drops the duplicate control point for the
same reason.
Co-Authored-By: Claude Fable 5 <[email protected]>
OFFSET now follows the pick-first flow of MOVE / COPY (#422): with
offsettable objects already selected it skips the pick step and goes
straight to the distance / side step, offsetting every selected object
toward the clicked side — each through the cursor point in through
mode, all by the typed magnitude once one is locked. Preview covers
every target and a multi-object result commits as one undo step; with
nothing selected the command gathers a pick exactly as before.
Co-Authored-By: Claude Fable 5 <[email protected]>
REFCLOSE only wrote the temp copies made at REFEDIT entry back into
the block, so anything drawn (or pasted / offset) during the session
stayed behind in model space (#423). The session now captures a
handle watermark at entry; on close, every current-space entity
allocated at or above it joins the working set — Save folds it into
the block through the same inverse transform, Discard removes it.
Covers every creation path with no per-command hooks.
Co-Authored-By: Claude Fable 5 <[email protected]>
Entering the block editor reframes the camera on the block's own
geometry, but nothing remembered where the model view was — Save /
Discard returned to the layout with the camera still parked at the
editor's framing (#425). The session now captures the camera on entry
and both exits restore it exactly.
Co-Authored-By: Claude Fable 5 <[email protected]>
Standard selection keywords, available typed or as prompt buttons in
every gathering command (#426): P / PREVIOUS re-selects the set the
last finishing command worked on (now captured before the working set
is dropped), and L / LAST selects the most recently created object of
the current space (highest handle among its selectable wires). Handled
centrally in the step router, so the command line, the option buttons
and the automation feeder share one implementation; deleted objects
and locked layers are filtered out.
Co-Authored-By: Claude Fable 5 <[email protected]>
A wide polyline with per-vertex varying widths rendered at its widest edge
(uniform-width ones were already exact). Draw the true taper instead.
`WireModel.taper_widths` carries a per-point full band width. The wire
instance (native slim + web fat) gains per-endpoint `world_hw_a/b`, and both
wire shaders interpolate `mix(hw_a, hw_b, which_end)` so each segment's band
narrows/widens linearly — depth-tested, and the linetype dash still applies.
Alignment (widths must match the tessellated points) is solved by building
both together: a new `TruckObject::TaperedLines(points, widths)`, filled by
`common::tapered_band_points` (straight = endpoints, arc = 16 samples with the
width lerped along it). to_truck routes a varying-width polyline here —
LwPolyline before the PLINEGEN split, Polyline2D at its return — while a
uniform-width one keeps the constant-band Contour path unchanged. `world_width`
stays the widest edge as the PDF-stroke + zoom-floor fallback.
Blocks fall back to the constant-widest band (`LocalWire` carries no
taper_widths).
Co-Authored-By: Claude Opus 4.8 <[email protected]>