mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
brep: docs for schema 4, and a pcurve lift test
docs/native-brep.md describes schema 4: the records with their SMLib and STEP names, regions and shells, the loop convention, every edge's uses, the canonical order (a reversed face walked backwards), each curve and surface kind's parameterisation, pcurve rules, validation, history, the SCBREP05 envelope and what the truck adapter writes and refuses. A test lifts every pcurve of a box, a filleted box with a bore and a sphere onto its face and checks it is its own edge at both ends and the middle; with a reversed face's pcurves left in truck's oriented order it fails by 30 mm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWhMwdqJYQKQq8RCLSL1LT
This commit is contained in:
@@ -169,3 +169,34 @@ fn intersections_and_revolutions_reload_exactly() {
|
||||
assert_eq!(crate::encode_native_body(&decoded).unwrap(), bytes);
|
||||
assert_eq!(crate::measure(&decoded).unwrap().volume, crate::measure(&cut).unwrap().volume);
|
||||
}
|
||||
|
||||
/// Each oriented edge's pcurve, lifted onto its face's surface, is its edge: checked at the
|
||||
/// ends and the middle of the edge's interval, on faces of either orientation.
|
||||
#[test]
|
||||
fn every_pcurve_lifts_onto_its_own_edge() {
|
||||
use mt::{MetricSpace, ParametricCurve, ParametricSurface};
|
||||
let cube = crate::box_solid(Vec3::ZERO, Vec3::new(40., 30., 10.)).unwrap();
|
||||
let rounded = crate::fillet(&cube, &[Vec3::new(20., 0., 10.)], 2.).unwrap();
|
||||
let bore = crate::cylinder(Vec3::new(10., 10., -1.), Vec3::Z, 3., 12.).unwrap();
|
||||
let holed = crate::boolean(&rounded, &bore, crate::BoolOp::Cut).unwrap().unwrap();
|
||||
let sphere = crate::sphere(Vec3::ZERO, 12.).unwrap();
|
||||
for (name, body) in [("cube", cube), ("holed", holed), ("sphere", sphere)] {
|
||||
let record = encode(&body, super::super::pcurve::pcurves).unwrap();
|
||||
for (f, face) in record.faces.iter().zip(body.solid.face_iter()) {
|
||||
let surface = face.surface();
|
||||
for l in &f.loops {
|
||||
let b::Loop::Edges { edges } = l else { continue };
|
||||
for u in edges {
|
||||
let e = &record.edges[u.edge as usize];
|
||||
let curve = load_edge_curve(e.curve.clone(), e.interval).unwrap();
|
||||
for s in [0., 0.5, 1.] {
|
||||
let t = e.interval[0] + s * (e.interval[1] - e.interval[0]);
|
||||
let [x, y] = super::super::pcurve::eval(&u.pcurve, t).unwrap();
|
||||
let d = surface.subs(x, y).distance(curve.subs(t));
|
||||
assert!(d <= e.tolerance, "{name}: edge {} at {s}: {d} mm off (forward face {})", u.edge, face.orientation());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+222
-92
@@ -1,4 +1,4 @@
|
||||
# Native B-rep, schema 3
|
||||
# Native B-rep, schema 4
|
||||
|
||||
This is SolidCraft's geometry data model in `solidcraft_geom::brep`. It has no
|
||||
modelling-backend dependency. It is the body encoding of a design's model cache
|
||||
@@ -7,106 +7,211 @@ replacement for external interchange. The container reader, binary codec and
|
||||
kernel adapters are separate layers. The truck adapter writes it exactly: a body
|
||||
decoded by the producer that encoded it is that body, number for number.
|
||||
|
||||
## Geometry and topology
|
||||
The records follow SMLib's flat form, `SmBrepData`. It stores one record per
|
||||
logical use: a kernel keeps two mated halves of each face use, loop use and edge
|
||||
use at run time, and it rebuilds them from the one record. The types take STEP's
|
||||
B-rep names. Each type's documentation names its SMLib record and STEP entity.
|
||||
|
||||
Coordinates are IEEE-754 binary64, in millimetres, right-handed Z-up. Angles are
|
||||
radians. Integer references are unsigned 32-bit shell-local indices, never kernel
|
||||
addresses or runtime IDs. The schema version is an explicit unsigned 32-bit field.
|
||||
A body contains ordered shells; a shell contains ordered vertices, edges and faces.
|
||||
Order is retained for SolidCraft's face provenance and naming. Closed and open
|
||||
shells are representable; material containment is not inferred from shell order.
|
||||
## Units and numbers
|
||||
|
||||
An edge references its start/end vertices in its curve's forward direction. Each
|
||||
face contains ordered closed loops of oriented edge uses. A reversed use swaps
|
||||
edge endpoints. Loops record the physical face-oriented traversal. Face `forward` flips only the
|
||||
surface normal; it never swaps surface parameters or reverses these stored loops.
|
||||
Adapters must convert their internal boundary conventions separately. Shared edges are stored once per shell. Closed edges may reference the same endpoint vertex; individual adapters may refuse them.
|
||||
The validator checks references and connected closed loops. It does not certify
|
||||
watertightness, geometric coincidence, absence of self-intersections or trustworthy
|
||||
modelling state; backend extraction has additional requirements.
|
||||
Coordinates are IEEE-754 binary64, in millimetres, right-handed Z-up. Angles and
|
||||
angular parameters are radians, always. (SMLib's STEP-style analytic parameters
|
||||
are degrees; an SMLib adapter converts them, the pcurves on analytic faces
|
||||
included.) References are unsigned 32-bit indices into the body's own lists,
|
||||
never kernel addresses or runtime IDs. Tolerances are millimetres.
|
||||
|
||||
Curves are tagged mathematical records:
|
||||
## Topology
|
||||
|
||||
- `line`: start/end points, linear parameter from 0 to 1.
|
||||
- `polyline`: ordered points, segment i spans parameter i to i+1.
|
||||
- `spline`: full nondecreasing knot vector and Cartesian control points. Degree is
|
||||
knot count minus control-point count minus one.
|
||||
- `rational_spline`: the same basis with homogeneous `[x*w,y*w,z*w,w]` coefficients,
|
||||
retaining the original coefficients rather than normalizing/reapproximating them.
|
||||
- `circle_arc` and `ellipse_arc`: an orthonormal frame, radius/radii and an
|
||||
increasing angular range. Position is origin + x*rx*cos(t) + y*ry*sin(t).
|
||||
- `intersection`: an explicit ordinary 3D polynomial spline (knots and Cartesian
|
||||
control points), positive fitting tolerance in millimetres, and optional two
|
||||
shell-local face references as hints. No supporting surfaces, leader, backend
|
||||
solver or parameter-search convention is required to evaluate it. (Read only;
|
||||
schema 3 writers store the intersection itself, below.)
|
||||
- `surface_intersection`: the intersection of two surface records, traced from a
|
||||
`leader` (a polyline or spline near it, its knots as the producer holds them) to
|
||||
within `tolerance` mm. The curve is the procedural one, not a fit: a different
|
||||
kernel must trace it again.
|
||||
| Record | SMLib | STEP | Holds |
|
||||
|---|---|---|---|
|
||||
| `Brep` | `SmBrep` | `MANIFOLD_SOLID_BREP`, `BREP_WITH_VOIDS`, `SHELL_BASED_SURFACE_MODEL` | tolerance, regions, shells, faces, edges, vertices |
|
||||
| `Region` | `SmRegionData` | (implied by shells) | its shells, `void` |
|
||||
| `Shell` | `SmShellData` | `CLOSED_SHELL`, `OPEN_SHELL`, `WIRE_SHELL`, `VERTEX_SHELL` | oriented faces and wire edges; or wire edges; or one vertex |
|
||||
| `OrientedFace` | `SmFaceuseData` | `ORIENTED_FACE` | face, `forward` |
|
||||
| `Face` | `SmFaceData` | `ADVANCED_FACE` | surface, domain, loops |
|
||||
| `Loop` | `SmLoopData` | `FACE_BOUND` with `EDGE_LOOP` or `VERTEX_LOOP` | oriented edges, or one vertex |
|
||||
| `OrientedEdge` | `SmEUData` | `ORIENTED_EDGE` and its `PCURVE` | edge, `forward`, pcurve |
|
||||
| `Edge` | `SmEdgeData` | `EDGE_CURVE` | curve, interval, start and end vertex, tolerance, uses |
|
||||
| `EdgeUseRef` | `SmEUData`'s radial fields | — | face, loop, position, `upper` |
|
||||
| `Vertex` | `SmVertexData` | `VERTEX_POINT` | point, tolerance |
|
||||
|
||||
Surfaces include planes (origin and independent parameter axes), polynomial and
|
||||
rational tensor-product splines, and explicit analytic records. Analytic frames
|
||||
store origin and orthonormal x/y axes; z is x cross y. Angular ranges are radians:
|
||||
**Regions.** `regions[0]` is the infinite region. It has no outer shell and is
|
||||
never a void. Each bounded region lists its outer shell first, a closed face
|
||||
shell, then its inner shells. A bounded region is material unless `void`. A
|
||||
closed shell separates two regions, so both list it: it is the outer shell of the
|
||||
region it encloses and an inner shell of the region around it. An open shell, a
|
||||
wire shell or a vertex shell lies in one region and only that one lists it. A
|
||||
shell is closed when every edge its faces use is used at least twice among them.
|
||||
|
||||
- cylinder: radius, angular range and axial height range;
|
||||
- cone: radius at origin, half angle, angular range and height range; radius at
|
||||
height h is radius_at_origin + h*tan(half_angle);
|
||||
- sphere: radius, longitude and latitude ranges;
|
||||
- torus: major/minor radii, longitude and meridian ranges;
|
||||
- revolution: profile, origin, unit axis and angular range. Rotate the profile
|
||||
around that axis using the right-hand rule. There is no persisted affine matrix
|
||||
or backend orientation/parameter-swap flag. (Read only in schema 3.)
|
||||
- placed revolution: a full revolution of a profile about an origin and unit axis,
|
||||
then mapped by a column-major affine `matrix` (non-singular), with the normal
|
||||
reversed unless `forward`. Nothing is baked into the profile.
|
||||
**Shells.** A face shell lists oriented faces, and the wire edges embedded in it.
|
||||
A wire shell is a connected graph of edges with no faces. A vertex shell is one
|
||||
isolated point. One shell record stands for both runtime sides of a shell.
|
||||
|
||||
A `plane_through` record is a plane given by three points: parameter (0, 0) at
|
||||
`origin`, (1, 0) at `u_point` and (0, 1) at `v_point`. Writers use it rather than
|
||||
the axis form, whose axes would be rounded differences of the points.
|
||||
**Oriented faces.** A face is used by one oriented face, or by two in different
|
||||
shells with opposite `forward`. `forward` means the face's normal in that shell is
|
||||
its surface's natural normal, ∂S/∂u × ∂S/∂v. In a closed shell, normals point out
|
||||
of the material.
|
||||
|
||||
Cylinder/cone positions are origin + h*z + r(h)*(cos(u)*x + sin(u)*y).
|
||||
Sphere positions are origin + r*(cos(v)*(cos(u)*x + sin(u)*y) + sin(v)*z).
|
||||
Torus positions are origin + (R+r*cos(v))*(cos(u)*x + sin(u)*y) + r*sin(v)*z.
|
||||
Analytic normals point outward; face orientation can flip them. Each adapter
|
||||
must preserve the geometric locus and explicit face-oriented boundaries, even
|
||||
when its runtime uses a different parameterization.
|
||||
**Loops.** A face's first loop is its outer loop and is an edge loop. A vertex
|
||||
loop (a pole, a collapsed boundary) is only ever an inner loop. Loops are stored
|
||||
relative to the surface's natural normal, whichever side a shell uses: seen from
|
||||
that normal, the outer loop runs counter-clockwise and inner loops clockwise.
|
||||
This lets one face sit in two shells without contradicting itself. An edge loop
|
||||
is a closed chain: each oriented edge starts at the vertex where the one before
|
||||
it ends, and the last ends where the first starts. An oriented edge runs from
|
||||
its edge's start to its end when `forward`, otherwise from end to start.
|
||||
|
||||
**Edges.** An edge's `interval` is the part of its curve it covers, min < max.
|
||||
Its `vertices` are at interval min and max; they are equal for a closed edge.
|
||||
`uses` lists every oriented edge on it, each once: the primary use first (the
|
||||
first one the canonical walk meets), then the rest in radial order, the
|
||||
right-hand rule about the edge's direction. A wire edge has no uses. `upper` says
|
||||
the radial cycle enters that use on the side of the surface's natural normal.
|
||||
With loops stored relative to the natural normal it follows from the use:
|
||||
`upper` is always `!forward` of the oriented edge it names.
|
||||
|
||||
**Tolerances.** The body, every edge and every vertex carry a tolerance, finite
|
||||
and positive, never optional. The body's is SMLib's intersection tolerance.
|
||||
|
||||
## Canonical order
|
||||
|
||||
The lists are in one backend-independent order, and every adapter produces it. A
|
||||
face's index in `faces` is its face index everywhere: in history, face sources,
|
||||
paint and persistent names. The same holds for edges and vertices.
|
||||
|
||||
1. Regions: the infinite region, then bounded regions in the producer's order.
|
||||
2. Shells and faces in order of first appearance, walking regions → shells →
|
||||
oriented faces.
|
||||
3. Edges in order of first appearance, walking faces → loops (outer first) →
|
||||
oriented edges, then wire edges walking shells. A face whose first oriented
|
||||
face is reversed is walked backwards: each of its loops from its last
|
||||
oriented edge to its first. That is how a reversed face is traversed.
|
||||
4. Vertices in order of first appearance, walking edges (start, then end), then
|
||||
vertex loops walking faces → loops, then vertex shells.
|
||||
|
||||
Nothing may be listed that the walk does not reach.
|
||||
|
||||
## Geometry
|
||||
|
||||
Curves and surfaces are unbounded. An edge's bounds are its `interval`, a face's
|
||||
its loops inside its `domain`. Each kind's parameter is stated exactly. For the
|
||||
kinds the truck adapter writes (`line`, `spline`, `rational_spline`,
|
||||
`surface_intersection`, `plane_through`, `placed_revolution`) the parameter is
|
||||
truck's own evaluation (`ParametricCurve::subs`, `ParametricSurface::subs`).
|
||||
Analytic frames store an origin and orthonormal x and y axes; z = x × y.
|
||||
|
||||
### Curves
|
||||
|
||||
| Kind | Point at t | Range of t |
|
||||
|---|---|---|
|
||||
| `line` {start, end} | start + t (end − start): 0 at start, 1 at end | unbounded |
|
||||
| `circle` {frame, radius} | origin + r (cos t x + sin t y) | periodic |
|
||||
| `ellipse` {frame, radii} | origin + r₀ cos t x + r₁ sin t y | periodic |
|
||||
| `polyline` {points} | segment i spans [i, i + 1] | [0, n − 1] |
|
||||
| `spline` {knots, points} | B-spline on the full nondecreasing knot vector; degree = knots − points − 1 | [knots[degree], knots[count]] |
|
||||
| `rational_spline` {knots, homogeneous} | the same basis, homogeneous `[x·w, y·w, z·w, w]` coefficients as the producer holds them | as `spline` |
|
||||
| `surface_intersection` {surfaces, leader, tolerance} | the intersection of two surface records, traced from `leader` (a polyline or spline near it) to within `tolerance` mm; t is the leader's | the leader's |
|
||||
|
||||
A `surface_intersection` is the procedural curve itself, not a fit: another
|
||||
kernel traces it again.
|
||||
|
||||
### Surfaces
|
||||
|
||||
Natural normals are ∂S/∂u × ∂S/∂v.
|
||||
|
||||
| Kind | Point at (u, v) | u | v |
|
||||
|---|---|---|---|
|
||||
| `plane` {origin, u_axis, v_axis} | origin + u u_axis + v v_axis | unbounded | unbounded |
|
||||
| `plane_through` {origin, u_point, v_point} | origin + u (u_point − origin) + v (v_point − origin) | unbounded | unbounded |
|
||||
| `cylinder` {frame, radius} | origin + v z + r (cos u x + sin u y) | angle, periodic | height, unbounded |
|
||||
| `cone` {frame, radius_at_origin, half_angle} | origin + v z + r(v) (cos u x + sin u y), r(v) = radius_at_origin + v tan(half_angle) | angle, periodic | height, from the apex up |
|
||||
| `sphere` {frame, radius} | origin + r (cos v (cos u x + sin u y) + sin v z) | longitude, periodic | latitude, [−π/2, π/2] |
|
||||
| `torus` {frame, major_radius, minor_radius} | origin + (R + r cos v)(cos u x + sin u y) + r sin v z | longitude, periodic | meridian angle, periodic |
|
||||
| `spline` {u_knots, v_knots, points} | tensor-product B-spline, `points[i][j]` with i along u | u knot domain | v knot domain |
|
||||
| `rational_spline` {u_knots, v_knots, homogeneous} | the same, homogeneous coefficients | u knot domain | v knot domain |
|
||||
| `placed_revolution` {profile, origin, axis, matrix, forward} | M · R(angle) · profile(s): the profile revolved about origin and unit axis by the right-hand rule, then mapped by the column-major affine matrix M | `forward`: s, the profile's parameter; else the angle | `forward`: the angle, [0, 2π); else s |
|
||||
|
||||
A `plane_through` is a plane given by three points, as its producer holds them;
|
||||
writers use it rather than the axis form, whose axes would be rounded
|
||||
differences of the points. A `placed_revolution` keeps the revolution exactly as
|
||||
its producer holds it (truck's `RevolutedCurve` under a `Processor`): nothing is
|
||||
baked into the profile, and `forward: false` swaps u and v, which reverses the
|
||||
natural normal.
|
||||
|
||||
### Parameter ranges
|
||||
|
||||
An edge interval and each side of a face domain are finite with min < max. In a
|
||||
periodic direction they may start anywhere and run up to one turn (2π), across
|
||||
the seam. Otherwise they lie inside the natural range: a spline's knot domain, a
|
||||
polyline's [0, n − 1], a sphere's latitudes, a cone above its apex.
|
||||
|
||||
### Pcurves
|
||||
|
||||
Every oriented edge carries its pcurve: the edge in its face's (u, v), as a 2D
|
||||
B-spline (`Curve2`: `spline`, or `rational_spline` with `[u·w, v·w, w]`
|
||||
coefficients). There is no polyline or analytic 2D kind. 2D lines and arcs are
|
||||
exact as degree-1 and rational quadratic splines; anything else is a fitted
|
||||
B-spline. A pcurve lies on the edge's own interval and runs from interval min to
|
||||
max, whatever the oriented edge's direction. Its interval must equal the edge's
|
||||
within 10⁻⁹ of the interval's magnitude (at least 1), room for a linear remap's
|
||||
rounding. A seam edge has two uses on one face, each with its own pcurve, a
|
||||
period apart. On a periodic surface a loop's pcurves are unwrapped, so each one
|
||||
starts where the one before it ends.
|
||||
|
||||
## Validation and trust
|
||||
|
||||
Schema 3 caps 1,024 shells, 1,000,000 combined topology/geometry items, geometry
|
||||
nesting at 16, spline degree at 16 and control points per spline dimension at
|
||||
8,192. Numbers must be finite and bounded in magnitude by 1e12. Knot order, active
|
||||
spline domain, multiplicities, rectangular grids, positive rational weights,
|
||||
`Brep::validate` checks structure and resources without a kernel. It caps 1,024
|
||||
shells, 1,000,000 combined topology and geometry items, geometry nesting at 16,
|
||||
spline degree at 16 and control points per spline direction at 4,096. Numbers
|
||||
must be finite and at most 1e12 in magnitude. It checks knot order, active spline
|
||||
domains, multiplicities, rectangular grids, positive rational weights,
|
||||
nondegenerate planes, orthonormal frames, unit revolution axes, positive radii,
|
||||
valid angular/height ranges and intersection face references are checked before an adapter is called. These are resource and structural guards,
|
||||
not a geometric validity certificate. The binary reader must additionally bound
|
||||
ring tori, cone dimensions and tolerances. Then the topology: every index in
|
||||
range; the regions and shells as described above; each face used by one or two
|
||||
oriented faces; first loops are edge loops and vertex loops are inner; loops are
|
||||
closed chains through shared vertices; every edge's `uses` lists exactly its uses,
|
||||
once each, primary first, with `upper` as stated; pcurve intervals; parameter
|
||||
ranges; and the canonical order.
|
||||
|
||||
These are structural and resource guards, not a geometric validity certificate.
|
||||
The validator does not check that edges lie on their surfaces, that pcurves lift
|
||||
onto their edges, that a closed shell is watertight or that shells nest as the
|
||||
regions say. A backend's adapter checks what it needs. The binary reader bounds
|
||||
bytes, allocation and nesting before creating these structures.
|
||||
|
||||
Schema tests build and validate an open triangular face without constructing or
|
||||
linking a kernel body. Kernel-specific structures must appear only inside adapters;
|
||||
they must never acquire authority over persisted field names or encodings. A
|
||||
second backend still requires an implemented and tested adapter before claiming
|
||||
cross-kernel interoperability.
|
||||
Schema tests build and validate one minimal body of each form (an open sheet, a
|
||||
closed shell, a seam edge, a vertex loop with a wire and a vertex shell, a void)
|
||||
and one fixture per defect, without a kernel.
|
||||
|
||||
## History
|
||||
|
||||
The runtime history types (`FaceOrigin`, `Change`, `FaceProv`, `Side`,
|
||||
`EdgeOrigin`, `EdgeProv`, `History`) live in `solidcraft_geom::brep::history`, so
|
||||
a backend can report history without the kernel crate. Indices are canonical face
|
||||
and edge indices; operands are numbered by each operation. An operation returns
|
||||
history beside the B-rep. A backend that cannot report it returns none and never
|
||||
guesses. The saved form is the envelope's own records (below).
|
||||
|
||||
Face-source certificates, operation history, appearances and persistent document
|
||||
identities belong to a versioned SolidCraft body/state envelope around this geometry.
|
||||
They must be preserved by the cache codec, not reconstructed from face counts or
|
||||
floating-point comparisons. A decoded cache remains provisional until authoritative
|
||||
re-evaluation finishes. The document and original blobs remain sufficient to reopen
|
||||
when a cache, representation, producer or adapter is unsupported.
|
||||
identities belong to a versioned SolidCraft envelope around this geometry. They
|
||||
must be preserved by the cache codec, not reconstructed from face counts or
|
||||
floating-point comparisons. A decoded cache remains provisional until
|
||||
authoritative re-evaluation finishes. The document and original blobs remain
|
||||
sufficient to reopen when a cache, representation, producer or adapter is
|
||||
unsupported.
|
||||
|
||||
## Body binary envelope, version 4
|
||||
## Body binary envelope, version 5
|
||||
|
||||
`SCBREP04` is the eight-byte envelope signature. Geometry schema 3 is nested in
|
||||
`SCBREP05` is the eight-byte envelope signature. Geometry schema 4 is nested in
|
||||
`solid`; metadata schema 1 is nested in `metadata`; `runtime` holds what the body
|
||||
measured about itself that later operations read (its size, from which kernel
|
||||
tolerances follow, and the orientation mesh's missing-face count), so a decoded
|
||||
body works to the same tolerances. These are separate version numbers because
|
||||
each contract can evolve independently. Signatures 01 and 02 were development
|
||||
prototypes; 02 introduced the explicit geometry schema while 03 also separated
|
||||
metadata from runtime Rust types and 04 made the encoding exact. Unsupported
|
||||
versions are cache misses, not design-file migration failures.
|
||||
prototypes; 02 introduced the explicit geometry schema, 03 separated metadata
|
||||
from runtime Rust types, 04 made the encoding exact and 05 carries schema 4.
|
||||
Unsupported versions are cache misses, not design-file migration failures: a
|
||||
design whose cache holds an older signature opens and is evaluated again.
|
||||
|
||||
The envelope uses its own explicitly versioned records for color, paint, authored
|
||||
boundaries, face-source certificates, operation history and boundary protection.
|
||||
@@ -125,17 +230,42 @@ keys, unknown tags, non-finite numbers and trailing bytes. Limits per body are
|
||||
256 MiB, 20,000,000 values, depth 48, 1,000,000 entries per collection and 1 MiB per
|
||||
string; a design's cache bounds the total (see the container format).
|
||||
|
||||
The truck adapter writes every curve and surface as truck holds it: lines,
|
||||
B-spline and NURBS coefficients, the three points of a plane, a revolution's
|
||||
profile, axis, matrix and orientation, and an intersection's two surfaces, leader
|
||||
and tolerance. Decoding builds the same structures without the constructors that
|
||||
would round them (normalizing an axis, composing a matrix with the identity), and
|
||||
rebuilds each shell's topology as recorded, including closed edges on one vertex
|
||||
and seam edges a wire uses twice. On reading it refuses standalone polylines,
|
||||
unclamped spline domains and non-full angular carrier surfaces in the portable
|
||||
records it does not write. Circle/ellipse arcs use exact rational quadratic loci;
|
||||
the adapter may reparameterize them. Normal flips and backend boundary orientation
|
||||
are converted separately.
|
||||
## The truck adapter
|
||||
|
||||
Truck keeps a face's wires relative to its surface and flips the face with an
|
||||
orientation flag. That is schema 4's convention: the adapter writes truck's wires
|
||||
as the loops and the flag as `forward`, with no wire reversed. It numbers faces
|
||||
in `face_iter` order and edges in first appearance through `edge_iter`, which
|
||||
walks a reversed face backwards, so the record's indices are the kernel's.
|
||||
|
||||
It writes every curve and surface as truck holds it: lines, B-spline and NURBS
|
||||
coefficients, the three points of a plane, a revolution's profile, axis, matrix
|
||||
and orientation, and an intersection's two surfaces, leader and tolerance. An
|
||||
edge's interval is its truck curve's parameter range. Pcurves come from
|
||||
`native::pcurve`, exact on planes and fitted elsewhere; a face's domain is the
|
||||
box around its pcurves' control points, clamped to the surface's natural domain
|
||||
and at most one turn in a periodic direction. Every tolerance is the body's face
|
||||
tolerance, 10⁻⁶ of its size and at least 10⁻⁵ mm, how far later operations take
|
||||
its faces to be from its edges.
|
||||
|
||||
Regions come from truck's shells. Open shells lie in the infinite region. One
|
||||
closed shell bounds one material region. With several closed shells, a
|
||||
tessellated nesting test (the one `Body::lumps` uses) finds which bound material
|
||||
and which voids, and what lies inside what. If truck's shell order is not the
|
||||
canonical one (a void listed before another lump), or open and several closed
|
||||
shells mix, the adapter refuses and the body is a cache miss. So it does for an
|
||||
edge with more than two uses, whose radial order would need geometry.
|
||||
|
||||
Decoding builds the same structures without the constructors that would round
|
||||
them (normalizing an axis, composing a matrix with the identity), and rebuilds
|
||||
each shell's topology as recorded, including closed edges on one vertex and seam
|
||||
edges a wire uses twice. It ignores pcurves, regions and domains, which truck
|
||||
does not keep. It refuses what truck cannot hold: wire and vertex shells, wire
|
||||
edges in a face shell, vertex loops, a face used by two shells, standalone
|
||||
polylines, unclamped spline domains and a spline or intersection edge on part of
|
||||
its curve. It loads circles and ellipses as exact rational arcs over the edge's
|
||||
interval, and analytic surfaces as revolutions, which truck parameterizes its
|
||||
own way.
|
||||
|
||||
Hashes prove which document and payload a cache belongs to, not who wrote it: the
|
||||
container's key ties a cache to the producer, application build and platform that
|
||||
@@ -143,11 +273,11 @@ computed it, and the decoder applies the structural and resource checks above to
|
||||
every body. A second backend needs its own tested adapter before cross-kernel
|
||||
interoperability can be claimed.
|
||||
|
||||
The box-and-fillet golden JSON fixture pins the decoded version-4 envelope,
|
||||
The box-and-fillet golden JSON fixture pins the decoded version-5 envelope,
|
||||
field names, tags, topology, geometry and metadata. Integer and structural values
|
||||
are exact; floating coefficients allow 1e-10 relative/absolute platform rounding.
|
||||
The signature is asserted separately. This protects the wire contract against
|
||||
accidental runtime-refactor changes.
|
||||
The signature is asserted separately. `NATIVE_BREP_BLESS=1` rewrites the fixture
|
||||
for an intended change.
|
||||
|
||||
## Bounded provisional display
|
||||
|
||||
|
||||
Reference in New Issue
Block a user