Persistent naming
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. SolidCraft stores names, and finds them again every time the timeline evaluates.
Why names follow the kernel’s history
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.
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>,~kwhen 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; see Older designs).
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:
- the face with that name;
- 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;
- 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;
- 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.
A sketch’s link to a projected edge (sketch.project {edge: [x, y, z]}, and Intersect and
Include 3D Geometry of an edge) keeps the edge’s name beside its point. The link is that edge
wherever a change takes it; of an edge that was split it is the piece nearest the link’s point,
and the sketch says so. When no edge has the name any more the link is lost and keeps its last
geometry, as it does for a face: it does not take whichever edge is nearest now. A link saved
without a name, by a build that kept only the point, is found by the edge nearest its point, as
it always was, and takes that edge’s name when its sketch is next edited.
A command that takes a face as a point can be given a name model.faces lists instead
(face.delete, solid.shell, solid.draft, solid.offset_face, solid.split_face, the faces
of solid.replace_face, the face of sheet.convert). It is found with the same lookup
(naming::locate_one: the face with that name, or the one face answering to it, on the given
body or any body; a name that finds several faces is refused). The command takes a point on that
face (naming::named_face_point: the incentre of the face’s roomiest triangle, moved onto the
surface of a plane, cylinder, cone, sphere or torus), which the feature stores like a picked one,
with the name it then gets as before. Nothing new is stored in the design. model.faces lists
that point for every face.
A hole placed by a position on a face is the exception to “the point nearest the reference’s point”: its position is a place, not a pick. The hole follows its face along its own axis, so it moves with the face when the face moves, and when the face no longer reaches the position it stays where it was placed and the feature says so, instead of sliding to the face’s edge and drilling half a hole there. (Holes at sketch points follow their sketch.)
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.
Their features also keep naming the pieces they split a body into in the kernel’s shell order
(Feature::shell_piece_order, set by the migration): the body names stored after them (fillet and
chamfer bodies, targets, participants, materials, appearances, joints) mean the pieces in that
order. Features added later name their pieces after their faces.
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 throughHistory::face_sourceswould 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.