254 lines
14 KiB
Markdown
254 lines
14 KiB
Markdown
# Neutral section geometry (W01a)
|
|
|
|
W01a, [#2053][issue] / [PR #2085][pr], adds the L0
|
|
[`solidcraft_geom::section`][section-source] representation and structural validation. See the
|
|
[foundation ledger](drawing-foundations.md) for dependencies and the remaining
|
|
W01 work. This contract is deliberately smaller than the eventual section and
|
|
projection result described in the [Drawing plan][plan].
|
|
|
|
The module can hold geometry supplied by a caller, validate its structure and
|
|
convert selected closed wires/regions to existing `Loop2`/`Region2` values. It does
|
|
not intersect model bodies, determine visibility, certify material regions,
|
|
attach persistent references or register an engine command. No native design
|
|
format, exporter, formatter or new runtime dependency is introduced here.
|
|
|
|
## Coordinates and frames
|
|
|
|
Distances and coordinates are millimetres; angular values in `Seg2` are radians.
|
|
The model is Z-up. Each result contains the existing [`Plane`][plane-source]:
|
|
|
|
```text
|
|
world(u, v) = origin + u * x + v * y
|
|
normal = x cross y
|
|
```
|
|
|
|
`validate_plane(&Plane)` checks a supplied frame without normalizing it. Origin
|
|
and both axes must be finite, with every component's absolute value at most
|
|
`1e12`. Each axis must be unit length within `1e-8`, and their absolute dot product
|
|
must be at most `1e-8`. The normal is derived from the axes; no separate normal or
|
|
handedness flag is serialized.
|
|
|
|
The existing named frames are:
|
|
|
|
| Plane | Local x | Local y | Derived normal |
|
|
| --- | --- | --- | --- |
|
|
| XY | +X | +Y | +Z |
|
|
| XZ | +X | -Z | +Y |
|
|
| YZ | +Y | +Z | +X |
|
|
|
|
In particular, XZ's local positive y points toward **negative world Z**. Replacing
|
|
it with +Z would reverse the normal. `Plane::flipped` reverses an in-plane axis
|
|
and therefore the derived normal; validation does not choose an orientation.
|
|
|
|
The future Stage A request adapter must preserve the existing
|
|
[`inspect.section`][section-command] conventions: `offset` follows the named
|
|
plane's normal; `at` is the world Z/Y/X coordinate for XY/XZ/YZ respectively;
|
|
`offset` and `at` are mutually exclusive; `flip` changes the cut direction without
|
|
moving the requested plane. These request fields are not part of W01a's DTO.
|
|
|
|
Mapping a point already on the plane to local coordinates and back is meaningful.
|
|
Mapping an arbitrary 3D point to two coordinates loses its normal component; it
|
|
does not have a unique 3D inverse.
|
|
|
|
## Data and serialization
|
|
|
|
| Type | Fields and meaning |
|
|
| --- | --- |
|
|
| `SectionGeometry` | `plane: Plane`, `curves: Vec<SectionCurve>`, `wires: Vec<SectionWire>`, `regions: Vec<SectionRegion>`, `contacts: Vec<Vec2>` |
|
|
| `SectionCurve` | `geometry: Seg2`, `accuracy: CurveAccuracy` |
|
|
| `SectionWire` | Ordered `curves: Vec<CurveUse>` and explicit `closed: bool` |
|
|
| `CurveUse` | `curve: u32` indexes `SectionGeometry.curves`; `forward: bool` selects its direction of use |
|
|
| `SectionRegion` | `outer: u32` indexes a wire; `holes: Vec<u32>` indexes its supplied hole wires |
|
|
|
|
All indices are **local to this one value**. They do not identify a model body,
|
|
face, occurrence, revision or an entity across recomputation. W01a has no source
|
|
fingerprint, provenance, freshness, persistent-ID or public schema-version field.
|
|
Those require a reviewed producer/integration contract in a later W01 slice.
|
|
|
|
The new section DTOs reject unknown fields through serde. Existing `Plane`,
|
|
`Vec2`, `Vec3` and [`Seg2`][segment-source] retain their existing serialization:
|
|
vectors are numeric arrays; plane axes are `x_dir` and `y_dir`; segment variants
|
|
use a `type` tag with snake-case names. Do not apply a blanket strict-schema claim
|
|
to the reused nested types.
|
|
|
|
`Seg2` represents lines, circular arcs, cubic Béziers, rational quadratic conics
|
|
and supported higher-degree polynomial Bézier pieces. This is not every curve in
|
|
the neutral B-rep schema, nor evidence of an exact producer for each kind. An
|
|
adapter must preserve supported geometry or report its actual conversion and
|
|
accuracy; it must not silently coerce unsupported B-rep curves into this shape.
|
|
|
|
`CurveAccuracy` uses the internally tagged serde field `kind`:
|
|
|
|
| JSON form | Meaning supplied by the producer |
|
|
| --- | --- |
|
|
| `{"kind":"analytic"}` | The producer declares an analytic representation without fitting |
|
|
| `{"kind":"bounded","max_deviation_mm":0.001}` | The producer claims a deviation bound in model millimetres |
|
|
| `{"kind":"sampled","max_sample_deviation_mm":0.001}` | The recorded deviation applies to evaluated samples; it is not a bound between samples |
|
|
|
|
Validation checks the metadata's numeric domain. It does not independently prove
|
|
an analytic classification or a claimed bound against a source surface. Accuracy
|
|
is per curve, not a global `exact: true` flag. There is no implicit default tag.
|
|
In particular, a requested fitting tolerance is not an established bound, and a
|
|
zero sampled deviation remains sampled.
|
|
|
|
This complete value shows the actual serde spelling for one open line. It is
|
|
data, not a request to an implemented Drawing command:
|
|
|
|
```json
|
|
{
|
|
"plane": {
|
|
"origin": [0, 0, 0],
|
|
"x_dir": [1, 0, 0],
|
|
"y_dir": [0, 1, 0]
|
|
},
|
|
"curves": [
|
|
{
|
|
"geometry": {"type": "line", "a": [0, 0], "b": [10, 0]},
|
|
"accuracy": {"kind": "analytic"}
|
|
}
|
|
],
|
|
"wires": [
|
|
{"curves": [{"curve": 0, "forward": true}], "closed": false}
|
|
],
|
|
"regions": [],
|
|
"contacts": []
|
|
}
|
|
```
|
|
|
|
The value can pass structural validation. Converting wire 0 to a `Loop2` must
|
|
fail because it is explicitly open. Merely meeting endpoints does not change a
|
|
wire's `closed` flag. Contact points are representable, but W01a does not classify
|
|
them as tangent intersections or certify their source.
|
|
|
|
## Validation and conversion boundary
|
|
|
|
The public operations are `validate_plane(&Plane)` and
|
|
`SectionGeometry::validate_structure(tol)`, `to_loop(wire, tol)` and
|
|
`to_region(region, tol)`. Errors use the module's `Invalid` error type. There is
|
|
no geometric `bounds()` API or batch region-expansion API in this slice.
|
|
|
|
`tol` must be finite and in `1e-9..=1` mm. It is a structural endpoint-connectivity
|
|
tolerance, separate from each curve's accuracy metadata and from future model
|
|
intersection tolerances, paper deviation and dimension display precision.
|
|
|
|
The public `MAX_ITEMS` is `100_000`. Validation counts the aggregate of curves,
|
|
wires, regions, contacts, all curve-use references and all hole references before
|
|
walking the geometry. Counting references prevents a small curve set from hiding
|
|
an unbounded number of uses. This limit is not a maximum JSON byte size.
|
|
|
|
Structural validation checks the frame, numeric domains, finite curve/contact
|
|
data, bounded collection counts and local reference integrity. It checks all
|
|
serialized Bézier control slots, including unused tail slots. Every wire must be
|
|
nonempty; oriented consecutive endpoints must meet within `tol`. A wire declared
|
|
closed must also meet last-to-first. Region boundaries must reference closed
|
|
wires; a region cannot list its outer wire as a hole or repeat the same hole.
|
|
Different regions may share a wire.
|
|
|
|
The arithmetic domains are explicit, not a geometric precision certificate:
|
|
|
|
| Stored value | Accepted domain |
|
|
| --- | --- |
|
|
| Coordinates and control slots | Finite; absolute component at most `1e12` mm |
|
|
| Arc radius | `1e-12..=1e12` mm |
|
|
| Arc start | `-1e6..=1e6` radians |
|
|
| Absolute arc sweep | `1e-12..=2*pi + 1e-12` radians |
|
|
| Conic weight | `1e-12..=1e12`; only positive weights are supported here |
|
|
| Higher-degree Bézier `n` | `4..=9`, using the existing ten-slot control array |
|
|
| Bounded/sample deviation | Finite, `0..=1e12` mm |
|
|
|
|
Line and Bézier/conic controls must not all coincide; evaluated segment endpoints
|
|
must be finite. These tests do not certify a minimum physical feature size or a
|
|
small absolute arithmetic error at extreme magnitudes. In particular, the stored
|
|
coordinate limit is not a computed geometric bounding box.
|
|
|
|
`to_loop` and `to_region` revalidate the entire value, check the selected index and
|
|
preflight the selected output's segment expansion before conversion. A
|
|
`forward: false` use copies the reversed segment. They preserve the supplied wire
|
|
orientation, outer/hole classification and geometry; they do not fit, repair,
|
|
close or correct a boundary's winding to make it acceptable. Repeated conversions
|
|
repeat validation. Callers planning many conversions must account for that work;
|
|
this API does not provide a shared validation certificate or a batch result.
|
|
|
|
Conversion also checks the actual converted loop's closure with the same `tol`.
|
|
Reversing an arc computes a new start angle; at large radii, floating-point
|
|
rounding can make its evaluated endpoints disagree even when the original wire
|
|
passed validation. The converter then returns
|
|
`converted loop is not closed within tolerance`; it does not snap endpoints.
|
|
`Loop2`/`Region2` contain geometry only, not `CurveAccuracy` or the result-local
|
|
indices. A caller that needs those records must retain the original metadata;
|
|
conversion neither carries an accuracy certificate nor promotes accuracy.
|
|
|
|
**Structural success does not certify winding, nesting, self-intersection,
|
|
separation of regions or a physically valid material section.** Converters do not
|
|
establish those properties either. The future section producer must supply and
|
|
verify outer-CCW/hole-CW boundaries in `(x,y)` coordinates, nesting, signed/net area
|
|
semantics and legitimate material regions. A result may be structurally valid
|
|
while violating those future geometric requirements.
|
|
|
|
Existing profile area and containment methods also have their own curve-specific
|
|
limitations; converting to `Loop2` or `Region2` does not make conic/freeform area or
|
|
containment exact or certified.
|
|
|
|
Deserialization does not call structural validation automatically. Validate before
|
|
use or serialization; the public data may have changed since an earlier check.
|
|
Serde can allocate vectors before these validators run. A future file, command
|
|
or transport adapter must impose byte, allocation, nesting and request limits
|
|
before or during decoding, then call structural validation before downstream
|
|
geometry operations. Producer traversal/iteration budgets, cancellation and
|
|
complete source selection are also future caller responsibilities. None is
|
|
provided by the `MAX_ITEMS` check after deserialization.
|
|
|
|
## Fixtures and handoff
|
|
|
|
These labels connect to the [requirement ledger](drawing-foundations.md).
|
|
Current checks exercise manually supplied representation values, not extracted
|
|
model geometry; see [`section_tests.rs`][tests-source]. Test results belong to the
|
|
tested PR revision, not to this list.
|
|
|
|
| Fixture | Current check or remaining acceptance gate |
|
|
| --- | --- |
|
|
| F01: frames | W01a: finite/orthonormal plane validation, XZ sign and plane-coordinate mapping. Degenerate, nonfinite and out-of-range frames fail |
|
|
| F02: representation/accuracy | W01a: supported segments and actual serde shape; accuracy numeric-domain checks; sampled error remains distinct from a bound |
|
|
| F03: rectangle with a hole | W01a: manual `100 x 60 mm` rectangle and radius-5 circular hole at `(50,30)`, in `Plane::XY.offset(10)`. Conversion preserves signed areas `6000` and `-25*pi`, and net area `6000 - 25*pi mm²`. It is not a body-section or nesting certificate |
|
|
| F04: malformed structure/conversion | W01a: invalid indices, disconnected/empty wires, closure, invalid region references, excessive aggregate items, nonfinite data, malformed curves and post-conversion endpoint agreement; no mutation |
|
|
| F05: plate through a hole | W03: a `100 x 60 x 20 mm` box with a radius-5 through hole parallel to Z, cut by an XY plane strictly between its end faces, has section area `6000 - 25*pi mm²`; producer must identify and certify outer/hole boundaries |
|
|
| F06: tilted cylinder | W03: radius 10, axis Z, plane normal 30° from Z, away from caps: ellipse semiaxes `10` and `10/cos(30°)`, area `100*pi/cos(30°)`. Analytic representation or justified conversion evidence is required |
|
|
| F07: section outcomes | W03: empty/tangent/coincident/open/degenerate/unsupported cases, with explicit outcome semantics rather than a guessed empty success |
|
|
| F08: source completeness | W02-W04: repeated/nested/mirrored occurrences retain source identity; one unsupported selected body cannot silently disappear from a complete result |
|
|
| F09: command/output | W04: explicit plane/selection, same CLI/MCP/engine result, no model/undo mutation, complete physical SVG/DXF output and bounded cancellation/error behavior |
|
|
|
|
F03 is `line_arc_region_conversion_preserves_winding_and_analytic_area`: one
|
|
rectangle edge is stored backwards and used in reverse; two semicircular arcs
|
|
are used in reverse order/direction to form the hole. It checks orientation and
|
|
unchanged input as well as these analytic line/arc areas. In contrast,
|
|
`connected_bow_tie_and_wrong_winding_are_not_certified_or_silently_repaired`
|
|
deliberately accepts and preserves structurally valid but geometrically unsuitable
|
|
boundaries. Neither test extracts a cut from a solid.
|
|
|
|
`reversed_large_arc_conversion_rechecks_actual_connectivity_without_snapping`
|
|
checks the converted result too: a reversed radius-`1e12` arc with start `0.1` and
|
|
sweep `1.0` radians fails at `tol=1e-9` mm but is accepted at `0.01` mm. This is an
|
|
arithmetic/closure regression, not evidence of that accuracy against a source.
|
|
|
|
[NOWN-DEV's PR #2068][producer-pr], reviewed as an open proposal rather than
|
|
merged code, contains Stage A producer/export work and a tilted-cylinder test.
|
|
Review that evidence and its actual mesh/fitting guarantees before reusing it.
|
|
Its body-name/loop/`sweep_deg` output is a distinct schema; W01a supplies no adapter
|
|
and promises no automatic compatibility. Agree conversion, accuracy and source
|
|
identity handling with that contributor before further overlapping claims.
|
|
|
|
W01a can unblock review of representation and structural invariants. W02-W04 still
|
|
need the producer contract for source/provenance, outcomes, completeness and
|
|
accuracy, plus an agreed kernel/solidcore boundary. Persistent Drawing files,
|
|
fonts, profiles and PDF are not prerequisites for standalone section work.
|
|
|
|
[issue]: https://github.com/tangency-labs/solidcraft/issues/2053
|
|
[pr]: https://github.com/tangency-labs/solidcraft/pull/2085
|
|
[plan]: https://github.com/tangency-labs/solidcraft/issues/860#issuecomment-6234779815
|
|
[producer-pr]: https://github.com/tangency-labs/solidcraft/pull/2068
|
|
[section-source]: ../crates/geom/src/section.rs
|
|
[tests-source]: ../crates/geom/src/section_tests.rs
|
|
[plane-source]: ../crates/geom/src/plane.rs
|
|
[segment-source]: ../crates/geom/src/profile.rs
|
|
[section-command]: ../crates/engine/src/cmd/section.rs
|