Releases: BlinkingSun/stl2step
Release list
stl2step 1.4.0
[1.4.0] - 2026-09-06
Added
- Torus engine — stage T transactional reclaim + U-3 face sharing the cylinder rim (D-140-6, D-140-11). Stage T admits torus candidates, but cylinder reclaim commits only when the U-3
Geom_ToroidalSurfaceface ships; a failed U-3 build restores the pre-stage-T topology and rebuilds with fresh vertices (Body28/Body11 byte-identical to the no-torus binary). The toroidal face shares the cylinder rim via explicit pcurves (Geom2d_Line((u0−f, v), (1,0))on the reused rim TShape, both seam pcurves atu0/u0+2π, and the c-rev wire). S04's boss-top round ships as a valid closed solid (TOROIDAL_SURFACE,solids=1,faceCount=9, I-checker PASS). S19 mouth rounds, the plate mouth band, and Handle pickup admit 0 tori — the certificate is honest on the real parts (smoothBuiltTori=0, expected-redS19.mouth-round-not-built). The torus closed-360 gate usesbandArc + asin(min(1, τ/hmin))(θ_q) instead of the retired1.5·bandArchand constant; U-3 probe warnings are discarded on topology revert. - Torus math — the
circle-on-torusclass (src/refit_torus_math.{hpp,cpp},torus_math_unit; D-140-6 §3(4), the D-130-3 hard gate applied verbatim; D-130-2).circleOnTorusMaxreturns, in closed form, the supremum distance from a circle COAXIAL with a ring torus to the untrimmed toroidal surface: every point of such a circle has the same(rho, z) = (r_c, h), so the distance is the constant|hypot(r_c − Rmaj, h) − Rmin|and that constant is the supremum — exactly0on every v-iso circle, in particular on the two tangency circles a mouth round is bounded by (S04:rho = 10 @ z = 22andrho = 7 @ z = 25; S19: both rims of both tori). A non-coaxial circle, a spindle or horn torus (Rmaj − Rmin <= gp::Resolution(), OCCT's own guard) and a degenerate tube are refused with-1.0/unhandled-circle-on-torus— tier 2, counted, never a widened tolerance. WithpointTorusDist,torusVIsoCircle(the tier-1 bounds a blend patch carries) andtorusVOfProfilePoint. Own translation unit for the reason the cone math has one (0d67eba): the P1 include allowlist is neither touched nor widened. No build path is edited;exactMaxAtBindstill returnsunhandled-surfacefor a torus until the build lane binds through this class. - The same-surface union census is readable with the door closed (D-140-8 U-R13; SPEC-union PART B). Under
STL2STEP_LAWBAND_DIAGtheDIAG_LAWUNIONline no longer reports theSTL2STEP_UNIONdoor's behaviour: it takes a copy of every law band's claim, applies D-140-8 U-R1…U-R4 to the copy (the D-130-13(2) same-surface islands undermax(bandResid, q), the joint-refit roll-back, the axial-extent overlap, the mutual-containment merge, in band order) and prints, per certified surface,nTri sigma punctures edgePieces domainFaces pinchVertices sizes=[..] pieces=[..] domainTris loc dir.sigmais U-R5's closed-form supremummax over the claim's welded edges of (R − dist(axis, midpoint)), punctures are U-R6's non-claim vertices withinsigmaof the surface,domainFacescounts U-R7's components of the claim plus the on-surface stars of its punctures,pinchVerticescounts U-R8's vertices whose link inside a component is two or more fans. The copy is never written back:claimTris, the band fits and every emitted Region are untouched, so the shipped STEP is byte-identical with the diag on or off and with the door open or closed (the plate:R=10.000000 nTri=255 sigma=0.019733720 punctures=8 edgePieces=6 domainFaces=1 pinchVertices=0 domainTris=279; Handle pickup:R=3.999998 nTri=52 punctures=20 edgePieces=11 domainFaces=1 pinchVertices=0 domainTris=84). New TUsrc/refit_union_census.cpp(claimChordSagitta,unionCensus, declared inrefit_internal.hpp; pureMeshViewmeasurement, no Region, no build path) and unit targetunion_census_unit:sigmaagainst the regular-ring closed form and a numerical supremum along every welded edge at three radii × three densities, world and placed frames, toPrecision::Confusion(), plus the U-R6/U-R7/U-R8 counts on hand-built interrupted rings. - Ellipse math — the two closed forms behind the ELLIPSE bind class (
src/refit_ellipse_math.{hpp,cpp},ellipse_math_unit; D-140-9 §2, D-130-2, D-S3-111; the third instance of the 130-CONE-MATH / torus-math shape). Measured first (SPEC-ellipse PART 0): all 55unhandlededges on the 9-fixture edge-class red line are plane∩cylinder ellipses whose plane-side pcurve is the exact 2-DELLIPSEGeomAPI::To2dwrites and whose cylinder-side pcurve is one degree-8 Bézier with affineuand a sinusoid-approximatingv.ellipseOnPlaneMaxis the EXACT supremum of|E(t) − S(pc(t))| = |W + A cos t + B sin t|over[f, l]: thetan(t/2)quartic's real roots (Ferrari, threshold-free shift, fixed Newton polish) plus the endpoints;W = 0collapses toσ_max([A B])(ellipseTrigSupZeroOffset), the shipped case to exactly0.0, and fora = bit is≤ circleOnPlaneMax(the D-S3-111 triangle bound) — the sanity relation between the two classes.ellipseOnCylMaxis a certified UPPER BOUND and says so in its name: the section identity (ellipseIsCylinderSection:ρ ≡ R,θ = θ₀ ± t,z = z₀ + Λ cos(t − ψ), residual carried exactly),ε_uexact from the poles by the Bernstein hull, the axial residual per knot span by Chebyshev interpolation (L_n max_j |v(x_j) − z(x_j)| + Λ H^{n+1}/(2^{2n+1}(n+1)!), the refused midpoint-Taylor form is 2⁸ = 256× looser), composed through the exact two-point identity on a cylinder. The grazing predicate (D-S3-111's open predicate):ellipseCylGrazingSin = |N̂·d̂| = cos α = b/aandellipseVertexBound = √(s² + h²) + s²/(2ρ_min)with|h| ≤ δ_pl,|s| ≤ (δ_cy + δ_pl|cos γ|)/sin γ; the class refuses (unhandled-ellipse-grazing) where that exceedsmeshTolCap— never a hand angle. Every refusal is named (unhandled-ellipse-plane-{param,degenerate},unhandled-ellipse-cyl-{oncyl,uaffine,span,degenerate}). No build path is edited;exactMaxAtBindstill returnsunhandled-otherfor an ellipse until the bind lane binds through these classes (SPEC-ellipse PART B), soedgeUnhandledis unchanged by construction and B0 holds trivially. - The ELLIPSE bind class —
ellipse-on-plane(EXACT) andellipse-on-cylinder/ellipse-on-cylinder-bound(a certified upper bound) insrc/refit_ellipse_bind.{hpp,cpp}on top ofsrc/refit_ellipse_math.{hpp,cpp}, reached by two branches inexactMaxAtBind(D-140-9 §1/§3).unhandledon the 9-fixture edge-class red line was 55, and every one of the 55 was an ellipse — an oblique plane∩cylinder cut shipping as tier-1Geom_Ellipsewith no closed-form supremum. The plane side is exact: both curves are evaluated at the samet, soE(t) − S(pc(t)) = W + A cos t + B sin tidentically, and the supremum is the maximum of|·|over the real roots of the quartic inτ = tan(t/2)plus the two endpoints. The cylinder side is a bound, and says so in its name: under the section identity (centre on axis,a|Û⊥| = b|V̂⊥| = R,Û⊥·V̂⊥ = 0) the azimuth is affine — so|Δθ| ≤ ε_uexactly, from the poles, by the convex hull at the Greville abscissae — while the axial coordinate isz₀ + Λ cos(t−ψ), and no NURBS reproducescos tint, so the axial residual is bounded per span byL_n·max_j |v_pc(x_j) − z(x_j)| + Λ h^{n+1}/(2^{2n+1}(n+1)!)at then+1Chebyshev nodes, over an exact de Casteljau subdivision (every depth is a valid bound, so the minimum over a fixed depth set is taken). The two compose on the cylinder as√(4R² sin²(ε_u/2) + max|Δz|²). Refusal is by named clause, never by silence:unhandled-ellipse-{plane-param,plane-degenerate,cyl-oncyl,cyl-uaffine,cyl-span,cyl-degenerate,grazing}; the range clause carries D-130-16's seam sub-clause, so a full-turn ellipse edge is refused rather than certified. Measured:unhandled55 → 1 (Body28 25→0, S11-b 24→0, linkage_bores_chamfer 4→0, handle-pickup 1→0; S16-R1-explode-success stays 1 under the seam clause, residueellipse.s16-post-bind-tolerance), withoverTol,overCapandpolylineTier2unchanged on every fixture and every corpus STEP byte-identical. No tolerance writer, no pcurve writer and no instrument was touched. - J6/free-edge containment, landed then fully sunset (D-140-4, D-140-5, D-140-12; SPEC-reverts-cycle2 §2). Cycle 2 landed three edits; none of them ships. Change 3 (the J6
if (j6UncollapsePass > 0)handoff plussmoothContainedRegions/smoothContainedTriangles) was reverted by D-140-5's sunset. Changes 1–2 —regionClosureHealEligibledroppingbuiltAs == BuiltAs::Singlein favour ofregionBuiltAnalytic(...), andfallbackGuardPass == 0widened to<= 2— were kept as "dormant" by D-140-5, then reverted by D-140-12: on Linux they were live, not dormant, and letS16-R2-ChainUnstablecomplete the analytic path (spike-s16r2-linux: first build at78f85b4). Restored to the3bf0575form: eligibility requiresbuiltAs == Single; the heal stays behindfallbackGuardPass == 0. - Two RESULT keys,
smoothContainedRegionsandsmoothContainedTriangles(D-130-10 declaration;RefitStats::containedRegions/containedTriangles, summed inConverter::run). Incremented at exactly one place — the J6-handoff demotion, after the admitted set shrank. Never on the seamed360-fail explode path, never on the D-S3-54 closure heal (which already fires at3bf0575and keeps reporting itself throughDIAG_HEAL/DIAG_HEAL_SUM). The warningsmooth: contained N region(s) to facets -- component builtis emitted only whenN > 0.DIAG_CONTAINis emitted only from the J6 handoff, underdiagP2Enabled(), with a realfreeBefore > 0.
The site never shipped live containment on any artefact this project owned (keys0/0,DIAG_CONTAINcount0). Sunset (D-140-4 §1(5)) fired in D-140-5 for the J6 handoff. **D-140-12 then reverted c...
stl2step 1.3.0 + SolidOut 1.3.0 for macOS and Windows
Downloads
SolidOut 1.3.0 — the desktop app (same UI on both platforms, engine 1.3.0 built in; no separate install)
| Platform | File | Notes |
|---|---|---|
| macOS (Apple silicon) | SolidOut-macOS-AppleSilicon.dmg | Developer ID signed, notarized and stapled. |
| Windows 10/11 (x64) | SolidOut-Windows-x64-setup.exe (recommended) · SolidOut-Windows-x64.msi | Not yet Authenticode-signed: SmartScreen warns on first launch — in Edge use the arrow next to Delete → Keep anyway, then More info → Run anyway; or fetch with curl.exe -L -o from PowerShell, which skips the browser prompt. Needs WebView2 (built into Windows 11). |
| Checksums | SHA256SUMS.txt | Windows files. |
SolidOut is closed source; its binaries ship here as release assets. The engine it drives is this repository.
stl2step CLI 1.3.0 — build from source on macOS, Linux (Debian/Ubuntu fixed in 955a0e4) or Windows per the README; the CLI is what SolidOut bundles.
What 1.3.0 converts that 1.2.0 did not (measured on the linkage-plate sample shipped in tests/corpus): through-holes as closed cylinders, conical chamfers as real cone faces, outer profile arcs and slot ends as partial cylinders, intercepted bores — 22 cylinders, 2 cones and 49 true circles where 1.2.0 wrote 1,517 facets; watertight, deterministic, round-trip volume error 0.0004 %. Toroidal rounds and freeform (lofted) surfaces stay faceted by design — see Deferred below.
Engine changelog (1.3.0)
Added
- Cones as a surface type: chamfer frustums (
Origin::ChamferCone→Geom_ConicalSurface; D-130-3). Detector C claims a ring adjacent to one accepted closed-360 cylinder and one cap, sharing the axis, with two rim radii and a constant slope, and ships it as a real cone face: the face is built on the same surface handle the bind site uses (SurfVar::ConeBase, from the fitted rims viaconeFromRims), rim and seam pcurves are written explicitly (a seam needs two, atu0andu0 + 2π), the seam is an explicit generator with both vertices snapped to it, and the apex is excluded by construction because the face's v-range runs between two positive-radius rims (b50328c, 49952bc). The taper sign is carried in a signed height and a signedSemiAngle, never by negating the axis, so both frusta of a hole-mouth pair build in one frame (30f67dd). Detector C runs a first pass right after the law bands and detector A — before B1 — so the exact frustum claims the ring before B1 can commit ~60° arcs of it as cylinders; the original post-B1 pass is kept (978ab69; D-130-3, D-130-8). General cones (apex inside the face, oblique sections) are not built (D-130-3). - Certified bind classes on cones —
circle-on-coneandline-on-cone(src/refit_cone_bind.{hpp,cpp},cone_bind_unit; 10ce7cd; D-130-3's hard gate). A rim circle binds only when its pcurve is a v-iso line and the circle is coaxial with the cone, in which case|C3d(t) − S(pc(t))|is constant intand the endpoint value is the supremum (proved, not sampled); a seam binds as an affine generator whose supremum is at an endpoint. Every other cone edge returns anunhandled-…-coneclass and ships as the mesh polyline — tier 2, counted (D-130-2). - Cone math (
src/refit_cone_math.{hpp,cpp},cone_math_unit; b3114da, 7f4eb5f, 0d67eba):circleOnConeMax(exact for coaxial and perpendicular-plane circles, a provable upper bound otherwise, invariant under rigid placement), the exactpointConeDist, tier-1IntAnacone cases (cone∩plane ⟂ axis, coaxial cone∩cylinder, coaxial cone∩cone; everything oblique returnsfalse), and the frustum helpers. Kept in its own translation unit so the P1 include allowlist is neither touched nor widened (0d67eba). - Cone volume prediction (19ec800; D-130-11(2)): a
ChamferConeregion predicts its facet-to-surface volume in closed form — the circular-segment gap between each inscribed facet and the frustum, summed as an exact per-triangle density over the triangles it claims — so a built cone is no longer read as zero-area by the cascade budget and the D4 guard (I9 green on the plate). At equal rim radii the formula collapses to the cylinder's own. - Mesh quantization floor (
src/stl_quant.{hpp,cpp},DIAG_QUANTFLOOR,quant_floor_unit; 61fab1d; D-130-12): the resolution of the number format a mesh file was written in, at that mesh's own coordinate magnitude — for a binary STL the binary32 ulp at the largest |coordinate| in the file times √3/2; for an ASCII STL the printed-decimal grid at the same magnitude. Measured from the file; an unreadable file yields 0, which certifies nothing. It is the certificate the absorb below runs on. - Law-band extent — the absorb (4af42c2, 3d25757; D-130-9, D-130-12; supersedes D-130-11(1)): a recognised law band owns every adjacent unclaimed triangle whose vertices lie on the cylinder the band has already certified, admitted only inside
max(the band's own residual, the mesh's quantization floor)— never a part-scale budget. After each round the band's cylinder is refit by least squares over its members with the axis direction among the unknowns, and a round whose refit leaves the certificate is rolled back whole. On the plate this closes the R 8.5 through-bore (6 → 139 triangles) and completes both R 30 profile arcs to exactly the sidecar's 75 and 80 triangles. The planar noise floor D-130-11(1) proposed as the certificate was measured (DIAG_NOISEFLOOR, a7f7b99) and refused: on the plate its median is 1.7e-15 mm and its max 0.019 mm — 105× looser than the budget it was meant to tighten. - Virtual generators (e2cf8f5; D-130-8): a law band's chain may be built on the lines where two adjacent facet planes meet rather than on the mesh edges the tessellation happened to draw, so a staggered strip chains at every fold the mesh resolves. The virtual path runs only where the mesh-edge chain refused. On the plate the four R 3 slot ends are recognised for the first time, the R 8.5 bore is claimed as a full 92-generator closed chain, and the cross bore reaches 214 of its 279 ground-truth triangles.
- N-gon wall cylinders (
Origin::NgonWall, detector A; 49952bc, ad5c814): closed unclaimed A2 walls (N ≥ 6, generators parallel) are claimed as right circular cylinders after the law bands and before B1. The accept test is the vertex residual against the fitted circle, with no chord-sagitta term (ad5c814): with the sagitta term the non-concyclic hex controlS12-bwas accepted at a 7 %-of-R residual; without it, refused. Detector A can certify a through hole: a link sharing exactly one mesh edge with its neighbour is no longer read as a turning-axis blend, and a single-cap ring (a chamfered through hole) is collected (ad5c814). - Planar CIRCLE loops (detector B; 49952bc, 88b14fd, 74f885c; D-130-2): LS-fit CIRCLE edges on already-analytic plane loops (N ≥ 6) where the two shipped surfaces yield no curve at all. A plane-loop CIRCLE replaces the polyline only when both sides of the chain are real regions that can share it (88b14fd), and it never replaces a curve derived from the shipped surfaces — two distinct planes meet along their line and nowhere else (74f885c, 47bdd57;
DIAG_130_PLCKEEPLIN). - Edge-class census and gate (
edgeClassesin RESULT;tests/gates/edge_class_gate.py+tests/gates/baseline/edge-class-ratchet.json; 9249ab0, 149564f, 082194b; D-130-2, D-130-5, D-130-7(c)). Every edge two analytic faces share is classified on the shipped shell: tier 1 (analytic) when the curve is a line, circle or ellipse and the closed-form bind-site supremum exists on both surfaces; tier 2 (polylineTier2) when it is a mesh polyline — counted, never absorbed;unhandledotherwise. Red linesunhandled*=0,dev>tol=0,tol>meshTolCap=0; the tier-2 count is a never-increase ceiling. Expected-red on 9 fixtures, recorded not relaxed (082194b): Body11 (overCap), Body28 (overCap,unhandled), S03 (overCap), S04 (overCap), S09 (overCap), S11-b (unhandled), S16-R1-explode-success (overCap,unhandled), handle-pickup (overCap,overTol,unhandled), linkage_bores_chamfer (overCap,overTol,unhandled). - Radius truth census (
radiusDriftin RESULT,DIAG_RADIUS; 082194b; D-130-8) and torus-band census (DIAG_TORUSBAND, classfilletStripOnTorusBand; D-130-7(c)): measurement only. Plate max |R_built−R_lsq| 1.55e-7 mm, torus-band count 0; S11-b's 12 fillets stay off-torus. - Fixtures and gates: the Mastercam linkage plate
linkage_bores_chamfer(49952bc) with a sidecar measured from the mesh; synthetic battery B2–B6 (cross_bores,chamfer_straight_ring,boss_cone_chamfer,counterbore_chamfer,cyl_meets_chamfer) via the corpus generator (beb6322; D-130-5). Contract owned bygate_130(tests/gates/gate_130.py; c49e169, 8a646f5; D-130-18(3)) — there is nogates/linkage_chamfer_gate.py. B2–B5 sidecars carry the generator'sexactVolume;gate_130's volume cell reads it (9ccaf8d; D-130-15(1)). TheconeNYIfixture-name heuristic and sidecar entries are retired (6952dcc; D-130-15(2)). Unit targetscone_math_unit,cone_bind_unit,quant_floor_unit.
Changed
- Body28 builds (D-130-6; 0361889): main reverted the whole component; on this branch it is a valid closed solid of 499 planes + 263 cylinders (2533 fewer faces than main's faceted answer), enabled by the two plate heals (7a1c822, e7c00a8). Its sidecar cell
smoothVolumeDeltaPctMaxis re-derived from the built shell as the exact measured round-trip value, 0.0 → 0.0033. Its sixanalytic MakeEdge failedchains cl...
stl2step 1.2.0 + SolidOut for macOS
stl2step 1.2.0 — analytic flats at construction
Engine
- Planar regions' boundary loops are ordered by an exact connectivity walk and oriented by continuity at construction, with the loop's sense taken from its own signed area. On the bundled Handle pickup fixture all four large flats build valid on the first attempt; the flange ships as its two CAD faces.
- Edge tolerances at the pcurve bind site are the exact closed-form maximum deviation plus the strict-inequality margin, at one site for planes and cylinders.
- Deterministic output under any thread count.
- Builds against OpenCASCADE 7.9 and 8.0 (the removed legacy typedef headers are no longer used).
- The four long-failing p2buildtest fixtures pass; the full suite (68 tests, including a new guard that ties the binary version to the CMake project version) passes on macOS, Linux and Windows.
Known limitations (registered, see CHANGELOG): output is deterministic per platform but not bit-identical across CPU architectures (one bundled fixture's torus-band recognition differs on x86-64 vs arm64); some seam chains still ship as mesh polylines; 27 small sliver plates reach validity through a repair pass; nine single-closed-edge inner loops are flagged by an internal assertion with no output consequence.
SolidOut for macOS (Apple silicon) — attached DMG
- Thin desktop app over this engine (engine 1.2.0 bundled with OpenCASCADE 7.9.3; no Homebrew needed).
- Signed with Developer ID Application: Joshua Roberts (L3LP86Z6L4) and notarized by Apple.
- Export STEP always opens the save panel; the last-used folder is only its starting location.
- Convert shows in-progress status under the Import | STEP pills. No floor grid.

