Skip to content

OpenGeometry 3.0.0 - #25

Open
aka-blackboots wants to merge 211 commits into
mainfrom
refactor-v2.5
Open

aka-blackboots wants to merge 211 commits into
mainfrom
refactor-v2.5

Conversation

@aka-blackboots

@aka-blackboots aka-blackboots commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

The new kernel, released as 3.0.0. It replaces the 2.0 code with a B-rep kernel in Rust and WebAssembly and a new TypeScript SDK for Three.js. Code written for 2.0 must change: MIGRATION.md shows how.

What 3.0.0 adds on top of the new kernel:

  • Shapes: line-and-arc profiles, arc wires, cylinder sectors and a polyhedron builder.
  • Sweeps: exact bends for ducts and pipes, closed paths, and open sweeps as sheets.
  • Openings: an arched opening or an arched door from the floor in a curved wall, and two flush openings in one wall.
  • 2D: public region Booleans (lines and arcs) and exact offsets with true arc corners.
  • Exchange: IFC shape records for a body, the B-rep placed in the world, and face normals.
  • A memory module that measures and limits WebAssembly memory growth.

The 2.0.17 and 2.0.18 fixes from main are carried into the new code. main's #28 (an exact cylinder cut by an oblique box face) is not in 3.0 yet: such a cut stops with a CoverageGap error, not a wrong result.

Checks: npm run check:full passes.

Restructure the 2.5 kernel and Three.js SDK to the planned layer tree
and enforce the coding standards mechanically, with behaviour proven
unchanged: the debug kernel snapshot (461 cases), the SDK snapshot and
the API surface are byte-identical to the Phase A baseline, the parity
oracle output equals its fixtures, and every gate passes on a clean
rebuild.

Kernel: one-way layer order, curated public API (776 to 322 paths),
every non-test function at most 80 lines and 7 parameters, shared
helpers consolidated, tests/source_rules with frozen one-way exception
lists, a test-support crate, string errors typed.

SDK: split into dto, kernel, world-graph, rendering, marks, bodies,
picking, export and runtime modules behind a 63-line facade; strict
tsconfig, typed ESLint with layer rules, source, cycle and duplicate
checks, a testing entry, and the scripts and browser tests in their
planned homes.
npm run check runs every Rust and SDK gate from main-v2.5 in a fixed
order, logs each step to .check/<step>.log, keeps going past failures
and exits non-zero with the failed step names. check:full adds the
Playwright runs on three 0.168 and 0.184. --only and --from pick a subset.
…orted

Covers --only, --from, --skip, the empty selection, unknown names and missing values, the test:wasm rename, and the performance and browser flake classifier, before verify.mjs exports them.
Clippy gets its own target dir so cargo re-lints only what changed without touching sources. A rotated20BooleanMs regression is retried twice and the memory disposal browser test is repeated five times before either counts as a failure; any other regressed metric is marked INVESTIGATE. Browser ports, Playwright output and vite caches follow OG_PW_PORT_BASE and the three version, so the 168 and 184 runs overlap. Step selection gains --skip, fails on empty selections and bad arguments with one line, and verify.mjs runs steps only as the entry script so its pure functions are testable.
… STEP

Finding O:orc-1: batch subtraction had no oracle. These tests replay a batch-matrix fixture corpus recorded from main; they fail until the oracle writes it. The STEP matrix loop becomes one helper shared by the boolean and batch matrices.
Finding O:orc-1: subtract_planar_cutters had no oracle. The traced copy now records subtract_planar_cutters and its batch handlers, and a batch module rebuilds the snapshot's batch scenes (13 entries, 4 ordered scenes and the staged mixed scene in both orders, 23 rows) with main's builders, writing each row's result, handlers and STEP files to batch-matrix/, plus a serial fallback row for non-mixed coverage gaps. The STEP writer moves to write.rs so matrix and batch share it; matrix output is unchanged. Five helpers repeat the snapshot's across kernels and get allowed duplicate groups.
…ode per context

Retires the frozen operations->world_graph upward imports and module cycle, and adds tests that SingularParameterization takes the code of its graph context and that every OperationError keeps its code and message as a GraphError. Both fail until OperationError and ErrorContext exist.
…form

The five boolean .tess.json files are never read and no matrix or batch row
has a tessellation fixture, so a regression in the ported trimmed-face
refinement goes unnoticed. Main numbers refinement midpoints from a HashSet,
so the comparison is made over an order-free canonical form built in
test-support. The batch fixture listing moves to tests/parity/support.rs so
the new test shares it.
The standards review of W-B1 found batch.rs sitting beside batch/,
which breaks the mod.rs convention for directory modules, and parts.rs
exposing upright, arc and Part's fields wider than their only users in
parts.rs need. scene and ordered now sit below their last caller, and
four duplicate-group reasons name the snapshot helpers as general
fixtures rather than batch-only ones. No output changes.
operations no longer imports world_graph: creating steps return OperationError, which world_graph converts into GraphError with the same code, message and null details. CreatingOperation and ModifyingOperation move into world_graph, and operands.rs keeps its own invalid(). SingularParameterization now reports InvalidGeometry, and every graph entry point rewrites it by ErrorContext (Create/Rebuild to InvalidParameter, Transform to InvalidTransform). The frozen upward imports and the operations<->world_graph cycle are retired; the sweep line_edge duplicate pair is re-keyed and its merge deferred to the debt note.
…sults

The oracle now tessellates every boolean fixture and every matrix and batch
row whose result holds a BRep with main and with traced, asserts the two
agree in an order-free canonical form, and writes it under tessellation/.
Main numbers refinement midpoints from a HashSet, so vertex numbering is not
stable across runs; the canonical form sorts vertices, triangles and outline
segments so the fixtures are. The canonical helpers exist once per kernel,
recorded as allowed duplicate groups.
The fixture-reading one-liner existed twice in the parity tests; it now lives
once in support.rs beside parity_fixtures, both pub(super) like every other
test-crate support module. The oracle's canonical.rs lists its steps below
their caller in call order and separates every function with a blank line.
The standards verifier found invalid declared pub(super), which from
operations/mod.rs is crate-wide, while its only callers are descendants
of operations; private is the narrowest visibility that compiles. The
blank line in sweep/mod.rs was left by the removed world_graph import.
86a80b4 removed a blank line in sweep/mod.rs, moving line_edge from line 270 to 269; the allowlist member now matches what check:duplicates reports. Hash, count and reason are unchanged.
The blank line added before canonical_vertices moved the later canonical functions down one line; the allowlist members now name the lines the duplicate check reports.
Pins D6 (a name over 4 KiB is LimitExceeded, empty stays InvalidParameter),
D7 (the exportStep node list is exempt from the 64 KiB params cap, capped at
100,000 ids, ids over 1,024 chars are InvalidParameter) and the export of a
Body's children in stored order. params.rs moves to params/mod.rs so its
tests sit beside it.
Pins typed ErrorDetails on GraphError: every GeometryError variant keeps
its code in every context, a failing chained operate names its handlers,
tool index and og_ids, a failing multi-body export names the body, and
the bindings DTO keeps the error JSON byte-identical. Retires the frozen
serde_json entries on world_graph/error.rs.
expand_export now walks every node's children depth-first in stored order,
so Solids parented under a Body are exported and Wires or Sheets under it are
reported in skipped (S:step-4). A name over 4 KiB is LimitExceeded, separate
from the InvalidParameter option faults (D6). exportStep reads its node list
through params::nodes, which is exempt from the 64 KiB params cap, caps the
list at 100,000 ids without allocating the extra one and rejects ids over
1,024 chars as it reads (D7).
…ar sweep seams stay bitwise watertight

The validity test named 20 of the 28 verdict fixtures and skipped arc, circle, cuboid and the five builder-box and builder-sphere-cut pairs. Looping over every verdict file in the folder keeps new fixtures from being skipped.

Refinement midpoints on the circular L-path sweep reuse the boundary sample they coincide with in uv; evaluating the surface there instead leaves seam edges that differ in the last bits and break bitwise watertightness. No parity fixture reaches the reuse, so it stays and this test pins it.
GraphError::Code now carries ErrorDetails instead of a serde_json Value,
so error.rs leaves the frozen serde_json lists. Operate failures raised
by the boolean handlers keep the handlers recorded so far, the failing
tool's index and the operand og_ids; multi-body STEP export failures in
preflight or emission name the failing body. The bindings build the
error JSON through ErrorDto with unchanged bytes, and the snapshot tool
records every GraphError as the same code/details/message JSON.
…gates need

The golden file hashes case records, handler listings and extra texts, so 'record text' was too narrow. CI runs on Ubuntu and macOS and compares on neither. The release step said 'locally', but the comparisons it relies on run only with CI unset, and two of them only on a machine with a golden file for its target and a timings block.
A line-and-arc Profile wire and a partial Arc wire, accepted as extrude profiles and holes so extrude reaches the arc-edged builder. Fixes the offset circular hole that was refused (the probe left the outer seam) and gives InvalidParameter for a hole that encloses the profile. SDK constants and types, golden scenes, an Unreleased migration entry and README rows.
The 2.0.18 helper moves to tests/node and the growth test is rewritten against the world-graph create calls. It counts 737 growth steps for 5,000 kept cuboids (19 to 982 pages), which decides that the memory module of OPE-17 is needed. Recorded numbers are compared only off CI.
body.getWorldBrep() returns the BRep placed by the node's world transform, read-only, with the shape id and revision kept so it matches getBrep() for an unmoved body. body.faceNormalAt(faceId, point) returns the body-local normal with the face sense applied; an unknown face is InvalidParameter. Migration entry and README rows.
OG_PRIMITIVE_CYLINDER_SECTOR builds a solid sector through the existing builder at the plane frame; a zero, non-finite or full-turn sweep and sizes at or below four geometric tolerances are InvalidParameter. SDK constant and type, golden scenes, migration entry and README row.
The 2.0.18 allocator as the root module memory: on wasm32, after a growth, it reserves one eighth of the memory between 1 MiB and 64 MiB, and stops after a refused reserve. 5,000 kept cuboids now grow the memory 22 times instead of 737. The layer list gains the memory line; the node tests port the 2.0.18 limit and worst-case tests, and the section reader is checked against the engine's import list.
A sweep with caps: false builds an open-ended Sheet body: the graph accepts a faces-without-solids result as a Sheet for that sweep only, and refuses it for a Solid request with BodyTypeMismatch. caps defaults to true, so a capped sweep is unchanged; the golden moves only by the Sweep label. The SDK gets the Sheet class and the Sheet body type. STEP export refuses a Sheet with InvalidOperand, as for a Wire.
OG_PRIMITIVE_POLYHEDRON builds a closed solid from the app's points and planar face rings. The builder checks planarity within the geometric tolerance, that each edge is used twice in opposite directions, outward winding, one shell, manifold vertices and unused or repeated points, and exits through validate(); every refusal is InvalidParameter or InvalidTopology. A polyhedron reaches the planar boolean handlers, so a hip roof minus a chimney and a wall minus a roof cut exactly. SDK constant and type, golden scenes, migration entry and README row.
…E-5)

The 2.0.17 and 2.0.18 region-boolean fixes, carried into geom2d: endpoints weld by distance instead of a grid key, the probe is bounded by the clearance to other edges, a hole goes to the smallest containing ring, a ring is degenerate when its area is below eps times half its perimeter, and an open chain or an orphan hole is an error. The planar boolean handlers map it to InvalidTopology. No stored result moves: the fixes change decisions the existing scenes never reach.
An arc segment in a sweep path builds exact bend faces: cylinder, annular plane and cone faces for each polygon profile edge, and two half-torus faces for a circle profile, all with exact pcurves. Floorkit's 0.4 x 0.25 m duct elbow and a round pipe elbow build as one arc and export to STEP. Joints at an arc must be tangent within 1e-9 rad, otherwise InvalidParameter; arcs above half a turn split into equal bends; the bend radius must clear the profile reach by four geometric tolerances; the overlap and mitre-span rules are unchanged; caps: false gives an open elbow Sheet. A sweep without arcs is byte-identical. One commit, because the face code alone is unwired and would fail the dead-code check.
The curved region boolean welds endpoints by distance with the clearance rule of OPE-5 instead of the quant grid key, in all three places that used it. The winding wraps a travel just below a full turn, so a point on an arc's seam ray winds once, and an arc end meeting a line is counted once. An orphan hole is now InvalidTopology instead of being dropped. No stored result moves.
OpenGeometry.exportIfcBody(body) returns the 2.0.18 V2 exchange body, read-only like STEP export: the world-placed BRep in Z-up metres, 27 IFC entity types, CSG with IfcBooleanResult for cavities, and the 2.0.18 refusals. Its output equals 2.0.18 byte for byte on all 50 exportable fixture and matrix bodies, recorded from the 2.0.18 crate as stored cases. A Wire or Sheet is InvalidOperand. STEP export is unchanged: its Z-up similarity and body placement are shared, not copied.
A closed path closes its last joint by the mitre rule and has no caps;
a straight closing joint is a perpendicular section. Also repairs the
IFC example fences in MIGRATION.md.
… widened tube (OPE-12)

A refused span is cleared only when a padded three-sample box of each
piece misses the trim boxes; a correction that fails in its tube is
retried once in a tube widened per axis and accepted only near its
anchor segment.
booleanRegions2D and booleanCurvedRegions2D take and return the 2.0
wire format, with OG_REGION_TOLERANCE and the exported region types;
the SDK refuses a bad operation, a non-list operand and unknown planar
fields before the kernel sees them.
…n (OPE-12)

The generic band handler gains a chain path for a cutter whose sides
cross the wall's two faces once each, as an arched profile does. The
open pieces join at the cutter's corner edges, a band face's seam turns
away from the opening, and every case it cannot cut is a CoverageGap.
The vertical-arc probes take their reach from the nearest boundary.
offset_ring_variable subtracts an exact stadium per edge from the ring
and its holes, drops a convex corner's redundant cap where nothing else
is near, refuses a self-crossing ring, and keeps an edge too short to
clip as one disk; OGWorldGraph.offsetRingVariable binds it.
…otch (OPE-20)

The band chain path takes a cutter that crosses both wall faces and the
bottom cap, or whose sill touches the cap within 4 geometric tolerances:
each wall face gets an open notch spliced into its outer loop and the
cap is cut through. Every other case stays a CoverageGap.
…nds by their centre (OPE-8)

Every curve pair also offers the curves' ends that lie on both, crowded
pieces are probed away from the other curve, duplicate pieces match by
ends and middle within tolerance, and an arc end snapped onto a ray
counts by the side its centre lies on.
…(OPE-8)

offset_polyline_regions unions exact segment rectangles with mitre or
bevel wedges; offset_path ports 2.0's one-sided offset at its own
tolerance; both are bound on OGWorldGraph. The curved boolean now looks
only at pieces whose padded bounding boxes can meet, with the same output.
Every file main changed is a 2.0 file that the 3.0 tree deletes or
replaces, so the 3.0 side is kept for each one.

- 533710f (2.0.17) and the 2D region fixes of cba5c0a are in geom2d
  (OPE-5, OPE-6); their tests moved into the crate.
- The memory work of cba5c0a is the memory module (OPE-16, OPE-17).
- The tessellation cache and accuracy rule of cba5c0a do not apply to
  the 3.0 tessellator.
- 46de326 (#28), the exact cylinder minus an oblique half-space cuboid,
  is not in 3.0 yet; tracked as OPE-33.
- The root package.json keeps the 3.0 workspace form; the version lives
  in main/package.json.
@aka-blackboots aka-blackboots changed the title Refactor v2.5 OpenGeometry 3.0.0 Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant