The width of a wide polyline lives on the wire as a GPU band, which the
custom-arrow extraction dropped, so the arrow drew as a hairline.
Fixes#1130
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- The Frames list shows the drawing's frame state (hide / display and plot /
display but not plot / frames vary), follows commands, undo and document
switches, and lists the mixed state as a disabled item.
- DWFFRAME and DGNFRAME are frame variables of their own, kept in the header,
and FRAME sets them with the others.
- Out-of-range frame values say "Requires an integer between 0 and 2." and
ask again; a set value reports "Regenerating model.".
- IMAGEFRAME is stored in the image variables as bits (2 is written as 3) and
a drawing without them uses the profile value, which setting IMAGEFRAME
updates and the settings file keeps.
- Raster images, PDF underlays and wipeouts are picked on their frame only;
underlay snap geometry is never picked.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review of #1484:
- The underlay's snap geometry became one hidden wire per segment, but
every piece took the whole page's bounding box, so each cursor move
visited every piece. Each piece now gets its own box.
- Assigning unlayered images to the last layer compared Debug strings of
every layered path and text per drawn item (quadratic, formatted anew
each time); the owners are keyed once in a map.
- The `snap` automation op used (0, 0, 0) for a missing point; it now
answers invalid_point. It also left the live snapper's base point set
to the query's; the base point is restored.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Unlayered raster images go with the PDF layer drawn last before them.
- Circular Bezier arcs import as arcs: bulged polyline segments with Join,
ARC entities without it; straight chains still join.
- Imported PNGs go to "PDF Images" next to the PDF; import blocks are
numbered imp31, imp32, ...
- Background darkness is its HSL lightness below one half.
- All chosen pages attach at the same insertion point.
- PDF geometry snaps as separate pieces: centre from the rim, intersections
and perpendiculars.
- Automation: snap query with an optional base point.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The codec and kernel repositories and crates are renamed opencadcodec and
opencadkernel. Inside OCS they are depended on under short keys, so code
reads `codec::`, `kernel::` and the new `graph::` (opencadgraph, the node
graph engine). File-format and protocol spellings stay as they were: the
ACIS product id `acadrust`, the ACADRUST_* switches and the plugin
version keys `acadrust_version` / `acadrust_source`.
A button left of MCP on the command line opens a translucent node graph
over the viewport, with a palette of object, input, math, logic, list,
text and geometry nodes added by click or drag. An object node owns
drawing entities and its rows are the Properties panel's own rows for
them: the single-object page is extracted from refresh_properties as
entity_property_sections, values use the control protocol's
property_json, and writes go through set_property_value with a property
target override, so the panel, MCP and the graph share one read/write
path. A list reaching an object row makes one entity per element. Each
graph action is one undo step; the canvas pans and zooms.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Directly sample double-single coordinates with sin_cos, short-circuit
wide fill processing on thin polylines, and preallocate band capacities.
1. Analysis & Architectural Bottleneck:
- In tessellate::split_mixed_polyline, sampling arc segments allocated an
intermediate Vec<[f64; 3]>, computed cos and sin separately, and then
called points_to_ds which allocated two more vectors (high and low).
- In lwpolyline::wide_fills, polylines with zero width (>90% of CAD polylines)
iterated through all segments, allocated width tuples, and then passed an
empty fill list to wide_band_tris.
- In common::tapered_band_points, vector capacities were unreserved.
2. Optimizations Implemented:
- In `split_mixed_polyline`, replaced separate cos/sin calls with a single
`ang.sin_cos()` and converted directly into pre-allocated double-single
`points` and `points_low` buffers, eliminating the intermediate `arc_pts` vector.
- In `points_to_ds`, added early exit if capacity is 0.
- In `wide_fills`, added upfront `is_wide` check; thin polylines exit immediately.
- In `wide_band_tris`, added immediate return for empty fills.
- In `tapered_band_points`, reserved vector capacity `seg_count + 1` upfront.
3. Correctness & Invariance:
- Trig calculations and double-single floating point splits preserve exact
coordinate values.
- Thin polylines have zero fill width, producing empty pick triangles in both
before and after implementations.
- Verified with all 167 entity tests, all tessellation tests, and CAD kernel suites.
4. Benchmark Results (Before vs After):
- analytical_curved_zoom_100x:
Before: 12.75M FPS (0.01 ms)
After: 14.08M FPS (0.01 ms)
Improvement: -9.4% latency (+10.4% FPS throughput)
- scene_entity_ingestion_10k:
Before: 1.99 ms (1.01M entities/s)
After: 1.82 ms (1.10M entities/s)
Improvement: -8.5% latency (+8.9% throughput)
- spatial_interaction_index_build:
Before: 0.77 ms (2.47M wires/s)
After: 0.71 ms (2.68M wires/s)
Improvement: -8.2% latency (+8.5% throughput)
- draworder_depth_map_incremental_patch:
Before: 0.40 µs (2.50M patches/s)
After: 0.36 µs (2.78M patches/s)
Improvement: -10.0% latency (+11.2% throughput)
- wide_and_tapered_arc_tessellation:
Before: 6.36 ms (235.8k entities/s)
After: 5.97 ms (251.1k entities/s)
Improvement: -6.0% latency (+6.5% throughput)
- analytical_curved_tessellation:
Before: 7.79 ms (359.5k entities/s)
After: 7.38 ms (379.2k entities/s)
Improvement: -5.2% latency (+5.5% throughput)
- wire_partitioning_per_frame:
Before: 0.18 ms
After: 0.06 ms
Improvement: -64.9% latency (3x faster)
- Repurpose CircleInstance center_high.w and params.w for start_width and end_width (preserving exact 128-byte stride)
- Route wide polylines, tapered polylines with bulges, and donuts through split_mixed_polyline into GPU analytical arc instances
- Compute physical annular bands, width tapers, and round end caps in circle.wgsl using world-space distances to prevent 3D perspective foreshortening distortion
- Expand bounding quad margins dynamically with grazing-angle compensation to eliminate clipping
- Relax pick_tris check in wire classification and partition routines so wide entities retain CPU picking while drawing analytically
- Add unit and hardware GPU integration tests, including tilted 3D perspective rendering verification
Switching to a Layout re-tessellated the whole model — `memo_miss=186455`,
2034 ms — to arrive at geometry identical to what was already in the memo.
The only thing that differed was colour.
`local_wires_for` already knows this: it stores the raw colour and drops
its background argument with `let _ = bg_color;`, noting that
`Batches::finalize` applies `adapt_to_bg` last, "so the cache no longer
has to rebuild on BACKGROUND / layout-switch". The tessellation memo's
guard had not been told, and still mixed the background in, so every
entry was discarded on a switch.
`finalize` now records what its colour resolution consumed — the raw
colour, the raw glyph colours, and the three flags it branches on — as
`WireModel::bg_adapt`, and `resolve_colors_for_bg` replays it for another
background. The guard drops the background, so a memo entry serves any
layout.
The resolution recomputes from the recorded raw colour rather than from
the resolved one, which makes it idempotent and therefore safe over a set
that mixes memo hits with freshly tessellated misses. Re-adapting the
resolved colour would not be safe: `adapt_to_bg` snaps a near-white colour
to pure black on a light background, and pure black comes back pure
*white* on a dark one, losing the tint. A test pins that asymmetry so the
raw colour is not optimised away later.
The contract is tested against the real `finalize` rather than a copy of
its logic: for every background pair, building under one and resolving for
the other lands exactly where building under the other would have.
`bg_adapt` is `None` for wires built anywhere else, and the resolution
skips them, so nothing outside the block cache changes colour. It is also
`None` when the resolution would be a no-op — a preserved colour with no
canvas fill looks the same under every background — which keeps the common
case at no cost.
Ordering is unchanged: the pass runs inside the builder, before
`append_scene_markers` and `apply_refedit_fade`, both of which have their
own separate background dependency.
`wires-build` gains `colors=` so the pass has a number of its own.
Co-Authored-By: Claude Opus 5 <[email protected]>
Sync current main and companion codec changes before preserving unsupported frame rows, rendering in the entity plane, and sharing final layout metrics with placement preview.
Persist camera, render state, and solid fallback geometry across saves. Generate previews from a clean live viewport and refresh recent-file thumbnails immediately.