JOIN, PEDIT convert, and EXPLODE rebuild polyline-family entities from
scratch and copied only common entity data, so a thickness (DXF 39) set
in Properties reset to 0 after those edits. Carry the source thickness
onto the rebuilt Line/LwPolyline and every exploded piece, like trim and
break already do for arcs.
Fixes#916
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.
Temporary fence pieces used a sentinel handle, so the source spline was included as its own cutting boundary and kernel intersection recursed indefinitely.
Refs #830
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>
EXTEND asked where a line reaches by matching over every Geo variant and
running its own line-line, line-circle and line-ellipse test — twice
over, once for a LINE and once for a polyline's terminal segment, about
two hundred and sixty lines between them. A boundary spline was walked
as sixty-four chords there rather than intersected, so an extend landed
on the chord nearest the spline instead of on the spline.
Both are one call now: shoot the segment's own infinite line and take
the nearest crossing on the side being extended. That is `cut_params`,
which every other cut in the file already goes through.
With those two gone, the sampled copy carried beside each boundary
spline had no remaining reader, so a spline boundary is now only ever
the exact NURBS. The line-ellipse adapter lost its last caller with it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SPLINEDIT's REVERSE reversed the control points and then regenerated a
clamped *uniform* knot vector. That is only harmless for a spline whose
knots were evenly spaced to begin with; for any other — which is most of
them once a modeller has been near one — the curve came back a different
shape, bulging where it had been taut. It now mirrors the knots within
the domain, which is exact, and swaps the end tangents to match.
PEDIT's Spline option evaluated a uniform cubic B-spline from four
blending polynomials written out by hand, clamped the open case by
repeating end control points, and then pinned the two endpoints
afterwards to undo the drift that leaves. A clamped knot vector says all
of that directly, and the closed case gets a genuinely periodic curve
rather than one whose seam is a special case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BLEND summed Bernstein terms using a binomial-coefficient table with
entries for degrees three and five — the two it builds — and a fallback
of one for everything else. Correct today, and quietly wrong the moment
a third continuity mode is added. De Casteljau needs no coefficients at
all, works at any degree, and is steadier: the Bernstein terms are large
coefficients times small powers and they cancel, while repeated
interpolation only ever averages neighbours.
Curvature through three points moves with it, since both are the 3D
arithmetic a blend needs and neither has a planar counterpart to
delegate to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FILLET enumerated its own candidates twice, once for a line against an
arc and once for two arcs, each with its own quadratic, its own four
sign combinations and its own conventions about which root meant what.
Both are the same construction: a circle of the given radius tangent to
a curve has its centre on one of that curve's offsets, so the fillet
centre is where an offset of one crosses an offset of the other.
That now comes from the kernel, which does it for any pair of straight
and circular curves and agrees with the corner formula where both apply.
What stays here is the part that genuinely belongs to the command —
which candidate the two picks meant, whether the touch lands on the
drawn part of an arc rather than elsewhere on its circle, and which side
of each entity to keep.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
DIVIDE and MEASURE did nothing at all on an ELLIPSE or a SPLINE. Their
length function answered `0.0` for both — the `_ => 0.0` arm — and the
command reported the entity type as unsupported. On a polyline they
measured the chords and ignored the bulges, so points landed short of
where they belonged; on an arc or a circle they read the centre as world
coordinates when the entity stores it in its OCS.
All three now walk the entity's own curve, which the kernel measures
properly: closed forms where they exist, and the integral of the speed
where they do not.
A path ARRAY had the same confusion in a different place. It sampled the
path at even *parameters*, which is the same as even distances on a line
and a circle and nothing like it on an ellipse or a spline — copies
bunched up wherever the parameter ran slow. It also now closes properly:
on a closed path the last copy no longer lands on the first.
LENGTHEN measured an ellipse with a hundred and twenty-eight chords and
then bisected on that, and a spline with forty rounds of thirty-two
chords apiece. A chord sum reads short, so "current length" was already
below the true one before any delta was applied. Both now ask the kernel,
which also makes the spline case one evaluation instead of about
thirteen hundred.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two places were left describing geometry for themselves.
A fit-point SPLINE was drawn from a C² cubic solved in spline.rs while
TRIM and snap cut a separate interpolation from the kernel. Near enough
to look right, far enough apart that a trim landed beside the line it
was aimed at. It now draws the curve those commands work on, and falls
back to the local solve only where the kernel declines — a fit spline
through points in space is not a planar curve, so there is nothing for
it to say.
Closed fit splines were worse than that: the kernel's interpolation is a
clamped solve with no wrap in it, so the curve TRIM used never returned
to its start and the seam cut nothing. Repeating the first fit point
closes it. The seam is C¹ where the rest of the curve is C², which is
the honest limit of a clamped solve — the alternative was a curve with a
gap in it.
OFFSET's preview sampling had drifted the same way TRIM's had: an arc
and a circle read `center.x/.y` as world coordinates when the entity
stores them in its OCS, so an extruded one previewed on the wrong side;
every curved kind was cut at a fixed twenty segments per radian whatever
its size; and an ellipse rebuilt its axis frame from the major axis' X
and Y alone, dropping the Z the entity stores. A hundred lines of that
become one tessellation. A RAY now previews as far as an XLINE does
rather than as a stub the length of its direction vector.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three per-type matches went; what is left is only the shape of the
boundary record, which the edit options need to mutate — imply_edge_geos
turns a segment into an infinite line and opens an arc into a full
circle, neither of which a bare curve says.
Two things change with it. A boundary spline is carried as a NURBS
rather than as 64 chords, so a trim cuts where the spline actually runs;
the sampled copy stays only for the two hand-rolled ray walks in the
polyline-extend paths. And an extruded arc or circle is read in its own
OCS instead of having its stored coordinates taken for world ones, so it
acts as a boundary where it is drawn.
sample_entity_xy is now one tessellation at the density the renderer
uses, so a fence pass and a preview cut follow the drawn geometry rather
than a coarser approximation of it. It declines to guess a length for a
ray or an infinite line; fence_sample_xy, which has an answer, still
supplies one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>