feat(doc): persistent names follow the kernel's exact face history (#136)
Why: every reference a user makes to a face or edge (fillet and chamfer edges, sketches on faces, shell/draft/offset faces, joints, appearances) is stored as a name and found again after any upstream edit. Those names were re-derived by sampling points of each face and voting, and pieces of a split face were numbered by where their centres lay. After an upstream edit that splits, merges, mirrors or shrinks a face, a fillet or sketch could silently re-attach to the wrong face, and nothing told the user. The kernel now reports exact history for its operations, so names follow it and fail loudly instead of guessing. - crates/doc/src/lineage.rs: a feature's kernel histories composed into one lineage over nameable operands (bodies before the feature, moved copies, shell cavities, tools named by role: sketch curve, cap, box side, imported face). Instrumented: extrude/revolve/sweep/loft tools, join/cut/intersect, lumps after a cut, fillet/chamfer, shell, draft, split face/body, pattern/mirror (bodies and features), combine, boundary fill, move/scale, offset/delete face, thread, import. - crates/doc/src/history_names.rs: names from lineage; split pieces told apart by the faces only they touch (F2:top#[F2:side:l4,F4:side:l4]), merged faces answer to every name (aliases), lump bodies ordered by their faces' names instead of shell order. - crates/doc/src/name_lookup.rs, piece_names.rs: references resolve by exact name, alias or bounding faces, else nearest piece with a warning; names worked out from geometry (operations without history) are reported when only geometry decided. - Format 14: names stored by older formats get a legacy mark and are found as the build that saved them found them (some geometric names name a face history names differently: a tube's outer wall was named after its inner circle). New corpus file saved by 9a979cf. - kernel: Body::lumps carries each piece's history over the whole. - Adversarial tests: split, merge, mirrored twins, shrink to a sliver, moving cut with body pieces, history-less bodies, certified sketch. - docs/book/src/dev/naming.md describes the real mechanism and why. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01J53sR6gEaVt56soFDpTck8
This commit is contained in:
1 parent
2bda050872
commit
08cf526fe2
29 files changed
+2917
-322
No files matched your search
+91
-17
@@ -1,22 +1,96 @@
|
||||
# Persistent naming
|
||||
|
||||
A fillet stores the edges it rounds, and a sketch stores the face it sits on. If those were face
|
||||
and edge *indices*, an upstream edit that adds a face would move the fillet to the wrong edge.
|
||||
SolidCraft stores **names** instead (`crates/doc/src/naming.rs`):
|
||||
A fillet stores the edges it rounds, a sketch the face it sits on, a shell the faces it opens.
|
||||
If those were face and edge *indices*, an upstream edit that adds a face would move the fillet
|
||||
to another edge. SolveCraft stores **names**, and finds them again every time the timeline
|
||||
evaluates.
|
||||
|
||||
- A face of an evaluated body is named after how it was made. A face lying on a face of the
|
||||
feature's input keeps that face's name; a face new to the feature is named by its role:
|
||||
`F<feature>:side:<sketch curve>`, `F<feature>:start` and `:end`, `F<feature>:blend:<k>` and
|
||||
so on.
|
||||
- Pieces of a split face get `#1`, `#2`, … in order along the longest axis.
|
||||
- An edge is named by the two faces it separates.
|
||||
- Pattern and mirror copies are named after their sources plus the instance.
|
||||
## Why names follow the kernel's history
|
||||
|
||||
Names are computed lazily, the first time something asks, from the body, the feature that made
|
||||
it and the model just before that feature. Face provenance from the kernel (which input face an
|
||||
output face came from) does most of the work.
|
||||
Names used to be worked out from geometry: sample points of each new face were looked up on the
|
||||
faces of the feature's input, and the majority won; pieces of a split face were numbered by where
|
||||
their centres lay. That guess is right most of the time, and wrong exactly when it matters: after
|
||||
an upstream edit that splits, merges, mirrors or shrinks a face, points can land on a neighbour,
|
||||
centres can swap order, and a fillet or a sketch quietly re-attaches to a different face. Nothing
|
||||
tells the user, which is the worst thing a CAD tool can do.
|
||||
|
||||
When a feature evaluates, each stored reference is resolved by name. If no face has the name
|
||||
any more, the reference falls back to the geometry it recorded (a point on the face, a normal),
|
||||
and the feature shows a warning so the user can check it. A reference that matches nothing is
|
||||
an error, never a guess.
|
||||
The kernel now reports, for every operation, what each face and edge of its result came from
|
||||
(`crates/kernel/src/history.rs`: a face's origins and how it changed, an edge's origin edge or
|
||||
the faces whose meeting made it). Names follow that history, so they are exact and deterministic,
|
||||
and when a reference can no longer be found exactly the feature says so instead of guessing.
|
||||
|
||||
## How a body's faces are named
|
||||
|
||||
`crates/doc/src/lineage.rs` composes the histories of the kernel operations a feature runs (a
|
||||
tool built from a sketch, cut from each target, the cut body split into lumps) into one
|
||||
**lineage** over operands that can be named:
|
||||
|
||||
| Operand | Its faces are named |
|
||||
|---|---|
|
||||
| a body of the model before the feature | as they were |
|
||||
| a moved copy of one (pattern, mirror) | `<name>@F<feature>.<instance>` |
|
||||
| the cavity side of a shell grown outward | `F<feature>:inner:<name>` |
|
||||
| a body the feature built | by role: `F<feature>:side:<sketch curve>`, `start`/`top`/`bottom`/`end` caps, `+x`… box sides, `face:<k>` imported faces |
|
||||
| a copied tool of a patterned feature | `F<feature>:<role>@F<pattern>.<instance>` |
|
||||
|
||||
`crates/doc/src/history_names.rs` names each face of the result from its origins: a kept or
|
||||
trimmed face keeps its origin's name, a fillet or chamfer face is `F<feature>:blend:<edge>` of the
|
||||
edge it replaces, a shell's inner wall `F<feature>:inner:<face>`. A side's sketch curve is the
|
||||
curve the profile segment lies on exactly; nothing is sampled.
|
||||
|
||||
- **Pieces.** Faces that end up with one name are pieces of one face, and are told apart by the
|
||||
faces around them that the others lack: `F2:top#[F2:side:l4,F4:side:l4]` is the piece of the
|
||||
block's top bounded by its left wall and the slot's left wall. That survives the slot moving,
|
||||
growing or shrinking. Pieces nothing around them tells apart are numbered (`#1`, `#2`): by what
|
||||
they came from when that differs (the two halves of a circle's wall), else in kernel order and
|
||||
then unverified.
|
||||
- **Merged faces.** A face made of several (a join flush with the body) takes the name of the face
|
||||
they are all pieces of, else the target's; it answers to the others' names too (aliases).
|
||||
- **Edges** are named by the faces they separate, `<face A>|<face B>`, `~k` when several edges run
|
||||
between the same two faces (numbered by where they lie; a reference by such a number is
|
||||
reported when only geometry confirms it).
|
||||
- **Lumps.** When a cut leaves a body in pieces, the whole is named first, so pieces of one face in
|
||||
different bodies are told apart, and the bodies (`Bar`, `Bar (1)`) are assigned in the order of
|
||||
their least face name, not the kernel's shell order (which an upstream edit can swap).
|
||||
|
||||
Names are worked out lazily, on first request, and shared by a body's clones.
|
||||
|
||||
### Where there is no history
|
||||
|
||||
A body made by an operation that does not report history yet (some kernel paths return none; see
|
||||
the kernel's handoff lists, and the doc features that do not compose it yet: sheet metal, plastic
|
||||
and surface features, emboss, rib, replace face, the faceted thread cut), or a face whose role
|
||||
is not known exactly (a loft's sides, a coil's or pipe's generated profile, a hole's tool), is
|
||||
named the old way, from geometry, and its names are marked **unverified** (`model.faces` reports
|
||||
`exact: false`). A reference that resolves through an unverified name while its stored point no
|
||||
longer lies on the face says so: `face ... was found by geometry, not exact history; check it`.
|
||||
|
||||
## How a reference finds its face again
|
||||
|
||||
`crates/doc/src/name_lookup.rs`, in order:
|
||||
|
||||
1. the face with that name;
|
||||
2. the one face answering to it: merged into it (the feature says it was merged), or the named
|
||||
piece, which the faces listed in its name still bound;
|
||||
3. the pieces of it (it was split, or it is on several bodies): the one nearest the reference's
|
||||
point, and the feature says it was split;
|
||||
4. nothing: the feature falls back to the geometry it recorded and says the face no longer exists.
|
||||
|
||||
An edge name is found as the edge between the two faces it names, each found as above: when
|
||||
exactly one edge runs between them it is that edge, even after a split of one of its faces.
|
||||
|
||||
## Older designs
|
||||
|
||||
Designs saved before format 14 stored names worked out from geometry, and a few of them name a
|
||||
face that history names differently (a tube's outer wall was named after the sketch curve nearest
|
||||
its centre, its inner circle). Migrating such a design marks those names (`?F2:top#2`), and they
|
||||
are found among the names geometry gives, as the build that saved them found them: an older design
|
||||
evaluates as it was saved. Picking the face again stores its exact name.
|
||||
|
||||
## Not done yet
|
||||
|
||||
- Certificates (`FaceSource`, the strict attachment identities behind sketches on faces and
|
||||
configured face styles) still travel only where the kernel's planar-source mapping keeps them;
|
||||
carrying them through `History::face_sources` would let a certified attachment follow a face a
|
||||
later join reshapes, which today is refused on purpose.
|
||||
- Operations without history, listed above, keep the unverified geometric names.
|
||||
Reference in new issue
Block a user