Point clouds: drawn from their scans, attached, cropped and stylized.
The fork's codec patch is resolved to the upstream pins: the point cloud
records landed in opencadcodec 5ef9376, carried through opencadkernel
68eeda1 and opencadgraph e204966.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once edited, a dimension is drawn live instead of from its *D block;
on the #898 drawing that differed in three places:
- Oblique / architectural-tick strokes were DIMASZ/2 to each side, the
block's are DIMASZ/sqrt(2) (its unit stroke runs corner to corner).
- Arrowheads and ticks shared the dimension line's wire, so a dashed
dim line broke its ticks into stubs. They get their own solid wire
(and explode as lines as before).
- A diameter's user-positioned text was lifted by the DIMTAD offset even
when the stored point already was the text's middle, as a file saved
elsewhere writes it; it is lifted only when it lies on the leader
(#1323's case).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The manager's switches write the cloud's hidden scans (by the scan
identifier its project and scan file carry) and hidden regions (the
unassigned points are region 0), as the reference stores them, instead
of extended data. A parent switch shows mixed while some of its rows
are on. A cloud that shows none of its points draws its extents box.
The codec reads and writes both lists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Scans (.rcs) and projects (.rcp) are read and their points drawn as
screen-sized dots, coloured by the cloud's stylization. The cloud's
placement carries its scale and rotation; its extents and crops stay in
the scan's coordinates. A missing scan shows the extents with the saved
path. -POINTCLOUDATTACH / POINTCLOUDATTACH, POINTCLOUDCROP and the
POINTCLOUD* settings follow the reference's prompts and messages, the
Properties list is the reference's, and a locked cloud is left alone by
edits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every near-white colour turned black on a light background (and near-black
white on a dark one), including true colours. A 255,255,255 solid used as a
white mask became a black block. Now only colour 7 swaps: true colours and
ACI 255 resolve a hair off pure white/black and keep their value on screen
and in plots.
Discussion #1500
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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.
Share source buffers across block placements and render nested content with GPU instance transforms.\n\nChunk silhouettes and compact mesh material data so large drawings stay under device buffer limits and remain responsive.
The last four truck crates are gone from the manifest. What they were doing
falls into three parts, and each now has one answer instead of two.
NURBS evaluation. truck was carrying B-spline curves and surfaces for the
SPLINE entity, hatch boundaries, BLEND's endpoint frames, the spline preview
and ACIS spline-surface faces. The kernel now has both, over one de Boor
written across however many coordinates a control point holds — so a plane
curve and a space curve cannot drift apart, and a surface is the same
algorithm applied twice. The rational and the polynomial cases stop being
separate types: weights absent means polynomial.
The entity conversion. Every LINE, ARC, ELLIPSE, SPLINE and polyline was
built into truck topology purely so it could be sampled back into points.
They are sampled through `entities::curve` now, which is where each one's
geometry is already defined once and what EXTRUDE and REVOLVE read — so a
circle drawn on screen and a circle handed to the Model tab come from the
same definition. That retires four of TruckObject's variants and the module
is renamed for what it does.
SWEEP and LOFT. Both only ever produced a mesh, so both are built from point
lists: a band of quads per span, and a lid where a profile closes. Lofting
profiles of different densities resamples them by distance rather than by
index, so a circle lofted to a square no longer twists.
Two things worth noting for anyone reading the old comments. The tolerance
globals that lived in the tessellation module were never about truck at all —
they are the per-frame chord height, and they move to `curve_tol`. And the
rule that a profile handed over as `Lines` silently broke EXTRUDE and REVOLVE
no longer holds: those read the curve directly and never look at this channel.
The two `automation` test failures are unchanged from before this and are not
caused by it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Route model, paper, viewport and nested block content through one traversal while preserving leaf renderers. Carry decoded material state into mesh shading and harden solid geometry conversion.
Preload one language-aware font resource across the interface, drawing text, and navigation labels.\n\nRender the navigation cube, ring, outlines, and labels through a 4x MSAA target.
A circle with a complex linetype (embedded shapes/text, e.g. IZOLACE) drew
its dashes and symbols scattered far off the entity at UTM-scale
coordinates. Two defects compounded, both catastrophic-cancellation at a
~1.2M-unit origin:
- The single-entity complex-linetype path fed apply_along the wire's HIGH
double-single half only (`base.points`), an absolute f32 quantised to
~0.1 at 1.2M. Walking the pattern along those points blew the shape /
dash positions up to ~1e308. Reconstruct the absolute f64 path from
high+low, re-origin it at the first vertex, walk apply_along in that
local frame, then lift each result wire back to world with
shift_wire_to_world (now pub(crate)) — the same local-frame trick the
MLINE complex-linetype path already uses. Fixes every entity, not just
circles.
- The circle was tessellated with truck's circle_arc (arc-through-three-
points), whose fit cancels catastrophically at 1.2M and returned a ~3%
radius wobble with uneven segments. Tessellate directly as a cos/sin
polyline (adaptive segment count from the curve tolerance); adding a
small ±r term to the centre stays precise, and the Lines path RTE-splits
it. Arc and polyline bulges share the circle_arc root and remain to fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three defects made a leader's arrowhead render as one stray line far
longer than the leader itself, at UTM coordinates:
- The arrowhead was drawn even when it is larger than the first leader
segment it sits on. A DIMASZ sized for a bigger plot (here 6 units on a
~1-4 unit pointer) painted a stroke longer than the whole leader.
AutoCAD omits the arrowhead when it does not fit the first segment, so
gate the arrow on `arrow_size <= first_segment_len`.
- Leader geometry was built as absolute f32. At UTM coordinates (Y ~ 4.5M,
f32 ulp ~ 0.5) the oblique arrow tick's small cross-slope quantised
away, collapsing the 45-degree tick into a flat horizontal line. Build
the path and arrow in absolute f64 — the arrow around a local origin so
append_arrow's f32 math stays precise — then RTE-split into the double
single (points_low / fill_tris_low) at the end.
- The `_Oblique` / `_Archtick` arrowhead block mapped to ArrowKind::Tick
with the full DIMASZ, but the tick emitter draws `size` to either side
(half-length semantics, matching DIMTSZ). Pass half so an oblique
arrowhead sizes to DIMASZ like every other arrowhead, not double.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>