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>
When a − b or b − a cannot be made (a kernel error, or a body that encloses no volume), the
region's volume and centre are null, measured_by is "not measured" and not_measured gives the
cut's error; its deviation, area, faces and box are still given, the side's total is null and
the verdict says the volume is not measured. The integrated-along-lines estimate is no longer
reported as a volume.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
inspect.compare integrated a region's volume along sampling lines: the
40 x 30 x 5 slab between a box and one 5 mm taller came out at 5976.49 mm3
against 6000. The regions are still found from the two surfaces; each one's
volume, centre and box now come from the pieces of the exact cut a - b (or
b - a) that lie there, measured as inspect.measure measures a body
(measured_by: boolean). Where a cut cannot be made the region keeps the line
integral, marked measured_by: lines, with a warning. The rig case
cov-compare-taller-box passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
inspect.center_of_mass measured each body's display mesh, so a cylinder r 5,
h 10 came out at 784.13 mm3 against 250 pi = 785.40, and the answer moved with
display quality. It now takes the exact mass properties inspect.measure uses
(ModelBody::measure), skips bodies that enclose no volume as Measure does, and
lists what it skipped. The rig case cov-center-of-mass-box-and-cylinder passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib's parallel projection (CreateParallelProjectionCurves) gives every curve where the
projecting lines cross the face: a line projected down onto a ball came back as its top and its
bottom, each split at the pole, and the link's first wire was half the bottom. A projection onto
a surface is where those lines first hit it (owner's decision, as mainstream CAD does): a curve
is now kept only when the first hit on the body's mesh along the line through its middle is the
curve itself (within 4 chord tolerances), and the pieces that meet end to end are joined in
whatever order and direction they come, a piece run the other way exactly (its knots and
control points reversed; a line's, circle's or ellipse's parameter mirrored). The ball's top
is then one wire from x = -10 to 10.
three_d_sketch_curves now expects that exact curve: one wire, its ends at (±10, 0, √300), every
point of its exact curves on the sphere of radius 20 in y = 0 over the top, to the projection's
own tolerance (a millionth of the body's size), and sweeps_and_pipes_follow_3d_sketch_curves
pipes along the whole joined top. Both pass on the pinned SMLib (90aabff): the pieces lie 9.1e-6
off the sphere against a 4e-5 tolerance and meet at the pole within 4.7e-8 rad, since the fork's
8d95600 keeps the pole's exact tangent in StepBackFromSingularity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A symmetric surface.extrude given no convention now splits its distance
evenly around the start plane (10 mm: 5 mm each side), the way
solid.extrude reads it, instead of going the distance each way. Both
commands take their default from one function, which a session setting
(surface_extrude_symmetric_per_side) can turn back to the per-side
reading for surface extrudes. timeline.edit and timeline.redefine give a
one-sided surface extrude made symmetric the same default.
Saved designs are unchanged: every surface extrude saved its convention
(legacy_per_side), and one saved before the marker existed reads per side
as it always did. The help of both commands says how the distance is
read. The 3830 kernel case, recorded with the per-side reading, now says
so explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
notch_into_a_ring_chamfer_is_cut_exactly measured the notch as the ring's mesh volume before less
after (0.2906 against the integral's 0.28665) and oblique_cuts_through_a_chamfered_ring_are_exact
compared the two mirrored cuts the same way (3.3e-4 apart). The cut is right: the ring less the
cut ring is a piece of 0.2866521 mm³, the integral to 2e-6. A difference of two 1149 mm³ volumes
is not: the re-tessellated ring's mesh volume moves by 4e-3, and SMLib's mass properties (at
their finest, 1e-4 asked) by 1.5e-6 of the ring, 1.7e-3. Each cut is now measured as the piece it
took away (the ring before less the ring after, an SMLib boolean), to 1e-5 of the integral and
1e-6 between the two mirrored cuts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib keeps a whole cylinder one face and the arm's round end and the boss over it (one
cylinder) one face, named for the arm's end. Three naming tests relied on truck's pieces:
- a_cut_through_one_of_two_bosses_names_only_its_own_walls finds the boss's wall by a point
on it (the centroid of truck's half cylinder lies on nothing now) and checks it keeps its
name through the cut and the fillets;
- split_pieces_keep_their_numbers_across_evaluations keeps the torus (its sweep's walls are
whole now, their names stable) and adds a disc whose top a round groove splits into
F1:top#1 and #2;
- a_design_saved_before_pieces_were_numbered_by_place_keeps_their_numbers uses that disc,
whose kernel order numbers the pieces the other way round from their place (the post's
side of multi-body-join-cut is one face now).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- shell_tangent_chain_that_opens_every_face_explains_how_to_keep_walls: the shell is the
rounded box (its 37 x 27 x 57 core grown by the 1.5 ball: 71753.436) less the sharp 36 x 56
x 28 cavity, 15305.436, to the measure's 2e-7 (SMLib: 15305.445); truck's was 15305.28.
- press_pull_on_curved_faces: the shell's inner wall behind a fillet is offset as a face
(SMLib builds it, truck refused it as tangent to its neighbours): pulled 0.5 into the
cavity it adds exactly the quarter ring π/4 (3² - 2.5²) 18 = 38.877.
- revolve_cut_round_a_box_corner_does_not_panic: the side face is picked by its point; the
report's face index was truck's face order (SMLib's face 1 is the opposite side).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SMLib keeps a whole cylinder (and an arc of one past half a turn) as one face closed on its
seam, where truck split it in two; its holes are exact circles; its mass properties come
within 2e-7 of a closed form. The tests that counted truck's pieces or its polygon hole now
check the exact geometry:
- extrude_of_adjacent_profiles_has_whole_edges: the cut-off cylinder is 4 faces (arc side
4674 = 40·(2π − 1.2019)·23, chord side 1039.8, two ends);
- step_export_of_an_enclosure_with_a_screw_hole: 27 faces, as the file holds them;
- model_faces_gives_a_point...: the hole's wall is one cylinder, its centroid on the axis;
- draft_takes_a_cylinders_side_by_name: one side face (the draft itself waits for SMLib's
draft of a cylinder);
- convert_to_sheet_metal_takes_the_plates_face_by_name: 15000 − 62.5π to the measure's 2e-7;
- a_checkpoint_survives_file_new: a unit box measures 1 + 2e-16;
- creating_a_component_recomputes_nothing: SMLib's boolean keeps the face certificate the old
setup relied on losing, so the legacy face plane is made as a design saved before face
attachments were certified (its anchor removed) and reopened.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the section hatch per part (#8529), a cut is tinted toward the hatch colour its body's
name picks; Body1 picks the steel one, so the cap test's warm pixels were none
(cpu_picture_section_caps_hide_the_inside). The test turns the part hatch off: it checks that
the cap hides the inside and shows its colour, which the hatch tests cover per part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
solid.shell takes faces: [] since 1df330c79 (a closed hollow body), so its row in
shared_types_keep_the_handlers_messages no longer matched the shared FaceRefs message. The row
uses solid.draft, which still refuses it; FaceRefs says shell's exception.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
picked_edges_give_the_sketch_s_table picked [E + R, 0, 5]: on SMLib's extruded circle that is the
vertex where the side's seam meets the top rim, a pick either edge answers, and it took the
seam. The pick is now a quarter turn round the rim, [E, R, 5]; the product's pick rule is
unchanged (owner's call).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_face_picked_by_point_on_a_block_with_a_modelled_thread_is_found guarded #6720 (the face search
charged the whole mesh once per face) with a modelled thread's 5 000 facets. On SMLib a modelled
thread is a few exact helical faces, so the block had nowhere near the 1000 faces the test
asserts, and the case no longer reached the old charge. A block drilled with 16 x 16 holes does:
the test now asserts its faces times its mesh's triangles is past the search's 2 000 000 budget
(the old charge) while their sum is well inside it (the new), then picks its top by point.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
intersect_keeps_its_exact_cuts pinned the tilted cylinder's section to 21 segments and one
segment's ends to 1e-4: the crossings of truck's mesh. The cut is the mesh's segments, as many
as the tessellation crosses (20 on SMLib's), each end within the section's reach of the surface,
the display tolerance (a thousandth of the mesh's size, doc::project::fit_segments; SMLib's ends
lie 6e-4 off at most, the test's limit is 0.051). The test now checks that every curve is a
line, at least 16 of them, and every end lies that close to the cylinder of radius 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ids_names_suppression_marker_and_failures_survive needs a feature that a parameter change makes
fail, to see the failure survive the script's replay. It made a 1 mm round 50 mm, which truck
refused as too big; SMLib builds a convex round wider than its faces by subtraction (the owner's
overflow decision), so that round now succeeds. A radius of -1 mm fails on any kernel, and the
replay still has to keep the failing feature, its id and its marker.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_face_engraved_with_text_projects_its_letters_exactly_and_extrudes counted the letters' pieces
as control-point splines. A TrueType outline's curves are quadratic Béziers, which are parabolic
arcs, and the cut made on SMLib keeps them exact: the projection gives 222 conics and 154 lines,
no splines. The count now takes conics with the splines. (The cut itself needs SMLib 1a1bebc;
with it the test passes.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loft_cut_through_a_shelled_basin_in_either_order asserted one body. The loft's top section is
the basin's full 100 x 60 outline at z = 10, so the cut takes the walls' whole cross-section
there and leaves the walls above (z 10 to 30) touching the rest only along their outer rim, an
edge: two pieces, which a cut makes two bodies (eval's lumps rule). SMLib gives exactly that;
truck kept them as one. The test now expects two bodies, the upper one the walls' exact
924 mm² ring times 20 = 18480 mm³, and the total as before (41582.028).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_face_sketch_saved_with_fitted_letters_reopens_and_builds_the_same compared the part with the
5916.018831 mm³ its saving build measured, to 1e-3. The part is the 30 x 20 x 10 block less the
"S" 1 deep and an r 2 circle 3 deep clear of it: 6000 - 46.290121 (the letter's exact profile
area, Sketch2's smaller region) - 12 pi = 5916.010767. SMLib measures 5916.010768 (1.2e-6 off);
the saved figure was 8e-3 off. The test now checks that closed form to SMLib's 1e-8, and counts
the letter's exact pieces as conics or splines (its quadratic Béziers come in as conics).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
unsupported_face_has_explicit_fixed_alternative_and_no_live_projection needs a planar face with
neither a construction certificate nor an exact history name, so a sketch on it must be fixed.
Its face, a split top face offset up, now keeps its history through SMLib's offset and gets a
name. A rib reports no roles for the faces it builds (Traced::unnamed, eval_more), so a rib's
wall has neither: the test now picks the wall at y = 12 of a rib in a bracket's corner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
interference_lists_a_pair_it_could_not_check needed a pair interference cannot check. Its
pair, an arc revolved up to a post's wall, was one truck could not intersect ("not verified");
SMLib intersects it exactly, and that reason is gone from the code. The pairs left unchecked
are a kernel failure, the time budget, and a mesh body: an imported mesh has no exact surfaces
to intersect (owner's call). The ring is now brought in as an STL beside the post, and the pair
is listed with "mesh body: not checked" instead of passing as clear.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
freezing_rebuilt_arc_borders_keeps_authored_dimension_entity_indices needs the projected
border of the sketch's face to come back with a different number of curves. Truck split the
sector's arc into pieces that changed with its sweep (270 to 300 degrees); SMLib keeps one arc
whatever the sweep, so the count stayed 3. A notch, clear of the sector until a parameter moves
it across the arc at 90 degrees, now splits the arc border into two arcs and the notch's sides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A section's chains are walked from either end, whichever segment the mesh's triangle order
puts first at a node (doc::project::fit_segments), so a line's direction says nothing about
the cut. SMLib's box tessellates in another order and the outline came out clockwise: the
same four lines. The two box-outline tests now compare each line with its lower end first.
- The base rim of SMLib's cylinder is one closed circle edge (truck's was two seam halves), so
projecting it gives one exact ellipse. Checked against the closed form: a radius-5 circle in
a plane tilted 45° projects to semi-axes 5 and 5·cos 45° = 3.5355339 (the reported minor
radius), and every sampled point lies on x²/(5/√2)² + y²/5² = 1 within 1e-9, as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Linking a reference (sketch.include_3d, project, intersect) stored the link's wires as points
only: add_link_unless_present called add_link_with_wires and dropped the resolution's exact
curves, which only the next refresh of the sketch filled in (doc::project, since 7952516eb).
So an included box edge had no exact line until any edit refreshed it, and the wire changed
under an unrelated sketch edit. The exact curves are now set with the wires when the link is
made, as a refresh sets them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The thread forms (b4549d270) parse designations in thread_forms, whose refusal for a plain
M28 said "give it: M28x1" and, for M0 or M1e9, still offered a pitch. The hint the hole
command gave (the two coarsest ISO fine pitches that suit the diameter: M28x2 or M28x1.5) is
now thread_forms::no_coarse_pitch, used by both; a diameter no metric thread has is an
unknown metric thread. The test's "unknown thread" follows the forms' wording ("unknown
metric thread").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#5191 read the header's preprocessor version and FILE_DESCRIPTION, so a SolidWorks file named
only there was stood up. The reader had moved to crates/step (p21.rs) before #5191 merged,
and the merge kept only the originating system: a file naming SolidWorks in its description
got no exporter_up (step_replies_name_a_y_up_exporter). The parser reads both fields again,
StepImport carries them, and exporter_up looks at all three.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The patch fill meets its neighbours to G4, so Construction::Patch now offers g0 to g4: the
surface.patch command takes g3 and g4 and the Patch dialog's continuity row offers
"Curvature change" and "Smooth curvature change" by itself, from the one table.
The three-round corner test patches G2, G3 and G4 and reads the seams with the Continuity
Analysis too: the middle of each seam (30-70 %) at the order asked; the whole seam G1, since at
the corners the rounds' curvatures differ and no patch can meet both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5Lzv2n8fyKqD61NCHSF
The command worked the repair out itself for its reply and the feature worked it out again
when it evaluated. The evaluation now keeps the kernel's report on the feature's result (as
an import keeps its import report, cached with it), and the command's reply is built from
that: one repair per call, and a part with nothing to repair fails the evaluation, which
leaves the design unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new undoable feature (`repair`, one pending format file) and command with input and
output schemas. In order:
- A cylinder, cone, sphere or torus face SMLib refuses on its own is rebuilt on a NURBS
form of its surface laid on arcs from where the face starts
(`form::surface_on_own_arcs`), with SMLib's own trims: the import's quarter-grid form of
a face round most of a turn closes on itself, which SmTorus::IsNurbSurfaceTorus does not
recognise. Geometry unchanged.
- Free edges that pair up within `gap` are sewn by SMLib's stitch; GlueEdgesGeneral gives
each glued edge its own tolerance (at least twice its gap), the body's and the other
edges' stay. The result is checked against the input's own SMLib check, the
tolerance-bound tests (#5, #6, #59, #82) exempt.
- Loops of free edges left are holes: offered as surface.patch, and with patch_holes made
(flat only when the loop is planar and every face around it is a plane, else G1) and
sewn in.
The reply reports what changed face by face; nothing to do is refused with why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a_combine_tool_stays_where_it_is_placed makes a part and then moves its occurrence
to check that a combine tool stays where it is placed. Since #2804 the design's first
part is grounded when it is made, so the occurrence.move refused and the test never
reached its question. The part is now made with grounded: false, as a part that is
meant to be moved is; the checks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fillet_into_an_earlier_round picked the box's upright after rounding its top edge
and expected only that edge rounded. Since solid.fillet rounds each picked edge's
tangent chain by default (84bd2daad), the upright runs on through the first round's
end arc into the top right edge, and that is rounded too. The two-radius checks now
ask for exactly the picked edges (tangent_chain: false), and a new check rounds the
chain and expects what a round of the 38 mm top right edge takes off as well.
ribs_flush_with_a_window_change_nothing_away_from_them compared the lid with
6703.5222 mm³, the old kernel's chorded volume. The lid's closed form (the plate
with its rounded corners, the skirt, less the window, the slot and the notch) is
6703.5344 mm³, which SMLib gives; the test computes it, and the script's
description says it. Each rib's volume and area are checked to SMLib's mass
property accuracy (2e-7 relative) instead of 1e-6 absolute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The seeded robustness goldens (tests/robustness/goldens.json, tests::robustness_goldens) recorded
what truck built for seeds 0-95, so they now fail on every seed SMLib builds differently. The
kernel-cases ladder (tests/kernel-cases/ladder, cargo xtask kernel-cases --ladder), whose cases
each carry their own right answer, supersedes them.
Gone with them: fuzz::golden, the CI goldens lane (the goldens output, SLOW_SEEDS, the re-record
hint), their nextest filter and priority entries, the mutants sample and the tests/robustness/
test-data mapping in affected.py. The robustness seeds and tests::robustness stay. The docs point
at the ladder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
expected.json may give `continuity: {order: g0…g4, edges?: "blend" | "corner"}`: the
seams of each round, chamfer face or corner patch with the faces either side of the edge it
replaced, and of each corner patch with its rounds, are measured as Continuity Analysis
measures them (seam_diagnostics at its default 33 stations and default tolerances), and the
case fails naming the worst seam, its faces, the order it reaches and the next order's worst
value. The seams come from the faces' persistent names (doc::naming::blend_seams). The older
`{near, within_mm, …}` form is kept.
Every fillet-area case with a body gets its order: constant rounds and rolling corners g1,
curvature rounds g2, chamfers g0, the corner: g2 patch g2 at its seams.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An app that embeds the engine can install a check on the Session
(Session::set_command_check). Session::execute asks it before each command
with the command id, its parameters and the session (read only); a refusal
comes back as EngineError::Refused with the check's reason and leaves the
design unchanged. Without a check nothing changes.
The check belongs to the host: it survives opening and starting designs,
scratch copies (previews, off-thread evaluation) carry it, and the desktop
UI hands it on to every document tab and to designs opened in the
background.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each body a section cuts is hatched at 45 or 135 degrees in one of three
colours of one family (the cap's clay, a steel and a sage, theme tokens
`section_cap` and `section_hatch`). Bodies whose cut outlines touch or
overlap get different hatches, the angle changed first, then the colour
(solidcraft_render::hatch, a greedy map colouring of the cut); otherwise a
body keeps the hatch its name seeds, so it stays the same from frame to
frame and as the plane moves. Spacing and line width are unchanged.
The hatch rides on the palette (an Rg32Uint texel: colour, 1 + hatch), so
the viewport, headless GPU pictures and the CPU raster draw the same.
Preference Graphics > Section hatch: By part (default) or One style, the
look before this change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5Lzv2n8fyKqD61NCHSF
SMLib's import says only that it refuses a body, so the kernel now keeps a structured
refusal with the body's SMLib cache (Body::smlib_refusal / smlib_refused: the first face SMLib
refuses on its own, found by sending each face alone, and its surface kind). A STEP import sends
each part's first instance as an operation would, notes the parts that need repair in the
import report, and the browser marks them ("VALVE_COVER_01: needs repair").
A feature dialog whose operation fails on such a part says, instead of the kernel's words and
an issue report: "Couldn't join to VALVE_COVER_01. One of its rounded faces can't be used for
modelling.", with Repair part (body.repair, declared as planned, also in the part's menu) and
Show face (selects and frames the face).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An open body (a surface body, a mesh that does not close) has no inside: its faces counted
with the rest took the cap from the closed body it lay in or behind. The body's closedness,
which the kernel knows (`ModelBody::is_closed`: no shell left open, a mesh body every side of
which two triangles share), goes with it to the renderer (`SceneMesh`, `PlannedBody`,
`InstancedMesh`, a block's closed runs, the loose open triangles), and the count draws closed
bodies only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5Lzv2n8fyKqD61NCHSF
Each seam now also reports the jump of the curvature's arc-length derivative across it (G3)
and of its second derivative (G4), both from the faces' exact partials, and its order: the
highest one every station meets within the tolerances (two new tolerances, defaults in the
kernel). The verdict keeps its meaning (smooth is G2 or better). inspect.continuity replies
with the new metrics, each seam's order and a count per order; the viewport widens G3 and G4
seams, the legend and the marker labels name every order in words. The Patch, Blend and
Boundary rows offer the orders the kernel's Construction table says they build. G4's words
in en, ar, de, he, ja, ko and zh.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernel's Continuity moves out of bridge.rs into continuity_order.rs and gains G4. One
table holds each order's command word (g0-g4), its code (G0-G4, for tips and messages) and
the message ids of its label and hover text. Construction says which orders the patch,
bridge, network, match and sketch blend curve build today; Continuity::built_by is the one
refusal every command and kernel entry point uses. A design may now save g4 (format note).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
The fit-point spline's extruded side is two faces split at x = 20; the hole now
lies inside one of them, so the test clips to a real inner loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The analysis sliced the display mesh with planes: faceted, approximate, and
broken at triangle seams and trims; the window drew it as a 2D overlay without
depth while the tool was active. Now one kernel routine (kernel::iso) clips a
surface's u = const or v = const line to a face's pcurve loops; the curve through
a point (sketch projection) and the analysis grid (count each way, evenly across
the face's extent) both use it. The loop polygon follows the loop's order (a
pcurve the loop runs against is walked backwards), which the inside test got
wrong before, and closes over vertex-loop poles.
ModelBody caches the lines per face, count and display quality. The session
keeps the picked faces as view state (inspect.isocurve add/clear, hostile counts
refused); the viewport, ui.render and snapshots draw them depth tested in the
accent colour. Curvature combs on faces follow the same exact lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New metric tapped holes drill the tap maker's drill (M8 6.8 mm). The Design preference "Metric
tapped holes drill the major diameter less the pitch" (`metric_tap_drill_major_less_pitch`, off
by default) gives them the drill every metric size took before (M8 6.75 mm): the Hole dialog
shows and sends it, and `solid.hole` given a thread and no `diameter` drills it, through the
session as `auto_project` is (kept when a design is opened or started, and across tabs). Unified,
pipe and taper sizes keep their tabulated drills; a given diameter is unchanged. Saved designs
keep the drill they stored either way.
Labels in en, ar, he, ja, zh, de and ko.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
Every tapped hole now drills the tap maker's tabulated drill. ISO metric takes Yamawa YEU23's
drill size column for cutting taps (printed p.704, M coarse; pp.704 to 706, MF fine): M8 drills
6.8 mm and M12 10.3 mm, where the major less the pitch gave 6.75 and 10.25. A custom metric size
the table lacks keeps the major less the pitch. UNC/UNF (Guhring), G, Rp, Rc and NPT (Yamawa)
were tabulated already. The Thread command's suggested hole for a metric size follows.
The Hole dialog's Tap drill diameter is now a field, the tap maker's drill by default, and the
command's `diameter` with a thread is checked the same way: a drill is accepted from the
internal thread's standard minor diameter (or the tap maker's drill where that lies below it:
Rp, Rc, NPT) to 5% over the tap maker's drill. Outside that the command names the range and
why. The field's tooltip gives the drill, the minor diameter and the range. Presets keep a
chosen drill.
A saved design keeps the diameter it stored, so it builds as before, and editing such a hole
keeps its drill: no preference and no format change. Scripts that give a thread without a
diameter drill the new default; ladder 05/007, 11/003 and rig case threads/2115 follow
analytically, and their golden entries are re-measured from runs (tier 05 re-sorted by time).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
The Rc and NPT drills were rounded to a drill series from a third-party reproduction of DIN 345
and a fractional/letter inch series cited from memory. They now come from the same tap maker's
catalogue the G and Rp drills do: Yamawa YEU23, printed p.709 (Rc, the maximum straight bored
hole with non-full threads, Rc 1/16 to 4) and p.710 (NPT, the maximum straight bored hole, 1/16
to 3). A size its tables lack (Rc 5 and 6, NPT 3 1/2 to 12) is refused as a tapped hole, naming
the range. The dialog's tooltip keeps the standard's minor diameter where the thread ends.
Rc 1/2 drills 17.6 mm (minor 17.807), 1/2 NPT 17.60 mm (minor 17.473).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
An Rc or NPT hole was drilled to the exact minor diameter where the thread ends, a size no shop
stocks. It now takes the largest stocked drill not larger than that: metric drills of the DIN 345
series for Rc (to 100 mm), letter and fractional inch drills by 64ths (to 1 1/2 in, ASME B94.11M)
for NPT; past the series it is bored to the exact value. The Hole dialog's drill shows the inch
size for NPT, and its tooltip the exact standard value. Rc 1/2: 17.807 mm, drilled 17.75 mm;
1/2 NPT: 17.473 mm, drilled 11/16 in (17.4625 mm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
The tapped-hole catalogue takes ISO 7-1's Rc and ASME B1.20.1's NPT internal taper threads,
every size of the tables a modelled thread cuts. Their drill is the internal minor diameter where
the thread ends (the reference plane's minor at the face less the 1 in 16 taper over the useful
or effective thread length), derived from the standards rather than a tap maker's table. An
external R is still no hole's.
A tapped hole's options mirror the thread's: `modeled` cuts its thread into the wall, from the
entry, by the same cut as `solid.thread` (now one function both call), and `ease_out` fades its
inner end out before the bottom (ISO 3508); without it the groove stops square at the thread
depth, clear of a blind hole's bottom. The Hole dialog's Tapped rows add Rc and NPT, Modeled
and the End choice (Square, Fade, Fade Length). The format notes in pending/thread-ends.rs and
pending/pipe-threads.rs record the new keys and sizes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The thread dialog offers the pipe threads: G (ISO 228-1), Rp (ISO 7-1), R and Rc (ISO 7-1) and
NPT (ASME B1.20.1). Their sizes are listed by designation without a pitch, one start and a hand
of right or left; a size picked for a hole the dialog sized Rc stays Rc.
Rp, ISO 7-1's parallel internal thread an R screws into, is a form of its own (`rp`): the
parallel profile (H = 0.960491 P) at ISO 7-1's gauge diameters, refused on a shaft. Its cut
meets the analytic material of its profile within 1e-5, as G does.
A taper thread takes a chamfered tip: each end's cone is measured from the major diameter the
taper has at that end. Pipe threads are not ISO metric, so they give chamfer_depth.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A thread's start runs off its free end, plain or chamfered; only its inner end fades out
(ease_out) or stops square. The `ease_in` field, the command parameter (refused now, naming
ease_out), the dialog's Ease In row and their docs are removed; `pending/thread-ends.rs`
drops a saved `ease_in` as the design opens. The runout test covers the short, standard and a
sub-turn fade at the inner end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The free-end test looked for material 1 to 1.5 pitches past the face's end, beyond the first
pitch so that a chamfer the groove runs out through counts as free. A plate thinner than that
(a boss on a 1 mm plate under M10) had none there, so its top was taken for a free end and
the groove's one-pitch overrun cut into the plate (15 mm³ of a 1 mm plate).
An end is now closed when, over the overrun's whole reach, material lies outside the face's
cylinder (inside it, for a hole) at the radii the groove cuts beyond the face: a shoulder,
plate, head or blind bottom, however thin, steps out past the face, while a chamfer, neck,
countersink or counterbore steps the other way. The band test beyond the first pitch stays
for a cylinder that goes on past a split.
The regression test builds the boss as the app does (a rounded plate, a circle sketched on its
top face, extruded with Join) on 6 and 1 mm plates.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>