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>
DIMAUNIT 4 is Surveyor's units, but format_angular_value matched only
0-3 and let 4 fall through the catch-all to decimal degrees. An angular
dimension in a drawing authored with surveyor units therefore read as
plain degrees, while AUNITS 4 in the same drawing writes a bearing for
the coordinate readout — the two disagreed on one angle.
Format the bearing from the dimension's own DMS path so it carries
DIMADEC, DIMAZIN and DIMDSEP, quantise before picking the quadrant so a
value just short of a cardinal does not quote a quarter turn, and offer
the option in the dimension-style dialog.
The Properties panel reads and writes ACAD_DSTYLE overrides for all three
dimension colours (DIMCLRD 176, DIMCLRE 177, DIMCLRT 178), but the
renderer consulted the override only for DIMCLRD and took DIMCLRE/DIMCLRT
straight from the dimension style. Editing 'Text color' on a dimension
therefore showed the new colour in the panel while the drawing kept
rendering the inherited style colour — which for the common
DIMCLRT = ByLayer default is the layer colour. (#898)
Route all three through one helper so the renderer and the panel read the
same value.
format_engineering split feet from inches before rounding to DIMDEC, so a
remainder that rounds up to a full foot printed out of range: 35.999" at
two decimals read 2'-12.00" instead of 3'-0.00". A magnitude that rounded
away to zero also kept a lone minus sign.
Round on the decimal grid first and split afterwards, matching the carry
handling already used by format_architectural and format_fractional.
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>
A two-line angular dimension stores two lines, not two rays:
first_point→second_point is one, angle_vertex→definition_point the other,
and the angle is the one they make where they cross. The renderer read
angle_vertex as the centre and the other two points as the rays from it,
which measures something else entirely — a ten degree angle came out as
two hundred and seventy, and a shallow one as very nearly a full circle.
The lines are now intersected for the real vertex, and the arc point
picks which of the four angles at that crossing is meant. Each line
offers its direction and its reverse, and the sweep is tried from either
line: taking only the first→second order leaves out half the angles, and
the half left out is the one wanted whenever the lines are nearly
parallel. Parallel lines cross nowhere, so only the extension lines are
drawn.
A zero sweep was also being turned into a full turn, because the wrap
that pushes a negative sweep forwards ran on zero too. Two rays pointing
the same way enclose no angle and now draw none.
Checked against the three angular dimensions in a drawing that carries
its own baked arcs: the vertex and sweep computed from the stored line
endpoints reproduce the baked centre and angle to the decimal — 7.48,
10.61 and 2.29 degrees — from entirely separate inputs.
This is the path taken when a dimension has no baked block: one OCS drew
itself, or one whose block was dropped by an edit. Both paths now agree.
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.