* Build: line tables only in dev, no debug info for dependencies
* Tests: one integration-test binary per crate
Each crate's tests/*.rs becomes a module of tests/it/main.rs. Files that
install process-wide state (the file io hooks, user aliases, SAVETIME and
SAVEFILEPATH, user linetype and pattern files, registered fonts) stay their
own test binaries.
* xtask ci: nextest, tests without examples, one wasm check call
* security: budget the rendering pipeline against DoS geometry (CWE-400/407/789)
Several render paths let crafted drawing data spin the rendering thread or
allocate without bound:
- Hatch dash sweeping reset its per-span step counter and used emitted geometry
as its only limit, so gap-only families (which emit nothing) could multiply
CPU work. Skip families that cannot draw and charge every dash step against a
shared per-hatch work budget (row 10).
- Hatch polygon deduplication compared loops with an O(n²) cyclic match and
every loop against every kept one. Charge vertex comparisons against a shared
budget and stop deduplicating when spent (row 12).
- Table coverage marked every merge rectangle, so overlapping merges re-marked
covered cells quadratically. Bound marking to a small multiple of the cell
count (row 11).
- Broken-table part layout expanded sparse rows to full width before checking
whether detailed layout is allowed. Only expand when within the cell limit;
otherwise leave cells sparse and let layout draw an outline (row 17).
- Dimension text substitution copied the base for every `<>` placeholder with no
size bound. Estimate the expansion and cap it at a fixed text budget (row 32).
- Linetype range generation cycled all-gap patterns forever (nothing emitted, so
the emit limit never tripped). Return continuous for all-gap patterns and
bound visited pattern elements (row 38).
- Viewport contents built with a fresh expansion counter, so a layout could far
exceed the documented block-expansion limit. Share one budget across the whole
build, seeded with what the enclosing list already spent (row 22).
New tests for the all-gap linetype path; all render tests pass, clippy clean.
Addresses findings rows 10, 11, 12, 17, 22, 32, 38.
* fix: preserve bounded render fallbacks and text output
---------
Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
* security: bound geometry-kernel degree and work (CWE-407)
Several geometry routines let a crafted spline or region cause quadratic or
worse CPU work:
- B-spline basis evaluation is O(degree²) per sample and tessellation takes
O(degree) samples, so a HATCH/DXF spline edge declaring a very high degree
(DXF allows up to 100 000) turned a tiny spline into billions of iterations.
Add `MAX_DEGREE = 64` and treat higher-degree splines as invalid, so
`eval`/`tessellate` fall back to the control polygon. The render clip boundary
also rejects such degrees up front (rows 18, 28).
- `Spline::derivative` re-ran the O(knots) validity scan on every call, and
arc-length integration calls it once per sample, making a valid spline's
measurement quadratic. Split off `derivative_unchecked` and have integration
(validated once by its public entry points) use it (row 24).
- `infer_knot_param` matched each fit parameter against the whole knot vector
from the start. Both are ascending, so replace it with a linear merge (row 16).
- Fillet seed collection did not stop the enclosing run-pair loops when the
per-call candidate cap was hit, and refined duplicate candidates. Bound total
seeds across pairs and deduplicate before Newton refinement (row 36).
- Region grid intersection counted only distinct pairs against its budget, so a
long segment replicated across many cells re-visited the same pair unbounded.
Charge every candidate-pair visit against a budget (row 41).
New spline degree tests added; all geom and render tests pass, clippy clean.
Addresses findings rows 16, 18, 24, 28, 36, 41.
* fix: preserve spline knot and fillet search behavior
---------
Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
* fix(render): keep filled highlight dash anchors stable
* outline test: as_chunks for clippy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Found while testing crates/render/src/units.rs:139-160 (format_linear, unit 3 and 4 arms) and feet_inches at :101
Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Slabs ran between vertex rows and paired the active edges by their
order at the slab's middle, so two edges crossing inside a slab (a
bowtie, two loops crossing) were paired across the crossing and the
trapezoids covered the wrong side of it. A slab with such a crossing is
now cut again at that y and each part paired on its own, under a cut
budget so hostile input stays bounded. Tests check coverage against an
even-odd parity oracle for a bowtie, an off-centre bowtie, two crossing
loops and the plain cases; the three crossing cases fail on main.
Claude-Session: https://claude.ai/code/session_011K1peyrh5MdZ6ttHkVq8Gh
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Paper-space units followed the drawing's MEASUREMENT only, so a layout
whose page setup is in millimetres (plot paper units 1), common in
drawings from AutoCAD with MEASUREMENT 0, was taken as inches: its PDF
drew everything 25.4 times too large, off the page.
PageSetup gains `paper_mm`, read from LAYOUT group 72 = 1; 72 = 0 still
leaves the units to MEASUREMENT, since earlier CADCraft versions wrote 0
for every layout. The layout's sheet, its PDF/raster plots, PREVIEW and
the pens of what its viewports show use the layout's units, and the
writer stores 72 = 1 for layouts in millimetres (it wrote 0 always).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AutoCAD interop: justified text, DIMPOST, current dimension style, *D block colours
What AutoCAD takes from CADCraft's DWG/DXF as it is:
- Justified TEXT/ATTRIB/ATTDEF (#1050): group 10 is now where the
baseline starts, from the alignment point and the text's width, not
the alignment point itself; AutoCAD drew centred and right-aligned
text from DWG starting at its alignment point. A stored point close to
CADCraft's own (AutoCAD's, exact for its fonts) is kept.
- DIMPOST (#1053) is written even when empty: the DWG bridge defaulted a
missing one to "<>", and AutoCAD then dropped the "R" of radius
dimensions when it regenerated them.
- The current dimension style (#1051): the DXF header now carries its
DIM* variables (AutoCAD draws new and updated dimensions from them);
the DWG bridge points $DIMSTYLE at the style by name (its handle was
renumbered, so the writer fell back to Standard), and reading a DWG
gives the current dimension and text styles their names back.
- *D blocks (#1052): lines, arrows, extension lines and text take the
style's DIMCLRD/DIMCLRE/DIMCLRT instead of ByBlock.
#1054 (group 42 of a rotated linear dimension) was already fixed on
main by #855; acad_interop.rs keeps it covered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test: angular dimensions store their angle in radians (group 42)
Three-point (60°, 174°) and two-line (120°) angular dimensions, read back
from the DWG as DXF. Already right on main since #855; this guards it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Justified text made here: Left Middle and Left Bottom write their baseline start too
text_insert kept a stored insertion point that was within 0.1 x an
estimated width + 0.5 x height of CADCraft's baseline start, taking it for
AutoCAD's own. The width was estimated from the alignment point's offset,
which for Left Middle is only h/2 (vertical), so a text made here, whose
stored point is the alignment point, passed and kept it: AutoCAD drew it
half a height high (Left Bottom: a third). Reported with a test by
@lee-cyrille-maskell.
A stored point equal to the alignment point is now always stale. Otherwise
the offset is split along and across the baseline: along it the same slack
as before (a tenth of the text's width plus half its height, as fonts and
their substitutes differ most in width), across it a fifth of the height.
Multiline attribute values keep the distance-only check, since their lines
are placed by the embedded MTEXT.
On 12 drawings made in AutoCAD (1,055 justified texts) the insertion points
written are the same as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Zoom and pan regenerate in the background instead of freezing the window
A rebuild that only the view asks for (a new 2x zoom band, a pan out of the
built area) now runs on a worker thread while the canvas keeps drawing the
previous display list, scaled by the view uniform. The worker builds the list,
converts it to vertex data and creates the GPU buffers, so the frame that swaps
it in only swaps buffers. One build runs at a time; a result from an older
drawing revision is dropped. Edits, the first build and the web build (no
threads) still rebuild synchronously.
ZOOM no longer calls touch(): it changes the view, not the drawing, and the
bumped revision made every typed zoom a full blocking rebuild (wheel zoom
never did). REGEN, REGENALL and layout switches still touch.
ui.screenshot waits (up to 6 s) for a background rebuild so agents capture
the new view; ui.inspect perf reports "building".
On a hatched 12k-entity DWG (~340k primitives) the longest UI stall per zoom
went from 2-4.5 s to 26-64 ms.
Also: cargo fmt of a comment in engine/src/prompt.rs that was failing the fmt
gate on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Cancel a background rebuild when the view moves on
A view that needs a different display list now cancels the build in flight
and starts one for itself, instead of waiting for the outdated build to land
and only then starting the next. A long scroll through many zoom bands builds
only for where it stops: the list lands about one build after the last step
(2.0-2.3 s on a hatched 12k-entity DWG) instead of up to two, and an outdated
build no longer keeps a core busy.
cadcraft_render::Options gains `cancel` (an Arc<AtomicBool>); a set flag stops
the build at the next entity (block contents included) and the hatch sweep at
its next pattern line, returning a partial list the caller discards. The
canvas's Job sets its flag when dropped, so replacing or dropping a job
cancels it; a cancelled worker sends nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* cargo fmt: crates/doc/src/dynblock.rs (fails the fmt gate on main)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Draw object transparency on screen, in exports and in plots
Transparency was read and saved (entity group 440, layer xdata, ByLayer and
ByBlock) but never drawn. Display lists now carry each object's effective
transparency (its own, its layer's, or the block reference's), and:
- the canvas (GPU and CPU paths), ui.render and PNG/SVG exports blend it;
TRANSPARENCYDISPLAY turns it off on screen and is now on by default, as in
AutoCAD (initial value 1);
- plots draw it only when "Plot transparency" is on: a page setup option
(PAGESETUP `plotTransparency`, a checkbox in the Page Setup Manager and the
Plot dialog), overridable per plot, with PLOTTRANSPARENCYOVERRIDE (0 never,
1 the setting, 2 always). PDF gets /ExtGState alpha, PNG plots blend, SVG
uses opacity. Plots without it are byte-identical to before.
The public DXF Reference has no plot layout flag for plot transparency, so
the option is kept in the session and in JSON, not in DXF/DWG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Transparency fixes: attributes, PDF overlaps, drag previews
- Attributes draw with their own transparency (their definition's, layer 0
meaning the reference's layer, ByBlock the reference's) instead of the
block reference's.
- PDF plots stroke overlapping transparent objects one by one, so they build
up where they cross as in PNG plots; opaque output is unchanged.
- Drag and rubber-band previews follow TRANSPARENCYDISPLAY.
- Effective transparency is worked out only when it is drawn, reusing the
layer the property lookup already found.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Transparency: one PDF stroke per transparent primitive; opaque rebuilt previews
- PDF plots never batch two transparent primitives into one path. Block and
viewport contents share their reference's handle, so the per-object flush
missed them; each primitive now blends on its own, as in PNG plots and on
screen. Opaque output is unchanged.
- Previews the session rebuilds with the current properties (rubber bands,
copies of objects) are drawn opaque again: they carry CLAYER's
transparency, not the objects'. The GPU drag of the objects themselves
keeps following TRANSPARENCYDISPLAY.
- Tests for attributes on a transparent reference layer, attributes without a
definition, and the drag cache across the TRANSPARENCYDISPLAY toggle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Selection like AutoCAD: colour highlight and the Options Selection tab
Selecting or hovering an object outlined each filled primitive (TrueType
glyphs, dimension and leader arrowheads, solid hatches, wide polylines)
by the rectangle around its triangles, and dashed everything else. A
selected dimension's text became a row of dashed boxes, one per glyph,
and its closed arrowheads small rectangles.
Selected objects are now redrawn in the selection colour: lines solid,
fills filled half see-through (so the lines and text on a highlighted
hatch stay visible), and the whole object reads as selected, text and
arrowheads included. A wipeout is always outlined, since filling it would
hide the drawing under it.
The Options dialog's Selection tab is laid out as AutoCAD's, with the
settings CADCraft can act on, each one AutoCAD's variable, kept in the
user profile:
- Selection modes: noun/verb selection (PICKFIRST), Shift to add
(PICKADD), Object grouping (PICKSTYLE bit 1, now saved in the profile).
- Selection effect color (selectionColor, an interface preference; null
is the theme's).
- Grips: Show grips (GRIPS) and the object selection limit for display of
grips (GRIPOBJLIMIT, 100; 0 no limit): grips are neither drawn nor
picked beyond it.
- Selection preview (SELECTIONPREVIEW): the object under the cursor is
highlighted when a command is active (2) and when none is (1).
- Visual Effect Settings: the selection area effect (SELECTIONAREA,
WINDOWAREACOLOR 150, CROSSINGAREACOLOR 100, SELECTIONAREAOPACITY 25)
and the Selection Preview Filter (PREVIEWFILTER 3: 1 objects on locked
layers, 2 xrefs, 4 tables, 8 multiline text, 16 hatches, 32 groups).
As in AutoCAD these are typed, not on the dialog: HIGHLIGHT (0 shows only
the grips of selected objects; not saved, so every session starts
highlighted), SELECTIONEFFECT (1 the selection colour, 0 dashed lines with
fills outlined along their real boundary) and PREVIEWEFFECT (the preview
dashed 0, thicker 1 or both 2).
Left for later, as CADCraft lacks what they control: lasso, grip tips and
menus, group grips, grips within blocks, associative hatch selection,
command and property preview, PICKDRAG, PICKAUTO and PROPOBJLIMIT.
DisplayList::fill_outlines returns the boundary of the region a filled
primitive covers as closed polylines (edges only one triangle has; the
slab filler's horizontal cuts, which need not line up between trapezoids,
are split at every x on their line and kept where an odd number cover
them), falling back to the bounding box over 600,000 vertices. The GPU
and CPU highlight paths and the drawing comparison overlay share the same
looks.
The new labels have Ukrainian rows (the required catalog); other
languages fall back to English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: EP995LAB <293597958+EP995LAB@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Selecting or hovering an object outlined each filled primitive (TrueType
glyphs, dimension and leader arrowheads, solid hatches, wide polylines)
by the rectangle around its triangles, and dashed everything else. A
selected dimension's text became a row of dashed boxes, one per glyph,
and its closed arrowheads small rectangles.
Selected objects are now redrawn in the selection colour: lines solid,
fills filled half see-through (so the lines and text on a highlighted
hatch stay visible), and the whole object reads as selected, text and
arrowheads included. A wipeout is always outlined, since filling it would
hide the drawing under it.
The Options dialog's Selection tab is laid out as AutoCAD's, with the
settings CADCraft can act on, each one AutoCAD's variable, kept in the
user profile:
- Selection modes: noun/verb selection (PICKFIRST), Shift to add
(PICKADD), Object grouping (PICKSTYLE bit 1, now saved in the profile).
- Selection effect color (selectionColor, an interface preference; null
is the theme's).
- Grips: Show grips (GRIPS) and the object selection limit for display of
grips (GRIPOBJLIMIT, 100; 0 no limit): grips are neither drawn nor
picked beyond it.
- Selection preview (SELECTIONPREVIEW): the object under the cursor is
highlighted when a command is active (2) and when none is (1).
- Visual Effect Settings: the selection area effect (SELECTIONAREA,
WINDOWAREACOLOR 150, CROSSINGAREACOLOR 100, SELECTIONAREAOPACITY 25)
and the Selection Preview Filter (PREVIEWFILTER 3: 1 objects on locked
layers, 2 xrefs, 4 tables, 8 multiline text, 16 hatches, 32 groups).
As in AutoCAD these are typed, not on the dialog: HIGHLIGHT (0 shows only
the grips of selected objects; not saved, so every session starts
highlighted), SELECTIONEFFECT (1 the selection colour, 0 dashed lines with
fills outlined along their real boundary) and PREVIEWEFFECT (the preview
dashed 0, thicker 1 or both 2).
Left for later, as CADCraft lacks what they control: lasso, grip tips and
menus, group grips, grips within blocks, associative hatch selection,
command and property preview, PICKDRAG, PICKAUTO and PROPOBJLIMIT.
DisplayList::fill_outlines returns the boundary of the region a filled
primitive covers as closed polylines (edges only one triangle has; the
slab filler's horizontal cuts, which need not line up between trapezoids,
are split at every x on their line and kept where an odd number cover
them), falling back to the bounding box over 600,000 vertices. The GPU
and CPU highlight paths and the drawing comparison overlay share the same
looks.
The new labels have Ukrainian rows (the required catalog); other
languages fall back to English.
Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Multiline attributes: read, draw and save their text
A multiline attribute (DXF attribute type 2, or 4 for a definition) has its
text as embedded MTEXT after its own groups (`101 Embedded Object`); its own
group 1 is empty. CADCraft read only the own groups, so the attribute drew
nothing, and a save wrote it back empty: its text was lost.
- `Attrib::multiline` keeps the multiline text; `text.value` holds its plain
text, which ATTEDIT and the other value editors change; an edited value
replaces the contents (`Attrib::multiline_text`).
- DXF read: ATTRIB and ATTDEF split at the marker; the MTEXT parsing is
shared with MTEXT entities (`mtext_from`).
- Drawn as multiline text; ZOOM Extents counts it.
- DXF write (2007 and later; earlier versions get one line): attribute type,
alignment point and the embedded MTEXT, only what differs from unrotated
text, as AutoCAD writes it.
- INSERT and ATTSYNC give references the definition's multiline text placed
with them; MOVE/ROTATE/SCALE, Properties rotation and scale, annotative
scaling and FLATTEN carry it.
- Tests: synthetic multiline ATTDEF and ATTRIB (read, drawn, saved, edited,
pre-2007 save, damaged embedded groups); engine INSERT, MOVE, ATTEDIT and
ATTSYNC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Multiline attributes: read MTEXT direction from its own groups (#667, as #834 does)
#834 moves these lines' fix into the MTEXT reader; the same fix in the shared
mtext_from keeps the two compatible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Attributes keep Verify, Preset and Lock position
ATTDEF/ATTRIB flags 4 (verify) and 8 (preset) and Lock position (group 280
after the tag, DXF 2007 and later) were dropped on save. Without Lock
position AutoCAD lets an attribute be dragged out of its reference, and its
dynamic block actions no longer carry it. A reference's attributes take Lock
position from their definitions, as AutoCAD saves them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Multiline attributes: read, draw and save their text
A multiline attribute (DXF attribute type 2, or 4 for a definition) has its
text as embedded MTEXT after its own groups (`101 Embedded Object`); its own
group 1 is empty. CADCraft read only the own groups, so the attribute drew
nothing, and a save wrote it back empty: its text was lost.
- `Attrib::multiline` keeps the multiline text; `text.value` holds its plain
text, which ATTEDIT and the other value editors change; an edited value
replaces the contents (`Attrib::multiline_text`).
- DXF read: ATTRIB and ATTDEF split at the marker; the MTEXT parsing is
shared with MTEXT entities (`mtext_from`).
- Drawn as multiline text; ZOOM Extents counts it.
- DXF write (2007 and later; earlier versions get one line): attribute type,
alignment point and the embedded MTEXT, only what differs from unrotated
text, as AutoCAD writes it.
- INSERT and ATTSYNC give references the definition's multiline text placed
with them; MOVE/ROTATE/SCALE, Properties rotation and scale, annotative
scaling and FLATTEN carry it.
- Tests: synthetic multiline ATTDEF and ATTRIB (read, drawn, saved, edited,
pre-2007 save, damaged embedded groups); engine INSERT, MOVE, ATTEDIT and
ATTSYNC.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Multiline attributes: read MTEXT direction from its own groups (#667, as #834 does)
#834 moves these lines' fix into the MTEXT reader; the same fix in the shared
mtext_from keeps the two compatible.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Cody Vogt <18035288+codyvogt@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A render of a large drawing (a set of sheets in model space) fits every
sheet in one image, too small to read. `render` takes `window`
[[x0,y0],[x1,y1]]: just that area, 1600 px wide with the window's shape
unless sized (the app's and the headless `ui.render` both; the display list
is built for that view only). `View::of_window` sizes it.
`find_text` (engine command `texts`, read-only) returns the readable text of
TEXT, MTEXT (formatting removed), block attributes and multileaders with
insertion point, height, rotation, style, layer and handle, filtered by
content, layer and a window. FIND selects and can replace; this only
reports, so an agent can find room names, sheet numbers and titles without
reading all the geometry.
On a 39,600-object permit set: 5,861 texts in 0.02 s; one 90 x 59.4 sheet
rendered at 2400 x 1584 in 0.5 s.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* AutoLISP: load and run .lsp routines (#758)
CADCraft runs AutoLISP routines. `.lsp` files load with APPLOAD, (load)
or by dropping them on the window, and at startup from the support folder
(acad.lsp once per session or per drawing with ACADLSPASDOC, acaddoc.lsp
per drawing, then S::STARTUP; never from a drawing's own folder).
(defun c:NAME …) defines commands, (autoload) defers loading to first
use, and (expression) and !variable work at the command line and at
prompts. docs/autolisp.md lists what is supported.
The interpreter (crates/engine/src/lisp) reads source with its own
stack and evaluates on an explicit-stack machine, so a routine can stop
for input (getpoint, pause in command) and carry on later on one thread,
deep recursion is an error rather than a crash, and long loops run a
slice per frame (Esc stops them). It follows AutoLISP: 32-bit wrapping
integers, dynamic scope, *error*, vl-catch-all-apply, AutoLISP's error
wording. About 200 built-ins cover the language, command (with pause and
commands left open), entity lists (entget/entmake/entmod/entdel/entlast/
entnext/handent, through the DXF reader and writer's groups, exposed by
cadcraft-io and handed to the engine through a hook), ssget with modes
and filters, tables, system variables, get… input with initget, and
units. A routine is one undo step; each drawing has its own symbols.
Also: keywords typed as _KEYWORD match at prompts; TEXT run by a
routine's command takes one line; DIMENSION records carry their
measurement (42); Prompt::raw shows a get… prompt as written; the
interface asks Session::prompting() (a command's or a routine's prompt)
where it asked whether a command runs, so clicks, right-click and Delete
work at a routine's prompts; getcorner and based get… prompts drag a
rubber band.
Tests: the language, errors, startup files, interactive input, hostile
input (lisp/tests.rs); the #648 corpus, 18/18, when CADCRAFT_LISP_CORPUS
points at it (tests/lisp_corpus.rs); drop, frames and canvas clicks in
the app (ui-egui/tests/lisp_files.rs).
* AutoLISP: bound memory, drop nested values iteratively, guard the per-frame tick
- Cons cells drop with a work list over both car and cdr (and function
bodies), so a list nested a million deep in its car no longer overflows
the stack when it goes away.
- Memory caps so a runaway routine fails with a LISP error instead of an
uncatchable out-of-memory abort: at most 5,000,000 live cons cells
(counted in Value::cons / Drop, checked every step; cells the drawing's
variables already held at the run's start don't count against it), no
string a built-in makes longer than 8 MiB (strcat checks before joining,
printed text is cut), and a 2 GiB per-run budget for new strings,
printed text and files read.
- Session::lisp_tick runs under catch_unwind, and so does every slice of
the LISP machine: a panic ends the routine with "internal error", puts
the drawing back and leaves the session usable.
- Regression tests for each (deep car nesting, nested functions, the cell
cap, string length and budget, a panicking built-in in a blocking run
and in a ticked one).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The view-limited path differs from full detail by ~3e-16 on Windows (float noise from the rotated-frame round trip); the test now compares structure exactly and coordinates within 1e-9.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Active viewport: redraw while panning and zooming inside it, without rebuilding the layout (#688)
Inside an active MSPACE viewport, middle-drag, trackpad pan and wheel or
pinch zoom change the viewport's view through set_view_quiet, which makes
no new revision. The layout's display list, which draws model space
through each viewport, was cached on (drawing, revision, zoom band), so it
kept showing the old view until a command such as ZOOM rebuilt it: the
viewport froze, or appeared to ignore the zoom.
The active viewport's contents are now drawn apart from the layout's list,
the way model space itself is drawn. They are built once for an area
around the viewport (half its larger side of margin all round), then moved
and scaled to the current view on the GPU and drawn under the layout's own
objects. They are rebuilt when the drawing changes, when the zoom has
moved 2x from the view they were built for, when the view leaves the built
area, and once a zoom comes to rest, so dash lengths and curve detail
follow the viewport's scale again. A pan step takes about 1 ms with 70,000
polylines, where rebuilding the layout takes about 140 ms per frame.
Rectangular viewports are clipped with the scissor rectangle. A clipped
viewport's outline is drawn into a stencil buffer as a triangle fan that
flips a bit (even-odd, as the outline clips on the CPU), and its contents
draw only where the bit is set; the native window now asks eframe for an
8-bit stencil buffer, and every canvas pipeline names its format. Where
there is no stencil buffer (the CPU canvas, the web runner), a clipped
viewport's contents stay in the layout's list, whose cache now follows the
viewport's view.
Render: Options::skip_viewport leaves a viewport's contents out of a
layout, and viewport_content builds them for any area. Viewport contents
are built on all cores, like model space (130 ms to 37 ms for 70,000
polylines).
* Active viewport highlight: selection on the GPU, hover cached apart, kept inside a clipped outline
Inside an active MSPACE viewport the selection highlight was drawn as
dashed egui shapes on the CPU every frame, about 8-13 ms per frame with
7,000 polylines selected, and its geometry was cached together with the
hover, so moving the cursor over objects tessellated the whole selection
again. The selection and the hover are now cached apart, and the
selection goes through the GPU highlight path model space already uses:
about 0.1 ms per frame. In a clipped viewport it is kept inside the
outline with the stencil mask its contents use; before, it was only cut
to the outline's bounding rectangle.
With a view, each span of a pattern line was cut to the view's extent
intersected with the boundary's. Spans already lie within the boundary,
and the boundary extent comes from rotating the corners directly while a
span end comes from interpolating along an edge, so where a line passes
through a corner the two differ by a rounding error and the cut moved the
end (by 3e-16 on Windows, whose sin/cos round differently). The spans are
now cut to the view's extent only, so normal_zoom_pattern_is_unchanged
holds on every platform.