Skip to content

Releases: BlinkingSun/stl2step

stl2step 1.4.0

Choose a tag to compare

@BlinkingSun BlinkingSun released this 06 Sep 17:46

[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_ToroidalSurface face 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 at u0 / 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-red S19.mouth-round-not-built). The torus closed-360 gate uses bandArc + asin(min(1, τ/hmin)) (θ_q) instead of the retired 1.5·bandArc hand constant; U-3 probe warnings are discarded on topology revert.
  • Torus math — the circle-on-torus class (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). circleOnTorusMax returns, 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 — exactly 0 on every v-iso circle, in particular on the two tangency circles a mouth round is bounded by (S04: rho = 10 @ z = 22 and rho = 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. With pointTorusDist, torusVIsoCircle (the tier-1 bounds a blend patch carries) and torusVOfProfilePoint. 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; exactMaxAtBind still returns unhandled-surface for 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_DIAG the DIAG_LAWUNION line no longer reports the STL2STEP_UNION door'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 under max(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. sigma is U-R5's closed-form supremum max over the claim's welded edges of (R − dist(axis, midpoint)), punctures are U-R6's non-claim vertices within sigma of the surface, domainFaces counts U-R7's components of the claim plus the on-surface stars of its punctures, pinchVertices counts 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 TU src/refit_union_census.cpp (claimChordSagitta, unionCensus, declared in refit_internal.hpp; pure MeshView measurement, no Region, no build path) and unit target union_census_unit: sigma against the regular-ring closed form and a numerical supremum along every welded edge at three radii × three densities, world and placed frames, to Precision::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 55 unhandled edges on the 9-fixture edge-class red line are plane∩cylinder ellipses whose plane-side pcurve is the exact 2-D ELLIPSE GeomAPI::To2d writes and whose cylinder-side pcurve is one degree-8 Bézier with affine u and a sinusoid-approximating v. ellipseOnPlaneMax is the EXACT supremum of |E(t) − S(pc(t))| = |W + A cos t + B sin t| over [f, l]: the tan(t/2) quartic's real roots (Ferrari, threshold-free shift, fixed Newton polish) plus the endpoints; W = 0 collapses to σ_max([A B]) (ellipseTrigSupZeroOffset), the shipped case to exactly 0.0, and for a = b it is ≤ circleOnPlaneMax (the D-S3-111 triangle bound) — the sanity relation between the two classes. ellipseOnCylMax is a certified UPPER BOUND and says so in its name: the section identity (ellipseIsCylinderSection: ρ ≡ R, θ = θ₀ ± t, z = z₀ + Λ cos(t − ψ), residual carried exactly), ε_u exact 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/a and ellipseVertexBound = √(s² + h²) + s²/(2ρ_min) with |h| ≤ δ_pl, |s| ≤ (δ_cy + δ_pl|cos γ|)/sin γ; the class refuses (unhandled-ellipse-grazing) where that exceeds meshTolCap — 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; exactMaxAtBind still returns unhandled-other for an ellipse until the bind lane binds through these classes (SPEC-ellipse PART B), so edgeUnhandled is unchanged by construction and B0 holds trivially.
  • The ELLIPSE bind classellipse-on-plane (EXACT) and ellipse-on-cylinder / ellipse-on-cylinder-bound (a certified upper bound) in src/refit_ellipse_bind.{hpp,cpp} on top of src/refit_ellipse_math.{hpp,cpp}, reached by two branches in exactMaxAtBind (D-140-9 §1/§3). unhandled on 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-1 Geom_Ellipse with no closed-form supremum. The plane side is exact: both curves are evaluated at the same t, so E(t) − S(pc(t)) = W + A cos t + B sin t identically, 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 |Δθ| ≤ ε_u exactly, from the poles, by the convex hull at the Greville abscissae — while the axial coordinate is z₀ + Λ cos(t−ψ), and no NURBS reproduces cos t in t, so the axial residual is bounded per span by L_n·max_j |v_pc(x_j) − z(x_j)| + Λ h^{n+1}/(2^{2n+1}(n+1)!) at the n+1 Chebyshev 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: unhandled 55 → 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, residue ellipse.s16-post-bind-tolerance), with overTol, overCap and polylineTier2 unchanged 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 plus smoothContainedRegions/smoothContainedTriangles) was reverted by D-140-5's sunset. Changes 1–2 — regionClosureHealEligible dropping builtAs == BuiltAs::Single in favour of regionBuiltAnalytic(...), and fallbackGuardPass == 0 widened to <= 2 — were kept as "dormant" by D-140-5, then reverted by D-140-12: on Linux they were live, not dormant, and let S16-R2-ChainUnstable complete the analytic path (spike-s16r2-linux: first build at 78f85b4). Restored to the 3bf0575 form: eligibility requires builtAs == Single; the heal stays behind fallbackGuardPass == 0.
  • Two RESULT keys, smoothContainedRegions and smoothContainedTriangles (D-130-10 declaration; RefitStats::containedRegions / containedTriangles, summed in Converter::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 at 3bf0575 and keeps reporting itself through DIAG_HEAL / DIAG_HEAL_SUM). The warning smooth: contained N region(s) to facets -- component built is emitted only when N > 0. DIAG_CONTAIN is emitted only from the J6 handoff, under diagP2Enabled(), with a real freeBefore > 0.
    The site never shipped live containment on any artefact this project owned (keys 0/0, DIAG_CONTAIN count 0). Sunset (D-140-4 §1(5)) fired in D-140-5 for the J6 handoff. **D-140-12 then reverted c...
Read more

stl2step 1.3.0 + SolidOut 1.3.0 for macOS and Windows

Choose a tag to compare

@BlinkingSun BlinkingSun released this 05 Sep 05:07

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 DeleteKeep anyway, then More infoRun 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::ChamferConeGeom_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 via coneFromRims), rim and seam pcurves are written explicitly (a seam needs two, at u0 and u0 + 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 signed SemiAngle, 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 conescircle-on-cone and line-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 in t and 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 an unhandled-…-cone class 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 exact pointConeDist, tier-1 IntAna cone cases (cone∩plane ⟂ axis, coaxial cone∩cylinder, coaxial cone∩cone; everything oblique returns false), 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 ChamferCone region 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 control S12-b was 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 (edgeClasses in 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; unhandled otherwise. Red lines unhandled*=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 (radiusDrift in RESULT, DIAG_RADIUS; 082194b; D-130-8) and torus-band census (DIAG_TORUSBAND, class filletStripOnTorusBand; 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 by gate_130 (tests/gates/gate_130.py; c49e169, 8a646f5; D-130-18(3)) — there is no gates/linkage_chamfer_gate.py. B2–B5 sidecars carry the generator's exactVolume; gate_130's volume cell reads it (9ccaf8d; D-130-15(1)). The coneNYI fixture-name heuristic and sidecar entries are retired (6952dcc; D-130-15(2)). Unit targets cone_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 smoothVolumeDeltaPctMax is re-derived from the built shell as the exact measured round-trip value, 0.0 → 0.0033. Its six analytic MakeEdge failed chains cl...
Read more

stl2step 1.2.0 + SolidOut for macOS

Choose a tag to compare

@BlinkingSun BlinkingSun released this 03 Sep 08:08

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.

SolidOut: imported mesh, Handle pickup.stl

SolidOut: converted STEP, 918 faces, watertight