The curves were held on a 1-2-5 ladder from the world origin, so count 3 on a 40 x 30 face gave 3
one way and 2 the other. They now follow the command's contract: the held parameter at
lo + k (hi - lo) / (count + 1), k = 1…count, over the face's extent. Faces of the body on the same
surface record are spaced together over their combined extent, so a face a boss splits still
shows the same lines straight across the split.
New case cov-isocurve-40-by-30-face-count-3: x = 10, 20, 30 and y = 7.5, 15, 22.5, to 1e-9 mm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both walked the picked curve's display polyline: a point a third along an R10 sketch arc was
0.87 µm off (8.6603, 5). They now evaluate the path's exact curves by arc length: lines and arcs
in closed form, a free-form span at the parameter its arc-length integral reaches (body edges by
their record curves, 3D sketch curves by their arc, spline, line or linked exact curves; only a
curve that exists as points alone, an intersection edge or a projected curve, runs along them).
The arc's point is now within 1e-12 mm of (10 cos 30°, 5, 0).
A t outside 0…1 was clamped silently, which the contract (`t: expr, 0…1 along it`) does not
say: it is refused ("t is 1.5: a point along a path is 0 (its start) to 1 (its end) of the way
along it"). The construction cases grade the arc and line to 1e-9 mm, past-the-end is refused,
and a new case takes the middle of a symmetric cubic spline, (20, 7.5).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A face of degree 1 in one direction (an extruded spline) was refused with "a fairness term does
not fit the net's degrees": the thin-plate energy's S_vv asks for second derivatives a linear
direction does not have. On every span that term is exactly zero, so it is dropped; the energy is
the bending along the curve and the twist. A face whose held layers are its whole net (a
single-span cubic with its edges kept, an extruded curve's two rows) has no interior control
point: it is left exactly as it is, its energy reported unchanged, 0 steps.
cov-fair-single-span-face-has-nothing-free-to-move passes and leaves KNOWN_FAILING.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The portable record's 4,000,000-item budget refused a real result: the join of a level-4
Menger sponge's level-3 sponges ended with "SMLib export: the mapped B-rep does not validate:
portable B-rep: item budget". The budget guards records from outside the kernel, and a body
the kernel built in memory from inputs already bounded where they came in isn't hostile.
Brep::validate keeps the budget for records read from outside: native design caches
(decode_native_body, behind its own 256 MiB, 1M-per-array and 20M-value caps) and STEP import.
The new Brep::validate_built runs every other check, without the cap, on what the kernel
builds: SMLib's export of an operation's result, a body's record (Body::from_record, after
the decoder has checked a file's), a part of a validated record, the record handed back to
SMLib, and the validity check. Its checks are linear in a record already in memory.
SMLib's export bounded its counts by the same budget; a count is now bounded by SMLib's own
longest array (flat::MAX_LEN, checked on entry), which keeps every offset worked out from
the counts from overflowing, and the redundant topology-length cap goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The barrel's arc has its centre across the axis, so its side is the inner (lemon) zone of a
spindle torus, which SMLib's torus does not describe (its meridian is on the X side): SMLib
(revolve-spindle) sweeps it as the exact rational surface of revolution. shell_of_a_revolved_barrel
asserted an analytic torus of major radius -10. As the owner decided, it now asserts what
matters: two flat caps and the curved side, the closed-form volume, 14 mm round at mid height,
and after the shell the two offset faces (the inner wall and floor; truck's side was in halves).
Needs SMLib with revolve-spindle (pin bump).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drafting a cube's side about its bottom edge (on the neutral plane) tilts the side and moves the
edges it shares with the top and the two faces beside it: those four are trimmed differently
(Modified); the bottom and the opposite side are untouched (Kept). Truck called all six
Modified; SMLib's history called the moved neighbours Kept. Needs the SMLib fix "history: a face
whose edges an operation moved is Modified, not Kept" (SMLib branch devgreen-kernel-records,
cdaf205) and a pin bump.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With SMLib integration-overnight + b78bd4a (the fixes of 2026-10-29), three
expectations that held a refusal now build, each checked against its exact answer:
- rig fillets/1207-fillet-box-end-edge-into-unblended-round (was ...-refused): the end
face's top edge of a cube rounded r3 along its top back edge, rounded r1 alone
(tangent chain off), builds. Its round runs on along y through the r3 round's end
until that surface drops below it at y = 17 + sqrt(5): V = 8000 - 20 (9 - 9 pi/4)
- 17 (1 - pi/4) - 0.218236 = 7957.505202 (the last term the section under the round,
integrated exactly); SMLib gives 7957.505245.
- blend_smlib smooth_edges_and_unfinished_tangent_chains_are_refused_saying_why: the
same cube builds, at that volume.
- modify_history_tests::smlib::draft_tilts_a_side_and_keeps_its_name: the rod's side,
a cylinder along the pull, drafts 5 deg to a frustum, radius 5 at the neutral plane
and 5 - 10 tan 5 deg at the top, at its exact volume.
tests/kernel-cases/fillets/KNOWN_FAILING drops 1557-fillet-vertical-corners-after-
bottom-rounds, which passes now; the other eight still fail, so the gate holds
(81 pass, 8 known failing, no regression).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since an operation's result is its record (f11c0c571), a mirrored body is
SMLib's transform read back from SMLib's export, and two SMLib defects there
made it invalid: a mirrored cylinder came back inside out the wrong way
(SmCylinder::IsNurbSurfaceCylinder; its edges 10 to 30 mm off the surface),
and every edge's own tolerance was reset to the body's (SmBrep::Transform).
Both are fixed in SMLib (fix/mirrored-cylinder-from-nurbs e02e63e, c603293,
with e008e82 for a scaled body without vertices);
this test needs the SMLib pin moved past them.
A plate with a through hole and a box with two rounds meeting at a corner,
mirrored in x = 30 and read back from their records: SMLib finds both valid
and their volumes are the originals'.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib makes a torus one face, so the face at (25, 0, 0) was the whole torus,
not a patch within half a turn each way. The patch is now the torus's piece
with x, y > 0 and z > 1, bounded by iso lines both ways (planes through the
axis, and z = 1 round the tube), and its thickened volume is the formula's
over the angles its mesh spans. (The sphere cap above it builds once SMLib's
offset takes an open edge's second trim point away from its first: SMLib
devgreen-kernel-geometry; waits for the pin.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib thickens a sheet across a convex sharp edge as other CAD does: each
face's offset is extended until the offsets meet (and trimmed where they
cross inward), where truck bridged the gap with a wedge. Owner decision:
keep SMLib's result. The exact values:
- three faces of a 10 box's corner out by 1: three slabs, a 1 × 1 × 10 block
along each edge and a unit cube at the corner, 300 + 30 + 1 = 11³ − 10³;
- into the inside corner by 1: 10³ − 9³ (it was refused); half each way:
10.5³ − 9.5³ (refused too);
- two faces across one edge: 2 · 100 + 1 · 1 · 10 = 210.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sweep along points follows the lines between them (no wall is fitted through
the points, sweep_sheet_points' contract, as sweeps_along_3d_paths already
says): along a quarter circle of 41 points the sheet is a wall per line, 40,
its area 10 × the polyline's length (within 10⁻⁴ of 10 × 15π); the bent chain
is 2 × 40 walls sharing every seam, 2·41 + 3·40 edges.
SMLib's sweep takes guide rails along a cornered path when each rail turns
with the path at its corners; a straight rail along a bent path is refused
for that reason, not as "guide rails along a path with sharp corners".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib revolves the cone frustum's sheet over angles 0…1 as one face, where
truck made two of half a radian each; the face's own span is angles 0…1 by
profile parameters 0…0.8: √125 · ∫₀^0.8 (5 + 5t) dt = √125 · 5.6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib makes a cylinder's side one face closed round its seam, and a sphere one
face; the tests picked "one half" of the side or "one sixth" of the sphere, which
were truck's pieces. The offset test moves the whole side (one face, area
2π·12·20); the thicken test takes half the side from a cylinder split at x = 0
(π·(11² − 10²)/2·20) and the whole side inward (π·(10² − 9²)·20); the sphere
test takes the cap above z = 5 (solid angle π: π/3·(11³ − 10³)).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
prism_measure's uncertain-material check built a 20 x 20 x 1e-5 box and asked that it get no
cap certificate. SMLib refuses that box (its loops fail SMLib's orientation check): at 1e-5 it
is half a body's tolerance (2e-5) thick, no solid. Refused or built without a certificate, the
property holds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
edge_at (matchsurf) and the mixed fillet mitre check evaluated `e.curve` at parameters from
`e.interval`, which is the interval on the edge's NURBS form when it has one (SMLib's edges
do): they picked and sampled the wrong points. They now evaluate `e.parameters()`, as the fill
helpers do since aa534cc6d's fix on the kernel-geometry branch. (Both tests still fail, for
their own causes: the G2/G3 match leaves a curvature jump of 2 at the seam, and SMLib's mixed
blend lies 4e-7 off its section circle.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Truck split what it swept: a big arc into two faces, a full circle into halves, a turn into
two or three parts, a sphere into six patches, a torus into six; it numbered boxes' faces in its
own order and sewed stepped cuts slab by slab. SMLib sweeps each profile segment into one face,
closes a full cylinder, sphere or torus along one seam edge, and cuts without slabs. The record,
face source, helix, display mesh, measure and boolean history tests still expected truck's
pieces. Each expectation now follows the geometry, checked by the tests' own independent
measures: every face's origin against the profile segment its mesh lies over (unchanged),
faces picked by where they lie instead of by truck's face numbers, and the merged counts by the
kernel-cases ladder's rule (each whole curve once, and each cylinder's seam): a cylinder 3
faces, 3 edges, 2 vertices; a ball cut from a slab 7, 14, 10. The truck-only stepped-prism
check (walls coalesced across slabs) becomes the property it stood for: the stock's walls stay
whole, one face each, Kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An SMLib circle edge's `interval` is on its NURBS form (`Edge::parameters`), and an analytic
face's `domain` on the form's (u, v). edges_are_on_smlibs_curves evaluated `curve` at the
form's interval and took the domain for angles; it checks the side's whole turn and height
on the form and a rim's turn by `curve_interval`. The tangent hole test's edge middles were
read the same way (the copy seemed to join faces on opposite sides of the line).
- kernel::chamfer blends the edges it is given; a feature's pick is extended by
kernel::tangent_chain first (doc eval). The chamfer tests do the same:
one_chamfer_pick_follows_the_tangent_chain_at_any_part_size removes its 54.66 mm³ again.
- Since the section hatch per part (#8529) a cut is tinted with the hatch colour its body's
name picks (Body1: steel); the GPU cap test turns the part hatch off, as the CPU one does
(2885f101b on devgreen-engine-builds).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib's draft extrusion takes one loop, so a tapered extrude of a region with holes was refused
("SMLib draft extrusion of a profile with holes"), and designs that use one no longer opened
(modern-plastic's Rest: design_corpus, action_matrix a_small_hole_through_the_wall_of_a_plastic_rest).
As other CAD does, every wall now leans by the taper relative to the pull on the material's
side: the outer loop is drafted, and each hole, taken counter-clockwise, is drafted the other
way and cut from it, so a taper that narrows the outer boundary widens the holes. Exact: each
loop is SMLib's draft extrusion of it, the walls' roles are found as the foot's are. Refused,
saying why, where the taper would make a hole meet the outer wall or another hole at either
end of the extrusion.
Test: a 40 mm square with a 10 mm square hole, 30 up at 5° inward, is the one frustum less the
other to 1e-6 with ten faces; at 30° the hole would pass the outer wall and is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chamfer_the_top_lip_at_a_tangent_wall_junction measured the removed volume as the difference of
two meshes at chord 0.0005. The display planner meshes the two bodies differently, so their
chord errors no longer cancel as truck's meshes did: 37.404, 37.672, 37.761 and 37.759 mm³ at
chords 0.002, 0.0005, 0.0001 and 0.00003, converging on the expected 37.762 (the chamfer as the
chord between the contact lines of a 2 mm ball, integrated exactly). SMLib's chamfer is that
one; the test measures at chord 0.0001.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A face's edges are projected exactly: lines, and arcs read off SMLib's NURBS within the
planar-edge tolerance (Body::planar_edges). An SMLib shell's offset corner is a NURBS whose
arc came out with its centre 2e-6 mm off and its ends up to 6e-7 mm from the lines they meet,
beyond the link's merge tolerance (1e-7): the inner loop of the sensor enclosure's case top
projected as sixteen loose curves, the offset of one found no chain to follow, and the Lid lip
extrude found no profile l13..a16 (the five acceptance scenarios, action_matrix
an_axis_through_a_corner_round_that_a_parameter_moves). Each edge's pieces now end on the
edge's vertices where they lie within the edge's and the reading's tolerance of them (the
vertex is the point both edges share), and an arc whose ends moved takes the centre nearest
its own that is equally far from both, so the sketch keeps both ends on its circle.
Body::planar_edges_tolerance names the reading tolerance planar_edges uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
map_planar_sources took every plane of a cut's tool as competing with an input face on it,
facing either way, and dropped that face's exact role. A text pocketed 1 mm down from a block's
top has its tool's top on the top's plane facing up, as the top does; the top then lost its
CertifiedExtrudeCap, and the sketch on it reported "the top cap of Extrude1 no longer exists"
(a_face_sketch_saved_with_fitted_letters_reopens_and_builds_the_same). A cut's tool faces enter
the result turned round, as the walls of the hollow, so a tool face can only take the place of a
face on its plane facing against it (a pocket's floor, a face the tool sat on from outside). Only
that orientation competes now; the existing blocking cases (a tool's bottom on a pocket floor, a
tool face against a wall) are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
same_support took two planes as one support only within 4096 ulps (normals 9e-13 apart,
origins about 1e-12). Faces SMLib rebuilds through a boolean lie within its own resolution of
the plane they were made on, not within ulps: a cap at z = 10 notched by a cut came back with
its origin 2.1e-11 off and its frame turned 1.36e-12 rad, so its CertifiedExtrudeCap was
dropped and the sketch on it reported "the top cap of Extrude1 no longer exists".
Supports are now compared at SMLib's own coincidence rule, SmPlane::CoincidenceCheck: normals
within the angle the tolerance makes across the plane (`tolerance / size`) and each origin within
the tolerance of the other plane, the tolerance being the record's `tolerance` (SMLib's per-brep
XSectTol3d, the one its booleans test surfaces for coincidence with). The body's size stands
for the plane's extent (at least as large, so no looser); rounding stays the floor. The normal
window the candidates are searched in follows the same angle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The truck booleans answered a tool clear of the body without intersecting (the operand kept,
its record shared). The SMLib boolean always rebuilt the body, so a cut that could not reach a
body still handed back a new record: a hole far from a second body reported that body changed
(#2904's test), and a body clear of a pocket's cutter was no longer the same solid. A cut or
intersection whose operands are apart (their exact boxes, grown by twice the tolerance as
union_apart takes them, do not meet) now gives the body itself with its faces and edges kept,
or nothing, with no SMLib call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two readings of the SMLib record went wrong in emboss_cylinder:
- The footprint arcs were labelled on the sketch's radial frame. The assembly gives each new
circle edge the NURBS form on its label's quarter grid, and the wall face keeps its own form on
its frame's grid (a cylinder's seam lies at an odd angle, build::SEAM_ANGLE). The two grids
differ by a non-quarter turn, so the arc's form is not a straight line in the face's (u, v)
and no exact pcurve exists: "no exact pcurve of a NURBS on a spline surface". The arcs at the
wall's radius are now labelled on the wall face's frame (form::on_frame), so their forms lie on
the face's grid and the pcurve is exact.
- wall_angles took the edge's interval as the circle's angles. On an SMLib record the interval
is on the edge's NURBS form (2πr long for a whole turn), so a wall spanned some 63 rad: a
profile across the seam was accepted, and a wall of radius 0.1 was refused as off its seam.
It now reads Edge::curve_interval.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A face that meets itself along a line (the Pinch case's boss side, turned 30 degrees: the hole's
wall touches it along a vertical line from the hole's bottom to the top rim) has its loop run up
that line and back down. Its motorcycle trace runs from the slit's tip to the bottom and the
iso-line grid meshes it, but the band between the grid cells and the boundary was filled by a
parity flood that flipped at every constraint, the slit's too: the band on one side of the slit
was taken for outside, the face did not follow its boundary, and the lenient fill took over.
The band now crosses a side the loops run along both ways without counting it, as the
triangulation itself already did. Delaunay's sag refinement had the same flood and left the far
side of a slit unrefined (the lenient fill's 0.52 mm^3 shortfall); it takes the slits too.
The Pinch case now meshes strictly: the boss side is 628.3177 mm^2 against 200 pi = 628.3185, and
hole_tangent_inside_a_stepped_boss_cuts_exactly passes. New tess test: a square with a slit
meshed by the cut grid follows the slit both ways and covers the square's area exactly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where a face meets itself along a line (a hole's wall tangent inside the wall round it: the
boss side touched by the hole along a vertical line, run up and back down), its loop runs along
that line both ways. The triangulation's region test flipped inside and outside at every
constraint side, so crossing the slit put the face's far side outside it; the follow check
wanted every boundary side under exactly one triangle, where a slit has one each side. Sides the
loops run along an even number of times no longer flip the region, and the follow check wants a
triangle per run. (The Pinch case's boss side still falls back to the lenient fill, now within
0.52 mm^3 of the exact 8717.92 where it was 340 short; the strict fill's remaining mismatch on
the seam-cut face is open.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A ring whose inner circle touches the outer one (a boss standing on a disc, tangent inside its
rim) is one loop through the touching vertex, both circles leaving it along one tangent. There
the inner circle's first node falls on the outer circle's last chord whenever the outer chord is
twice the inner (exactly but for the next order: the inner circle at s rises s^2 / 2r, the outer
chord at s/2 of its length rises L^2 / 8R, equal for L = 2l, R = 2r), as the samplings fell
here. No crossing by the untangle test, but no triangulation can keep both as constraints: the
strict fill refused the face, and the lenient one, dropping constraints, filled the inner disc
(314 of the ring's 942 mm^2, the Pinch test's 1047 mm^3 shortfall). A node of one chord lying on
another (inside it, not at its ends, and not two chords along one line, as a seam's two uses a
period apart are) now has that chord halved. Regression test: the ring meshes strictly, to
300 pi.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fillet_an_open_curved_edge, fillet_sharp_arc_and_arc_loop and
fillet_mixed_arc_and_line_removed_volume_converges took the volume a round removes as the
difference of two mesh volumes, which magnifies their sag (each mesh lies within its chord of its
faces, so the difference can be off by both areas times the chord: for the r 0.25 round at a
1e-4 chord 1.8e-3 of what it removes). Measured exactly (SMLib, 1e-8) the rounds are right:
the open curved edge's removal 25.7629198 against Pappus' 25.7629199, the sharp arc loop's against
its lens quadrature, the mixed joint's 2.7155830 and 0.6842666 against the reference 2.7155785 and
0.6842658 (1.7e-6 and 1.2e-6: the blend's own fit, whose section error the joint test reports).
The removals are now exact, the mixed one held to 1e-5 of the reference; the meshes' removal is
held to its chord bound of the exact one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Support::station reads the curve leaving a boundary from five samples: one-sided where the
support ends at the station, central where the surface goes on past it. The central five-point
formulas had the first and second derivatives' signs reversed ((-f0 + 8 f1 - 8 f3 + f4) and
(f0 - 16 f1 + 30 f2 - 16 f3 + f4) over 12h, 12h^2, the negatives of the standard ones), so the
curvature read there pointed away from where the surface bends. Truck's half cylinders ended at
every picked line and read one-sided; SMLib's cylinder is one periodic face, the seam is inside it,
and the central branch read the wall bending outward (+radial / R): the G2 and G3 matches to it
built sheets bending the wrong way, and the continuity check, reading the same frames, called
them G2. The central formulas are now the standard ones.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_split_in_the_plane_of_a_face_keeps_all_of_the_body held the two parts' volumes to the body's
within a millionth, each read from a mesh at a 1e-3 mm chord. Nothing was lost: the parts came
out at exactly the closed forms (780.3707 and 1035.2797 for the 5 mm groove, 1815.6504 together);
the whole body's own mesh sagged 0.0034 mm³ short of that round the hole, twice the millionth.
They are now SMLib's exact volumes (1e-8).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
measure now integrates to SMLib's finest relative accuracy, 1e-8 (da57f37e5, cherry-picked from
the engine-commands branch), not 1e-4: the mirrored bracket's union and twice its half, each an
integral good to 1e-8, agree to 2e-8. (The test stops earlier on this base, at the mirrored
operand SMLib's check refuses: the mirror-regressions agent's cause.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
measure asked SMLib for a relative accuracy of 1e-4, after SmApiBrepComputeMassProperties'
documented clamp to [1e-4, 1e-1]. smlib_brep_mass_properties does not go through that call: it
calls the kernel's integration directly because the clamp "leaves curved faces off by 1e-5 to
1e-3", and takes [1e-8, 1e-1] (solidcraft_smlib::MEASURE_ACCURACY). Asking 1e-4 threw that
away: a swept cylinder's centre came out 5e-7 off its exact place, and a 0.0177 mm³ crescent
(a pin 0.59 µm off its hole) 0.27% off the lens of two circles. Measured at 1e-8, the sweep
centres are exact to 1e-7 and the crescents match the closed form to 1e-6; the engine-commands
batch runs in the same 20 s.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cap_support_origin_is_not_a_trimmed_face_height read the drilled block's last face as a cap. The
record's face order is SMLib's: its last face can be the hole's wall, which has no cap loop, and
the test unwrapped None. The cap is now the first face on a plane square to z.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_lip_swept_on_a_round_rim_joins_it compared the union's mesh volume with the two operands' at a
1e-5 chord to 1e-3 mm³. Each mesh lies within its chord of its own faces, and the union is
tessellated by its own faces (the lip's foot merged into the top): two meshes of one solid differ
by up to their area times twice the chord, 0.02 here; they differed by 1.6e-3. The bound is now
that. overlapping_holes_and_mirror_joins held the mirrored union to 2v within 1e-9, below the
relative accuracy SMLib integrates volumes at (1e-4, measure::ACCURACY): the two integrals
differed by 2.3e-8. (That test now stops earlier, at the mirrored operand SMLib's check refuses,
the mirror-regressions agent's cause.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
faceted_cut_of_a_helical_groove took the volume removed as the shaft's exact volume less the cut
body's mesh volume at a 0.01 mm chord. That mesh sags inside the shaft's round wall: the uncut
shaft's own mesh at 0.01 is 0.18 mm³ short of pi r^2 h, five times the 3 % the groove is held
to, so the groove read 1.326 where the screw integral gives 1.2214 (the section's 2 pi int r dA
over 4 turns plus the ramp at z = 0). The cut is a B-rep: its exact volume (measure) gives the
removed volume within the 3 %.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
faceted_boolean took the tool mesh's triangles by vertex index and refused any edge not shared
by exactly two triangles. A body's mesh (Body::mesh) gives each face its own vertices, the faces
along an edge the very same points, so a box's mesh came in as six open patches: "the mesh is
not closed". Corners at the very same point are now one vertex first (triangles keep their order,
so the history's triangle faces are unchanged).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check_kept panicked on any edge origin that was not an operand's edge, but a new edge where two
faces meet has a Meet origin by the history's contract (EdgeOrigin::Meet), and SMLib's history
gives them: the raised band's and the thickened sheet's new edges. It now checks the edges an
operand still owns and passes over the meetings. The thickened sheet is the whole cylinder side
(SMLib's one face, truck's half): a wall stands along each of its free edges, the two rims, and
none along its seam, which the face meets itself along (2 + 2 faces, not 2 + its 3 edges).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A layout can leave triangles standing on their long side across the surface:
their corners on it, their plane across it. They count several times the area
under them (the front triangle's loft tubes 0.7% and 1.1% over the finer mesh)
and no split repairs them, since each half stands the same way.
After the sag refinement a post-pass flips a side inside the face when the
two triangles on it turn less from the surface normal the other way (by more
than a degree), keep their orientation in (u, v) and against the surface
normal (nothing folds), have area in 3D, and the new side and triangle middles
stay within the tolerance of the surface or no farther than before. Boundary
sides are never flipped.
Test: tess triangles_standing_across_a_curved_sheet_are_flipped_along_it; the
boolean_regression joined_loft_frames_reference_mesh_covers_each_face_once
case now passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refinement split the side of a triangle that sagged most. Where a long
inner side lies on the surface at its middle (a section with an inflection,
a loft tube of the bike frame's front triangle) and the triangle's short
sides sag, only the short sides were split: each split left a child with the
long side and its vertex nearer to it, and the triangle beside a split side
lost its shape the same way. The triangles flattened onto the long side
without end, refinement ran to its point budget, and the mesh of the face
counted 3.4 times its area (1.45 times on a second tube).
A triangle whose widest angle is past 150 degrees now splits its longest
side, and a split never goes across an obtuse triangle's shorter side: that
triangle's longest side is split first, along the path (Rivara's
longest-edge propagation, its lengths growing, so it ends). Only sides inside
the face are split, as before.
Test: tess sag_refinement_across_an_inflection_ends_without_slivers, a sheet
with an S section meshed with a triangle flat on the chord across its
middle; it ran to its budget of 20000 points before and adds 28 now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A whole sphere as one face has its seam as its only edge, used both ways. In a body many times
its size the seam gets two segments at the body's chord (pole, equator, pole), so no node off
the poles follows a node off them, and the loop kept its first node, a pole: its closing step
crossed the other pole, which the drift unwrapped to a whole turn ("boundary winds around the
surface"). The sphere was left out of the mesh (cut_beside_a_sealed_void_keeps_the_body: a void
r 2 in a 75 mm part, "1 face(s) don't mesh"). The loop now starts, failing a node with an
off-pole predecessor, at a node whose predecessor is a pole on the same edge, so the step into
it is that edge's own and the step into the pole is unwrapped in the loop like any other.
Regression test a_small_sphere_void_in_a_large_block_meshes (tess tests): watertight, no face
left out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
export_keeps_faces_split_on_purpose took a box whose top Split Face cut in two, dropped the
split record by hand (with_split_lines(None)) and expected the STEP file to have 6 faces: truck's
export joined pieces on one surface, "as it does for pieces a boolean left". No operation makes
such a body: the split record is only set (doc eval, Split Face), never dropped, and removing a
Split Face rebuilds the part without the split. SMLib's booleans join pieces on one surface at
their end (two touching boxes unite into 6 faces), and the local modifiers heal through
boolean_record::healed, so no operation leaves incidental pieces either. The file now has what the
body has (7 faces, the export merges nothing), and the heal every later operation applies
joins the unrecorded pieces: 6 faces in the body and in its file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib's cylinder side is one face round the whole turn (truck's two halves): a line at z = 5
splits it into a quarter and three quarters of 2 pi 10 x 20, two side faces, not three. (The
test still fails further on, at the face-split tool's "invalid argument", engine-builds'
cause fixed on their branch in 50356dbf2.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_modelled_thread_in_a_bore_meshes_inside_its_block held each face's triangle area to within 5%
of its surface's area. At the 0.1 mm tolerance, face 18 failed that: 1.777 mm^2 of triangles
over 1.522 mm^2 of surface. Face 18 is the groove's crest band where it runs out through the
block's end, a wedge at most 0.44 mm wide. Its boundary sides are the helix edge's chords, 1.37
mm long and sagging 0.062 mm (inside the tolerance). A flat triangle on such a chord bridges the
sail between the chord and the edge instead of following it, and the band is no wider than the
sag. The excess, 0.255 mm^2, is less than the sails along the face's boundary (sum of 2/3 L sag
= 0.62 mm^2). Every triangle is within the tolerance: the chord check passes. No triangulation
of the inside changes this; the boundary nodes are the shared edge nodes.
The check now allows 5% plus the boundary's sails at the tolerance, 2/3 L tol per boundary side.
A zigzag that multiplies a face's area, which this test exists to catch, is still far outside
that. On the run-out root band the bound allows about 40% at 0.1 mm and 5% plus 4 mm^2 at
0.01 mm, against the former 6x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tapered_extrude_of_lines_and_arcs expected the quarter disc (its arc meeting both lines at a
corner) to be refused, "arc meets another edge at a corner". That was truck's limit. SMLib's
curvature-keeping taper builds it as mainstream CAD does: each wall continues as itself until it
meets its neighbour. The test now checks what was built. The section at d = z tan 8 deg is the
disc of radius r + d over x >= -d, y >= -d. Its area is in closed form, and the volume is its
integral by Gauss-Legendre: 458.5870559 against SMLib's 458.5870567 (2e-9). The body has 5 faces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thread_display_tests sewed its M10 and M12 threads from cubic Bezier pieces approximating
helices, which lie about 1e-5 mm off the core and crest cylinders. No exact (same-parameter)
pcurve exists on the cylinder for such an edge, and SolidCraft's pcurves are exact B-splines
only. Sewing refused the core face ("no exact pcurve of a B-spline on a spline surface"). Truck
had fitted one. The thread feature makes its helices exactly, with SMLib's screw sweep, so the
test now does the same: the ISO ridge's section is screw-swept and joined to the core cylinder.
The section reaches a tenth of a pitch into the core along its flanks. ridge_volume is still the
thread's exact volume, and both display tests run unchanged, from the body as made and after a
STEP round trip. Imported edges that lie off their surfaces stay refused; they are Repair Part's
job.
The quality check's sag is now measured at the surface point nearest each triangle's centre.
Before, it was read at the mean of the corners' parameters. A closed surface's parameters are
kept within one period, so a triangle across the core's seam had corners a period apart, and
their mean read 6.27 mm off a triangle that lies on the surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Body size (the box diagonal every size-relative tolerance comes from: mesh chords, reach,
fit tolerances) came from the record's vertices alone. SMLib closes a sphere, a cylinder or a
torus along seam edges, so those vertices can be two poles, two seam ends or a single point: a
torus was 1 mm across (its preview at a hundredth of that took 214 720 triangles and broke the
budget), a 5 x 8 cylinder 8 instead of 16.2. The size now also takes three points along each
edge: points on the edges, not their control hulls, which for a rational edge can lie far off
it (a sheared L-prism's outline box was 49 km across).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sample_aliasing_does_not_certify_spline_caps built its trap as one polynomial of degree 17 with
a root at each of the 17 old sample locations k/16 (and a circle times such a polynomial, degree
19). A B-rep record holds splines up to degree 16 (MAX_SPLINE_DEGREE, #8618), so no record can
carry either and the evaluator refuses them: the test failed before testing anything. The trap
is now a C0 spline with a knot at every k/16: a quadratic on the line at each knot, bowing 1 mm
off it half way along the first span; and the quarter circle of radius 10 with its homogeneous
coordinates scaled by 1 + bump / 10, radius 10 at every knot and 11 at 1/32. Sampling at the old
locations still sees a line and a circle; the certifier, reading the spans' control points,
must certify neither, and doesn't.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
genuine_curves_and_oriented_traversal_are_certified built its degree-elevated line at degree
17. A B-rep record holds splines up to degree 16 (MAX_SPLINE_DEGREE, Brep::validate, #8618), so
no such edge reaches the certifier, and the evaluator refuses to build it. The line is now
written at MAX_SPLINE_DEGREE: still a line of the highest degree a record can carry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The smooth-path sweep named walls only by the profile's edges in the start plane and took any
planar face nothing claimed for the end cap. A wall the start plane meets along no edge got
no name or became a cap: round a closed path, a square tube's planar top and bottom (whole
annuli, no seam) were both called end caps; along a line then an arc, the walls SMLib makes
along the arc were unnamed or caps. Like the cornered and controlled sweeps, it now also names
walls by the profile carried half way along, takes the end cap from where SMLib carries the
profile last, and round a closed path has no caps at all.
The issue #2005 tube check counted V - E + F without the ring term: SMLib's annular planar
faces have two loops each, so it checks V - E + F - (L - F) = 2 - 2g (Euler-Poincare). Along a
line then an arc SMLib makes a wall per profile segment per path segment (two, where truck
made three).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
transform_matrix moves a body by a matrix that is not a rigid motion through SMLib, which
numbers the moved body's faces afresh, then attached the input's face sources and paint in the
input's face order. Planar certificates landed on the wrong faces (a reflected, sheared plate's
hole walls and caps no longer lay on their certified planes). Each moved face now takes the
source and paint of the face its history names; a face with no known origin takes no source,
and with no history at all the paint stays as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>