Feature walks: generate them from field roles with #[derive(Walk)] (#1251)

The three walks over a feature (expressions, references, geometry) are generated from FeatureKind by #[derive(Walk)] in a new proc-macro crate, solidcraft-derive, from #[walk(...)] attributes written next to each field. The hand-written walks in params.rs, feature_refs.rs and feature_geometry.rs are gone; Ruled Surface and Extend have their attributes, and the three golden walk records are unchanged.

Co-authored-by: jijinbei <[email protected]>
This commit is contained in:
JIJINBEIandjijinbei authored and GitHub committed 2026-10-15 00:13:53 -04:00
1 parent a026a69182
commit 464a6dfad4
13 files changed
+998 -937

No files matched your search

+1
View File
@@ -205,6 +205,7 @@ impl FeatureKind {
/// Map the feature's world geometry (points, directions, explicit planes) by `m`.
pub fn transform_geometry(&mut self, m: &Mat) {
use crate::feature_geometry::Geo;
use crate::walk::Walk;
self.walk_geometry_mut(&mut |g| match g {
Geo::Pick(p) | Geo::Pos(p) => *p = apply_point(m, *p),
Geo::Dir(v) => *v = apply_vector(m, *v),
File diff suppressed because it is too large. Load diff
+4 -314
View File
@@ -1,14 +1,13 @@
//! The geometry a feature holds, each piece with its role: the points that pick faces (moved
//! with the feature, and the source of its persistent face names), other positions, directions,
//! planes, construction references, a primitive's placement point and its profile selections.
//! One walk, written once for `&` and `&mut`, behind `transform_geometry`, `face_points`,
//! `profile_sels` and `point_expr`. Every variant is listed, so a new one has to say what each of
//! its points is.
//! The walk (`crate::walk`) is behind `transform_geometry`, `face_points`, `profile_sels` and
//! `point_expr`.
use solidcraft_geom::Vec3;
use crate::construct::GeoRef;
use crate::{AxisRef, FeatureKind, PatternKind, PlaneRef, PointExpr, ProfileSel};
use crate::walk::Walk;
use crate::{FeatureKind, PointExpr, ProfileSel};
/// A piece of a feature's geometry: `V` is `&Vec3` or `&mut Vec3`, and so on for the others.
pub(crate) enum Geo<V, P, G, E, S> {
@@ -30,316 +29,7 @@ pub(crate) enum Geo<V, P, G, E, S> {
Profiles(u64, S),
}
pub(crate) type GeoItem<'a> = Geo<&'a Vec3, &'a PlaneRef, &'a GeoRef, &'a PointExpr, &'a ProfileSel>;
pub(crate) type GeoItemMut<'a> = Geo<&'a mut Vec3, &'a mut PlaneRef, &'a mut GeoRef, &'a mut PointExpr, &'a mut ProfileSel>;
/// Every piece of a feature's geometry, written once for `&` and `&mut`: `$item` is `GeoItem` or
/// `GeoItemMut`, `$refs` the matching `refs` / `refs_mut`, `$m` is `mut` or nothing. Face picks
/// come in the order the feature's face names follow.
macro_rules! geometry_walk {
($name:ident, $item:ident, $refs:ident $(, $m:tt)?) => {
pub(crate) fn $name<'a>(&'a $($m)? self, f: &mut impl FnMut($item<'a>)) {
use Geo::{Construct, Dir, Pick, Pinned, Placement, Plane, Pos, Profiles};
match self {
FeatureKind::Sketch { plane, .. } | FeatureKind::ConstructionPlane { plane } | FeatureKind::Mirror { plane, .. } => f(Plane(plane)),
FeatureKind::ConstructionAxis { def } => {
for r in def.$refs() {
f(Construct(r));
}
}
FeatureKind::ConstructionPoint { def } => {
for r in def.$refs() {
f(Construct(r));
}
}
FeatureKind::Extrude { sketch, profiles, extent, thin, .. } => {
// A thin extrude follows curves; its profiles are not used.
if thin.is_none() {
f(Profiles(*sketch, profiles));
}
if let Some(p) = & $($m)? extent.to {
f(Pick(p));
}
if let Some(p) = & $($m)? extent.from {
f(Pick(p));
}
}
// Surface Extrude refuses `to` and `from` for now; they would pick faces as for
// Extrude.
FeatureKind::SurfaceExtrude { sketch, profiles, extent, .. } => {
if let Some(s) = profiles {
f(Profiles(*sketch, s));
}
if let Some(p) = & $($m)? extent.to {
f(Pick(p));
}
if let Some(p) = & $($m)? extent.from {
f(Pick(p));
}
}
FeatureKind::Revolve { sketch, profiles, axis, to, .. } => {
f(Profiles(*sketch, profiles));
if let AxisRef::Line { origin, dir } = axis {
f(Pos(origin));
f(Dir(dir));
}
if let Some(p) = to {
f(Pick(p));
}
}
FeatureKind::SurfaceRevolve { sketch, profiles, axis, .. } => {
if let Some(s) = profiles {
f(Profiles(*sketch, s));
}
if let AxisRef::Line { origin, dir } = axis {
f(Pos(origin));
f(Dir(dir));
}
}
FeatureKind::Sweep { sketch, profiles, .. } | FeatureKind::SheetBase { sketch, profiles, .. } => f(Profiles(*sketch, profiles)),
FeatureKind::SurfaceSweep { sketch, profiles, .. } => {
if let Some(s) = profiles {
f(Profiles(*sketch, s));
}
}
FeatureKind::Emboss { sketch, profiles, face, .. } => {
f(Profiles(*sketch, profiles));
if let Some(face) = face {
f(Pinned(& $($m)? face.at));
}
}
FeatureKind::Loft { sections, .. } => {
// A section that is a point has no profile.
for s in sections {
if s.point.is_none() {
f(Profiles(s.sketch, & $($m)? s.profiles));
}
}
}
FeatureKind::SurfaceLoft { sections } => {
for s in sections {
if let Some(p) = & $($m)? s.profiles {
f(Profiles(s.sketch, p));
}
}
}
FeatureKind::Patch { sketch, profiles, edges, .. } => {
if let Some(sketch) = sketch {
f(Profiles(*sketch, profiles));
}
for e in edges {
f(Pos(e));
}
}
FeatureKind::Fillet { edges, style, .. } => {
for e in edges {
f(Pos(e));
}
if let crate::FilletStyle::Variable { start, .. } | crate::FilletStyle::Spline { start, .. } = style {
f(Pos(start));
}
}
FeatureKind::Chamfer { edges, .. } | FeatureKind::SheetHem { edges, .. } => {
for e in edges {
f(Pos(e));
}
}
FeatureKind::SurfaceExtend { edges, up_to, .. } => {
for e in edges {
f(Pos(e));
}
match up_to {
Some(crate::ExtendTo::Plane(plane)) => f(Plane(plane)),
Some(crate::ExtendTo::Face(t)) => f(Pos(& $($m)? t.point)),
Some(crate::ExtendTo::Bounds) | None => {}
}
}
FeatureKind::Shell { faces, .. }
| FeatureKind::OffsetFace { faces, .. }
| FeatureKind::DeleteFace { faces, .. }
| FeatureKind::SurfaceOffset { faces, .. } => {
for p in faces {
f(Pick(p));
}
}
FeatureKind::SurfaceRuled { edges, direction, face, .. } => {
for e in edges {
f(Pos(e));
}
if let Some(d) = direction {
f(Dir(d));
}
if let Some(p) = face {
f(Pick(p));
}
}
FeatureKind::Unstitch { faces, .. } | FeatureKind::ReverseNormal { faces, .. } => {
for p in faces {
f(Pos(p));
}
}
// The face is picked (and named); the holes are picked by points on their edges.
FeatureKind::SurfaceUntrim { face, holes, .. } => {
f(Pick(& $($m)? face.point));
if let Some(points) = holes {
for p in points {
f(Pos(p));
}
}
}
FeatureKind::BoundaryFill { cells, .. } => {
for p in cells {
f(Pos(p));
}
}
FeatureKind::Draft { faces, neutral, pull, .. } => {
for p in faces {
f(Pick(p));
}
f(Plane(neutral));
f(Dir(pull));
}
FeatureKind::ReplaceFace { faces, target, .. } => {
for p in faces {
f(Pick(p));
}
f(Plane(target));
}
FeatureKind::SplitFace { faces, plane, tool, .. } => {
for p in faces {
f(Pick(p));
}
f(Plane(plane));
if let Some(t) = tool {
f(Pos(& $($m)? t.point));
}
}
FeatureKind::Split { plane, tool, .. } => {
f(Plane(plane));
if let Some(t) = tool {
f(Pos(& $($m)? t.point));
}
}
FeatureKind::SurfaceTrim { plane, tool, keep, .. } => {
f(Plane(plane));
if let Some(t) = tool {
f(Pos(& $($m)? t.point));
}
f(Pos(keep));
}
FeatureKind::Box { corner: p, .. } | FeatureKind::Sphere { center: p, .. } | FeatureKind::Torus { center: p, .. } => {
f(Placement(p))
}
FeatureKind::Cylinder { base, axis, .. } | FeatureKind::Coil { base, axis, .. } => {
f(Placement(base));
f(Dir(axis));
}
FeatureKind::Pattern { pattern, .. } => match pattern {
PatternKind::Rectangular { dir1, dir2, .. } => {
f(Dir(dir1));
if let Some(d) = dir2 {
f(Dir(d));
}
}
PatternKind::Circular { origin, axis, .. } => {
f(Pos(origin));
f(Dir(axis));
}
// Follows its path sketch.
PatternKind::Path { .. } => {}
},
FeatureKind::Hole { position, direction, points, to, .. } => {
// Placed at sketch points, the hole names no face by its position.
if points.is_none() {
f(Pick(position));
} else {
f(Pos(position));
}
f(Dir(direction));
if let Some(p) = to {
f(Pick(p));
}
}
FeatureKind::Thread { face, .. } => f(Pick(face)),
FeatureKind::SheetFlange { edges, .. } => {
for e in edges {
f(Pos(& $($m)? e.point));
}
}
FeatureKind::SheetFold { a, b, fixed, .. } => {
f(Pos(a));
f(Pos(b));
if let Some(p) = fixed {
f(Pos(p));
}
}
FeatureKind::SheetConvert { face, bends, .. } => {
// With bend edges the fixed face is found by name (a plate keeps its point).
if bends.is_empty() {
f(Pos(face));
} else {
f(Pick(face));
}
for p in bends {
f(Pos(p));
}
}
FeatureKind::Boss { position, direction, .. } => {
f(Pick(position));
f(Dir(direction));
}
FeatureKind::SnapFit { position, direction, hook, .. } => {
f(Pick(position));
f(Dir(direction));
f(Dir(hook));
}
FeatureKind::Rest { position, direction, along, .. } => {
f(Pick(position));
f(Dir(direction));
if let Some(a) = along {
f(Dir(a));
}
}
FeatureKind::Lip { face, .. } => f(Pos(face)),
FeatureKind::Align { from, to, from_normal, to_normal, .. } => {
f(Pos(from));
f(Pos(to));
if let Some(n) = from_normal {
f(Dir(n));
}
if let Some(n) = to_normal {
f(Dir(n));
}
}
FeatureKind::Scale { origin, .. } => f(Pos(origin)),
FeatureKind::Move { rotate_axis, .. } => {
if let Some(a) = rotate_axis {
f(Dir(a));
}
}
FeatureKind::SheetContour { .. }
| FeatureKind::SheetPerforate { .. }
| FeatureKind::SheetUnfold { .. }
| FeatureKind::Combine { .. }
| FeatureKind::BoundingSolid { .. }
| FeatureKind::Pipe { .. }
| FeatureKind::Rib { .. }
| FeatureKind::Remove { .. }
| FeatureKind::Stitch { .. }
| FeatureKind::Thicken { .. }
| FeatureKind::Import { .. }
| FeatureKind::MeshImport { .. }
| FeatureKind::MeshConvert { .. }
| FeatureKind::BodyTransfer { .. } => {}
}
}
};
}
impl FeatureKind {
geometry_walk!(walk_geometry, GeoItem, refs);
geometry_walk!(walk_geometry_mut, GeoItemMut, refs_mut, mut);
/// The points that pick faces (shell, draft, offset and replace faces, a hole's placement and
/// `to`, an extrude's `to` and `from`…), in the order of the feature's persistent face names.
pub fn face_points_mut(&mut self) -> Vec<&mut Vec3> {
+4 -225
View File
@@ -1,12 +1,12 @@
//! References from a feature to the rest of the design: the sketches it is built from (by id),
//! the features it names (patterned and mirrored features, the construction plane a plane is
//! built on) and the bodies it works on (by name). One walk, written once for `&` and `&mut`,
//! behind `input_sketches`, `remap_refs` and `remap_bodies`. Every variant is listed, so a new
//! one has to say what it refers to.
//! built on) and the bodies it works on (by name). The walk (`crate::walk`) is behind
//! `input_sketches`, `remap_refs` and `remap_bodies`.
use std::collections::BTreeMap;
use crate::{FeatureKind, PatternKind, PlaneRef};
use crate::FeatureKind;
use crate::walk::Walk;
/// A reference a feature holds: `S` is `&u64` or `&mut u64` (a sketch id), `N` is `&String` or
/// `&mut String` (a feature or body name).
@@ -16,228 +16,7 @@ pub(crate) enum Ref<S, N> {
Body(N),
}
/// The construction plane a plane is built on (through offsets and angles), for `&` and `&mut`
/// (`$m` is `mut` or nothing). Nesting is capped like every walk over a plane.
macro_rules! plane_walk {
($name:ident $(, $m:tt)?) => {
fn $name<'a>(mut p: &'a $($m)? PlaneRef, f: &mut impl FnMut(Ref<&'a $($m)? u64, &'a $($m)? String>)) {
for _ in 0..64 {
match p {
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => p = base,
PlaneRef::Construction { name } => return f(Ref::Feature(name)),
_ => return,
}
}
}
};
}
plane_walk!(plane_refs);
plane_walk!(plane_refs_mut, mut);
/// Every reference of a feature, written once for `&` and `&mut`: `$plane` is the matching plane
/// walk, `$m` is `mut` or nothing.
macro_rules! feature_walk {
($name:ident, $plane:ident $(, $m:tt)?) => {
pub(crate) fn $name<'a>(&'a $($m)? self, f: &mut impl FnMut(Ref<&'a $($m)? u64, &'a $($m)? String>)) {
use Ref::{Body as B, Feature as F, Sketch as S};
match self {
FeatureKind::Sketch { plane, .. } | FeatureKind::ConstructionPlane { plane } => $plane(plane, f),
FeatureKind::Extrude { sketch, targets, .. } | FeatureKind::Revolve { sketch, targets, .. } => {
f(S(sketch));
for b in targets {
f(B(b));
}
}
FeatureKind::Emboss { sketch, targets, face, .. } => {
f(S(sketch));
if let Some(face) = face {
f(B(& $($m)? face.body));
}
for b in targets {
f(B(b));
}
}
FeatureKind::SurfaceExtrude { sketch, .. }
| FeatureKind::SurfaceRevolve { sketch, .. }
| FeatureKind::Rib { sketch, .. }
| FeatureKind::SheetBase { sketch, .. }
| FeatureKind::SheetContour { sketch, .. } => f(S(sketch)),
FeatureKind::SurfaceSweep { sketch, path_sketch, .. } => {
f(S(sketch));
f(S(path_sketch));
}
FeatureKind::SurfaceLoft { sections } => {
for s in sections {
f(S(& $($m)? s.sketch));
}
}
FeatureKind::Sweep { sketch, path_sketch, targets, .. } => {
f(S(sketch));
f(S(path_sketch));
for b in targets {
f(B(b));
}
}
FeatureKind::Pipe { path_sketch, targets, .. } => {
f(S(path_sketch));
for b in targets {
f(B(b));
}
}
FeatureKind::Loft { sections, targets, .. } => {
for s in sections {
f(S(& $($m)? s.sketch));
}
for b in targets {
f(B(b));
}
}
FeatureKind::Coil { targets, .. } => {
for b in targets {
f(B(b));
}
}
FeatureKind::Hole { points, .. } => {
if let Some(p) = points {
f(S(& $($m)? p.sketch));
}
}
FeatureKind::Pattern { features, pattern, bodies } => {
for n in features {
f(F(n));
}
if let PatternKind::Path { path_sketch, .. } = pattern {
f(S(path_sketch));
}
for b in bodies {
f(B(b));
}
}
FeatureKind::Mirror { features, plane, bodies, .. } => {
for n in features {
f(F(n));
}
$plane(plane, f);
for b in bodies {
f(B(b));
}
}
FeatureKind::Split { body, plane, tool } | FeatureKind::SurfaceTrim { body, plane, tool, .. } => {
f(B(body));
$plane(plane, f);
if let Some(t) = tool {
f(B(& $($m)? t.body));
}
}
FeatureKind::SplitFace { body, plane, tool, sketch, .. } => {
$plane(plane, f);
if let Some(s) = sketch {
f(S(s));
}
if let Some(b) = body {
f(B(b));
}
if let Some(t) = tool {
f(B(& $($m)? t.body));
}
}
FeatureKind::Draft { neutral: plane, body, .. } | FeatureKind::ReplaceFace { target: plane, body, .. } => {
$plane(plane, f);
if let Some(b) = body {
f(B(b));
}
}
FeatureKind::SheetFold { sketch, body, .. } | FeatureKind::Patch { sketch, body, .. } => {
if let Some(s) = sketch {
f(S(s));
}
if let Some(b) = body {
f(B(b));
}
}
FeatureKind::Fillet { body, .. }
| FeatureKind::Chamfer { body, .. }
| FeatureKind::Shell { body, .. }
| FeatureKind::OffsetFace { body, .. }
| FeatureKind::DeleteFace { body, .. }
| FeatureKind::SheetFlange { body, .. }
| FeatureKind::SheetHem { body, .. }
| FeatureKind::SheetPerforate { body, .. }
| FeatureKind::SheetUnfold { body, .. }
| FeatureKind::Boss { body, .. }
| FeatureKind::Lip { body, .. }
| FeatureKind::SnapFit { body, .. }
| FeatureKind::Rest { body, .. }
| FeatureKind::SurfaceOffset { body, .. } => {
if let Some(b) = body {
f(B(b));
}
}
FeatureKind::SurfaceUntrim { face, .. } => f(B(& $($m)? face.body)),
FeatureKind::SurfaceExtend { body, up_to, .. } => {
f(B(body));
match up_to {
Some(crate::ExtendTo::Plane(plane)) => $plane(plane, f),
Some(crate::ExtendTo::Face(t)) => f(B(& $($m)? t.body)),
Some(crate::ExtendTo::Bounds) | None => {}
}
}
FeatureKind::SheetConvert { body, .. }
| FeatureKind::SurfaceRuled { body, .. }
| FeatureKind::Unstitch { body, .. }
| FeatureKind::ReverseNormal { body, .. }
| FeatureKind::MeshConvert { body, .. } => f(B(body)),
FeatureKind::Combine { target, tools, .. } => {
f(B(target));
for b in tools {
f(B(b));
}
}
FeatureKind::Thicken { bodies, targets, .. } => {
for b in bodies {
f(B(b));
}
for b in targets {
f(B(b));
}
}
FeatureKind::BodyTransfer { bodies, .. }
| FeatureKind::Scale { bodies, .. }
| FeatureKind::BoundingSolid { bodies, .. }
| FeatureKind::Align { bodies, .. }
| FeatureKind::Remove { bodies }
| FeatureKind::Stitch { bodies, .. }
| FeatureKind::Move { bodies, .. } => {
for b in bodies {
f(B(b));
}
}
FeatureKind::BoundaryFill { tools, .. } => {
for b in tools {
f(B(b));
}
}
// Construction axes and points refer to geometry, which copies remap on their own
// (`Feature::remap_clone`).
FeatureKind::ConstructionAxis { .. }
| FeatureKind::ConstructionPoint { .. }
| FeatureKind::Box { .. }
| FeatureKind::Cylinder { .. }
| FeatureKind::Sphere { .. }
| FeatureKind::Torus { .. }
| FeatureKind::Thread { .. }
| FeatureKind::Import { .. }
| FeatureKind::MeshImport { .. } => {}
}
}
};
}
impl FeatureKind {
feature_walk!(walk_refs, plane_refs);
feature_walk!(walk_refs_mut, plane_refs_mut, mut);
/// Sketches the feature is built from (profiles, paths, points): deleting one of them
/// deletes the feature.
pub fn input_sketches(&self) -> Vec<u64> {
+1
View File
@@ -39,6 +39,7 @@ pub mod physical_material;
mod piece_names;
mod profile_refs;
pub mod progress;
mod walk;
pub use profile_refs::{capture_profile_selection, profile_selection_indices};
pub mod plastic;
pub mod project;
+11 -382
View File
@@ -9,7 +9,8 @@ use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use crate::expr::{self, Kind, ParamDef, Value};
use crate::{DocError, Document, FeatureKind, HoleKind, MAX_PARAMS, PatternKind, PlaneRef, Result};
use crate::walk::Walk;
use crate::{DocError, Document, FeatureKind, MAX_PARAMS, Result};
/// Whether an expression of a feature is one of its inputs, which become model parameters named
/// in walk order (`dN`), or a coordinate of a primitive's placement point, which gets no name and
@@ -20,387 +21,11 @@ pub(crate) enum ExprRole {
Placement,
}
/// The offsets and angles of a plane, outermost first, for `&` and `&mut` (`$m` is `mut` or
/// nothing). Nesting is capped like every walk over a plane.
macro_rules! plane_walk {
($name:ident $(, $m:tt)?) => {
fn $name<'a>(mut p: &'a $($m)? PlaneRef, f: &mut impl FnMut(&'static str, &'a $($m)? String, Kind, ExprRole)) {
for _ in 0..64 {
match p {
PlaneRef::Offset { base, distance } => {
f("Offset", distance, Kind::Length, ExprRole::Input);
p = base;
}
PlaneRef::AtAngle { base, angle, .. } => {
f("Angle", angle, Kind::Angle, ExprRole::Input);
p = base;
}
PlaneRef::AlongPath { t, .. } => {
f("Distance (0 to 1)", t, Kind::Unitless, ExprRole::Input);
break;
}
_ => break,
}
}
}
};
}
plane_walk!(plane_exprs);
plane_walk!(plane_exprs_mut, mut);
/// Every expression of a feature with its label, kind and role, in a fixed order (the inputs'
/// order names their parameters), written once for `&` and `&mut`: `$plane` is the matching plane
/// walk, `$refs` the matching `refs`/`refs_mut`, `$m` is `mut` or nothing.
macro_rules! feature_walk {
($name:ident, $plane:ident, $refs:ident $(, $m:tt)?) => {
pub(crate) fn $name<'a>(&'a $($m)? self, f: &mut impl FnMut(&'static str, &'a $($m)? String, Kind, ExprRole)) {
use ExprRole::{Input as I, Placement as P};
use Kind::{Angle as A, Length as L, Unitless as U};
const XYZ: [&str; 3] = ["X", "Y", "Z"];
match self {
FeatureKind::Sketch { plane, .. }
| FeatureKind::ConstructionPlane { plane }
| FeatureKind::Split { plane, .. }
| FeatureKind::SplitFace { plane, .. }
| FeatureKind::Mirror { plane, .. } => $plane(plane, f),
FeatureKind::ConstructionAxis { def } => {
for r in def.$refs() {
if let crate::construct::GeoRef::Plane { plane } = r {
$plane(plane, f);
}
}
}
FeatureKind::ConstructionPoint { def } => match def {
crate::construct::PointDef::AlongPath { t, .. } => f("Distance (0 to 1)", t, U, I),
other => {
for r in other.$refs() {
if let crate::construct::GeoRef::Plane { plane } = r {
$plane(plane, f);
}
}
}
},
FeatureKind::Extrude { extent, thin, .. } => {
f("Distance", & $($m)? extent.distance, L, I);
if let Some(d) = & $($m)? extent.distance2 {
f("Distance 2", d, L, I);
}
if let Some(d) = & $($m)? extent.start_offset {
f("Start offset", d, L, I);
}
if let Some(d) = & $($m)? extent.taper {
f("Taper angle", d, A, I);
}
if let Some(d) = & $($m)? extent.to_offset {
f("To offset", d, L, I);
}
if let Some(thin) = thin {
f("Thickness", & $($m)? thin.thickness, L, I);
if let Some(t) = & $($m)? thin.thickness2 {
f("Thickness 2", t, L, I);
}
}
}
FeatureKind::SurfaceExtrude { extent, .. } => {
f("Distance", & $($m)? extent.distance, L, I);
if let Some(d) = & $($m)? extent.distance2 {
f("Distance 2", d, L, I);
}
if let Some(d) = & $($m)? extent.start_offset {
f("Start offset", d, L, I);
}
if let Some(d) = & $($m)? extent.taper {
f("Taper angle", d, A, I);
}
if let Some(d) = & $($m)? extent.to_offset {
f("To offset", d, L, I);
}
}
FeatureKind::Revolve { angle, angle2, .. } | FeatureKind::SurfaceRevolve { angle, angle2, .. } => {
f("Angle", angle, A, I);
if let Some(a) = angle2 {
f("Angle 2", a, A, I);
}
}
FeatureKind::Fillet { radius, style, .. } => match style {
crate::FilletStyle::Constant => f("Radius", radius, L, I),
crate::FilletStyle::Curvature => f("Equivalent radius", radius, L, I),
crate::FilletStyle::Chord => f("Chord length", radius, L, I),
crate::FilletStyle::Variable { radius2, mid, .. } => {
f("Start radius", radius, L, I);
f("End radius", radius2, L, I);
for (_, r) in mid {
f("Mid radius", r, L, I);
}
}
crate::FilletStyle::Spline { radii, .. } => {
f("Start radius", radius, L, I);
let n = radii.len();
for (i, r) in radii.into_iter().enumerate() {
f(if i + 1 == n { "End radius" } else { "Control radius" }, r, L, I);
}
}
},
FeatureKind::Chamfer { distance, distance2, angle, .. } => {
f("Distance", distance, L, I);
if let Some(d) = distance2 {
f("Distance 2", d, L, I);
}
if let Some(a) = angle {
f("Angle", a, A, I);
}
}
FeatureKind::Box { length, width, height, corner, .. } => {
f("Length", length, L, I);
f("Width", width, L, I);
f("Height", height, L, I);
for (l, c) in XYZ.into_iter().zip(corner) {
f(l, c, L, P);
}
}
FeatureKind::Cylinder { radius, height, base, .. } => {
f("Radius", radius, L, I);
f("Height", height, L, I);
for (l, c) in XYZ.into_iter().zip(base) {
f(l, c, L, P);
}
}
FeatureKind::Sphere { radius, center, .. } => {
f("Radius", radius, L, I);
for (l, c) in XYZ.into_iter().zip(center) {
f(l, c, L, P);
}
}
FeatureKind::Torus { major, minor, center, .. } => {
f("Major radius", major, L, I);
f("Minor radius", minor, L, I);
for (l, c) in XYZ.into_iter().zip(center) {
f(l, c, L, P);
}
}
FeatureKind::Pattern { pattern, .. } => match pattern {
PatternKind::Rectangular { count1, spacing1, count2, spacing2, .. } => {
f("Quantity", count1, U, I);
f("Spacing", spacing1, L, I);
if let Some(c) = count2 {
f("Quantity 2", c, U, I);
}
if let Some(sp) = spacing2 {
f("Spacing 2", sp, L, I);
}
}
PatternKind::Path { count, spacing, .. } => {
f("Quantity", count, U, I);
f("Distance", spacing, L, I);
}
PatternKind::Circular { count, angle, .. } => {
f("Quantity", count, U, I);
f("Total angle", angle, A, I);
}
},
FeatureKind::Shell { thickness, .. } => f("Thickness", thickness, L, I),
FeatureKind::Draft { angle, neutral, .. } => {
f("Angle", angle, A, I);
$plane(neutral, f);
}
FeatureKind::Hole { diameter, depth, hole, .. } => {
f("Diameter", diameter, L, I);
if let Some(d) = depth {
f("Depth", d, L, I);
}
match hole {
HoleKind::Simple => {}
HoleKind::Drilled { tip_angle } => f("Tip angle", tip_angle, A, I),
HoleKind::Counterbore { cb_diameter, cb_depth } => {
f("Counterbore diameter", cb_diameter, L, I);
f("Counterbore depth", cb_depth, L, I);
}
HoleKind::Countersink { cs_diameter, cs_angle } => {
f("Countersink diameter", cs_diameter, L, I);
f("Countersink angle", cs_angle, A, I);
}
}
}
FeatureKind::Thread { length, .. } => {
if let Some(l) = length {
f("Length", l, L, I);
}
}
FeatureKind::Scale { factor, factors, .. } => {
f("Scale factor", factor, U, I);
if let Some([x, y, z]) = factors {
f("X scale", x, U, I);
f("Y scale", y, U, I);
f("Z scale", z, U, I);
}
}
FeatureKind::OffsetFace { distance, .. } => f("Distance", distance, L, I),
FeatureKind::BoundingSolid { margin, .. } => f("Margin", margin, L, I),
FeatureKind::Pipe { diameter, wall, .. } => {
f("Diameter", diameter, L, I);
if let Some(w) = wall {
f("Wall", w, L, I);
}
}
FeatureKind::Move { translate, angle, .. } => {
let [x, y, z] = translate;
f("X distance", x, L, I);
f("Y distance", y, L, I);
f("Z distance", z, L, I);
if let Some(a) = angle {
f("Angle", a, A, I);
}
}
FeatureKind::Emboss { depth, .. } => f("Depth", depth, L, I),
FeatureKind::Boss { diameter, height, hole_diameter, hole_depth, draft, fillet, ribs, rib_thickness, rib_length, rib_offset, .. } => {
f("Diameter", diameter, L, I);
f("Height", height, L, I);
for (l, o, k) in [
("Hole diameter", hole_diameter, L),
("Hole depth", hole_depth, L),
("Draft angle", draft, A),
("Root fillet", fillet, L),
("Ribs", ribs, U),
("Rib thickness", rib_thickness, L),
("Rib length", rib_length, L),
("Rib offset", rib_offset, L),
] {
if let Some(e) = o {
f(l, e, k, I);
}
}
}
FeatureKind::Lip { width, height, gap, .. } => {
f("Width", width, L, I);
f("Height", height, L, I);
if let Some(g) = gap {
f("Gap", g, L, I);
}
}
FeatureKind::Rest { width, length, height, draft, thickness, .. } => {
f("Width", width, L, I);
f("Height", height, L, I);
for (l, o, k) in [("Length", length, L), ("Draft angle", draft, A), ("Wall thickness", thickness, L)] {
if let Some(e) = o {
f(l, e, k, I);
}
}
}
FeatureKind::SnapFit { length, thickness, width, catch_depth, catch_length, .. } => {
f("Length", length, L, I);
f("Thickness", thickness, L, I);
f("Width", width, L, I);
f("Catch depth", catch_depth, L, I);
f("Catch length", catch_length, L, I);
}
FeatureKind::SheetContour { distance, .. } => f("Distance", distance, L, I),
FeatureKind::SheetFlange { edges, height, angle, radius, .. } => {
f("Height", height, L, I);
f("Angle", angle, A, I);
if let Some(r) = radius {
f("Bend radius", r, L, I);
}
// The edges' own values come last, so the inputs above keep their names.
for e in edges {
if let Some(a) = & $($m)? e.angle {
f("Edge angle", a, A, I);
}
if let Some(h) = & $($m)? e.height {
f("Edge height", h, L, I);
}
}
}
FeatureKind::SheetFold { angle, radius, .. } => {
f("Angle", angle, A, I);
if let Some(r) = radius {
f("Bend radius", r, L, I);
}
}
FeatureKind::SheetHem { length, gap, .. } => {
f("Length", length, L, I);
if let Some(g) = gap {
f("Gap", g, L, I);
}
}
FeatureKind::SheetPerforate { diameter, pitch, margin, .. } => {
f("Diameter", diameter, L, I);
f("Pitch", pitch, L, I);
if let Some(m) = margin {
f("Margin", m, L, I);
}
}
FeatureKind::Coil { diameter, pitch, turns, section_size, start_angle, base, .. } => {
f("Diameter", diameter, L, I);
f("Pitch", pitch, L, I);
f("Revolutions", turns, U, I);
f("Section size", section_size, L, I);
if let Some(a) = start_angle {
f("Start angle", a, A, I);
}
for (l, c) in XYZ.into_iter().zip(base) {
f(l, c, L, P);
}
}
FeatureKind::Rib { thickness, depth, .. } => {
f("Thickness", thickness, L, I);
if let Some(d) = depth {
f("Depth", d, L, I);
}
}
FeatureKind::ReplaceFace { target, .. } => $plane(target, f),
FeatureKind::Stitch { tolerance, .. } => f("Tolerance", tolerance, L, I),
FeatureKind::MeshConvert { tolerance, .. } => f("Tolerance", tolerance, L, I),
FeatureKind::Thicken { thickness, .. } => f("Thickness", thickness, L, I),
FeatureKind::SurfaceTrim { plane, .. } => $plane(plane, f),
FeatureKind::SurfaceExtend { distance, up_to, .. } => {
f("Distance", distance, L, I);
if let Some(crate::ExtendTo::Plane(plane)) = up_to {
$plane(plane, f);
}
}
FeatureKind::SurfaceRuled { distance, angle, .. } => {
f("Distance", distance, L, I);
if let Some(a) = angle {
f("Angle", a, A, I);
}
}
FeatureKind::Unstitch { .. } | FeatureKind::ReverseNormal { .. } | FeatureKind::SurfaceUntrim { .. } => {}
FeatureKind::SurfaceOffset { distance, tolerance, .. } => {
f("Distance", distance, L, I);
if let Some(t) = tolerance {
f("Tolerance", t, L, I);
}
}
FeatureKind::Patch { .. } => {}
FeatureKind::Combine { .. }
| FeatureKind::SheetBase { .. }
| FeatureKind::SheetUnfold { .. }
| FeatureKind::SheetConvert { .. }
| FeatureKind::Align { .. }
| FeatureKind::Remove { .. }
| FeatureKind::DeleteFace { .. }
| FeatureKind::BoundaryFill { .. }
| FeatureKind::Loft { .. }
| FeatureKind::Sweep { .. }
| FeatureKind::SurfaceSweep { .. }
| FeatureKind::SurfaceLoft { .. }
| FeatureKind::Import { .. }
| FeatureKind::MeshImport { .. }
| FeatureKind::BodyTransfer { .. } => {}
}
}
};
}
impl FeatureKind {
feature_walk!(walk_exprs, plane_exprs, refs);
feature_walk!(walk_exprs_mut, plane_exprs_mut, refs_mut, mut);
/// The feature's numeric inputs: (label, expression, kind), in a fixed order.
pub fn inputs_mut(&mut self) -> Vec<(&'static str, &mut String, Kind)> {
let mut v = Vec::new();
self.walk_exprs_mut(&mut |label, e, kind, role| {
self.walk_exprs_mut(&mut |(label, e, kind, role)| {
if role == ExprRole::Input {
v.push((label, e, kind));
}
@@ -411,7 +36,7 @@ impl FeatureKind {
/// Read-only view of [`FeatureKind::inputs_mut`]: (label, expression, kind).
pub fn inputs(&self) -> Vec<(&'static str, String, Kind)> {
let mut v = Vec::new();
self.walk_exprs(&mut |label, e, kind, role| {
self.walk_exprs(&mut |(label, e, kind, role)| {
if role == ExprRole::Input {
v.push((label, e.clone(), kind));
}
@@ -421,9 +46,13 @@ impl FeatureKind {
/// Every expression the feature uses: its inputs, then a primitive's placement coordinates.
pub fn expressions(&self) -> Vec<&str> {
let mut v = Vec::new();
self.walk_exprs(&mut |_, e, _, _| v.push(e.as_str()));
v
let (mut inputs, mut placement) = (Vec::new(), Vec::new());
self.walk_exprs(&mut |(_, e, _, role)| match role {
ExprRole::Input => inputs.push(e.as_str()),
ExprRole::Placement => placement.push(e.as_str()),
});
inputs.extend(placement);
inputs
}
}
+358
View File
@@ -0,0 +1,358 @@
//! The walks over a feature: its expressions (`params`), its references to the rest of the design
//! (`feature_refs`) and its geometry (`feature_geometry`). `#[derive(Walk)]`
//! (`solidcraft-derive`) generates them from `#[walk(...)]` roles on the fields of `FeatureKind`
//! and the types inside it, so what a field is, is written once, where it is declared. The
//! traits here carry the walks through the types the fields have: options, lists, arrays, planes
//! and construction definitions.
pub(crate) use solidcraft_geom::Vec3;
pub(crate) use crate::ProfileSel;
use crate::construct::{AxisDef, GeoRef, PointDef};
pub(crate) use crate::expr::Kind;
pub(crate) use crate::feature_geometry::Geo;
pub(crate) use crate::feature_refs::Ref;
pub(crate) use crate::params::ExprRole;
use crate::{PlaneRef, PointExpr};
/// An expression: its label, the expression, its kind and its role.
pub(crate) type ExprItem<'a> = (&'static str, &'a String, Kind, ExprRole);
pub(crate) type ExprItemMut<'a> = (&'static str, &'a mut String, Kind, ExprRole);
pub(crate) type RefItem<'a> = Ref<&'a u64, &'a String>;
pub(crate) type RefItemMut<'a> = Ref<&'a mut u64, &'a mut String>;
pub(crate) type GeoItem<'a> = Geo<&'a Vec3, &'a PlaneRef, &'a GeoRef, &'a PointExpr, &'a ProfileSel>;
pub(crate) type GeoItemMut<'a> = Geo<&'a mut Vec3, &'a mut PlaneRef, &'a mut GeoRef, &'a mut PointExpr, &'a mut ProfileSel>;
/// A value's expressions, references and geometry, each walked in order. A type with none of
/// them implements it empty.
pub(crate) trait Walk {
fn walk_exprs<'a>(&'a self, _: &mut dyn FnMut(ExprItem<'a>)) {}
fn walk_exprs_mut<'a>(&'a mut self, _: &mut dyn FnMut(ExprItemMut<'a>)) {}
fn walk_refs<'a>(&'a self, _: &mut dyn FnMut(RefItem<'a>)) {}
fn walk_refs_mut<'a>(&'a mut self, _: &mut dyn FnMut(RefItemMut<'a>)) {}
fn walk_geometry<'a>(&'a self, _: &mut dyn FnMut(GeoItem<'a>)) {}
fn walk_geometry_mut<'a>(&'a mut self, _: &mut dyn FnMut(GeoItemMut<'a>)) {}
}
impl Walk for String {}
impl Walk for std::sync::Arc<str> {}
impl Walk for bool {}
impl Walk for u64 {}
impl Walk for i8 {}
impl Walk for f64 {}
impl Walk for ProfileSel {}
impl Walk for solidcraft_sketch::Sketch {}
impl Walk for solidcraft_kernel::StepNode {}
impl Walk for crate::document::Operation {}
impl Walk for crate::document::Direction {}
impl Walk for crate::document::FilletFit {}
impl Walk for crate::document::MeshData {}
impl Walk for crate::document::RevolveConvention {}
impl Walk for crate::document::CoilSection {}
impl Walk for crate::document::ThinSide {}
impl Walk for crate::document::ExtendMode {}
impl Walk for crate::document::RuledMode {}
impl Walk for crate::document::RuledCorners {}
/// Containers walk what they hold, in order.
macro_rules! walk_each {
($([$($g:tt)*] $t:ty),*) => {$(
impl<$($g)*> Walk for $t {
fn walk_exprs<'a>(&'a self, f: &mut dyn FnMut(ExprItem<'a>)) {
for t in self {
t.walk_exprs(f)
}
}
fn walk_exprs_mut<'a>(&'a mut self, f: &mut dyn FnMut(ExprItemMut<'a>)) {
for t in self {
t.walk_exprs_mut(f)
}
}
fn walk_refs<'a>(&'a self, f: &mut dyn FnMut(RefItem<'a>)) {
for t in self {
t.walk_refs(f)
}
}
fn walk_refs_mut<'a>(&'a mut self, f: &mut dyn FnMut(RefItemMut<'a>)) {
for t in self {
t.walk_refs_mut(f)
}
}
fn walk_geometry<'a>(&'a self, f: &mut dyn FnMut(GeoItem<'a>)) {
for t in self {
t.walk_geometry(f)
}
}
fn walk_geometry_mut<'a>(&'a mut self, f: &mut dyn FnMut(GeoItemMut<'a>)) {
for t in self {
t.walk_geometry_mut(f)
}
}
}
)*};
}
walk_each!([T: Walk] Option<T>, [T: Walk] Vec<T>, [T: Walk, const N: usize] [T; N]);
/// The values of a field with a role (`T`, an optional one or a list), in order.
pub(crate) trait Each<T> {
fn each<'a>(&'a self, f: &mut dyn FnMut(&'a T));
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(&'a mut T));
}
impl<T> Each<T> for T {
fn each<'a>(&'a self, f: &mut dyn FnMut(&'a T)) {
f(self)
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(&'a mut T)) {
f(self)
}
}
impl<T> Each<T> for Option<T> {
fn each<'a>(&'a self, f: &mut dyn FnMut(&'a T)) {
if let Some(t) = self {
f(t)
}
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(&'a mut T)) {
if let Some(t) = self {
f(t)
}
}
}
impl<T> Each<T> for Vec<T> {
fn each<'a>(&'a self, f: &mut dyn FnMut(&'a T)) {
for t in self {
f(t)
}
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(&'a mut T)) {
for t in self {
f(t)
}
}
}
impl<T> Each<T> for Option<Vec<T>> {
fn each<'a>(&'a self, f: &mut dyn FnMut(&'a T)) {
for t in self.iter().flatten() {
f(t)
}
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(&'a mut T)) {
for t in self.iter_mut().flatten() {
f(t)
}
}
}
/// The expressions of an expression field, each with its index and their count (for its label).
pub(crate) trait ExprField {
fn each<'a>(&'a self, f: &mut dyn FnMut(usize, usize, &'a String));
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(usize, usize, &'a mut String));
}
impl ExprField for String {
fn each<'a>(&'a self, f: &mut dyn FnMut(usize, usize, &'a String)) {
f(0, 1, self)
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(usize, usize, &'a mut String)) {
f(0, 1, self)
}
}
/// A position along an edge with its radius (a variable fillet's mid radii).
impl ExprField for (f64, String) {
fn each<'a>(&'a self, f: &mut dyn FnMut(usize, usize, &'a String)) {
f(0, 1, &self.1)
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(usize, usize, &'a mut String)) {
f(0, 1, &mut self.1)
}
}
impl<T: ExprField> ExprField for Option<T> {
fn each<'a>(&'a self, f: &mut dyn FnMut(usize, usize, &'a String)) {
if let Some(t) = self {
t.each(f)
}
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(usize, usize, &'a mut String)) {
if let Some(t) = self {
t.each_mut(f)
}
}
}
/// Lists and arrays number their elements.
macro_rules! expr_list {
($([$($g:tt)*] $t:ty),*) => {$(
impl<$($g)*> ExprField for $t {
fn each<'a>(&'a self, f: &mut dyn FnMut(usize, usize, &'a String)) {
let n = self.len();
for (i, t) in self.iter().enumerate() {
t.each(&mut |_, _, e| f(i, n, e));
}
}
fn each_mut<'a>(&'a mut self, f: &mut dyn FnMut(usize, usize, &'a mut String)) {
let n = self.len();
for (i, t) in self.iter_mut().enumerate() {
t.each_mut(&mut |_, _, e| f(i, n, e));
}
}
}
)*};
}
expr_list!([T: ExprField] Vec<T>, [T: ExprField, const N: usize] [T; N]);
/// An expression's label: one for every element, one per element, or worked out from the
/// element's index and the count.
pub(crate) trait Label {
fn at(&self, i: usize, n: usize) -> &'static str;
}
impl Label for &'static str {
fn at(&self, _: usize, _: usize) -> &'static str {
self
}
}
impl<const N: usize> Label for [&'static str; N] {
fn at(&self, i: usize, _: usize) -> &'static str {
self.get(i).copied().unwrap_or("")
}
}
impl<F: Fn(usize, usize) -> &'static str> Label for F {
fn at(&self, i: usize, n: usize) -> &'static str {
self(i, n)
}
}
/// The offsets and angles of a plane, outermost first (nesting is capped like every walk over a
/// plane), the construction plane it is built on, and the plane itself.
impl Walk for PlaneRef {
fn walk_exprs<'a>(&'a self, f: &mut dyn FnMut(ExprItem<'a>)) {
let mut p = self;
for _ in 0..64 {
match p {
PlaneRef::Offset { base, distance } => {
f(("Offset", distance, Kind::Length, ExprRole::Input));
p = base;
}
PlaneRef::AtAngle { base, angle, .. } => {
f(("Angle", angle, Kind::Angle, ExprRole::Input));
p = base;
}
PlaneRef::AlongPath { t, .. } => return f(("Distance (0 to 1)", t, Kind::Unitless, ExprRole::Input)),
_ => return,
}
}
}
fn walk_exprs_mut<'a>(&'a mut self, f: &mut dyn FnMut(ExprItemMut<'a>)) {
let mut p = self;
for _ in 0..64 {
match p {
PlaneRef::Offset { base, distance } => {
f(("Offset", distance, Kind::Length, ExprRole::Input));
p = base;
}
PlaneRef::AtAngle { base, angle, .. } => {
f(("Angle", angle, Kind::Angle, ExprRole::Input));
p = base;
}
PlaneRef::AlongPath { t, .. } => return f(("Distance (0 to 1)", t, Kind::Unitless, ExprRole::Input)),
_ => return,
}
}
}
fn walk_refs<'a>(&'a self, f: &mut dyn FnMut(RefItem<'a>)) {
let mut p = self;
for _ in 0..64 {
match p {
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => p = base,
PlaneRef::Construction { name } => return f(Ref::Feature(name)),
_ => return,
}
}
}
fn walk_refs_mut<'a>(&'a mut self, f: &mut dyn FnMut(RefItemMut<'a>)) {
let mut p = self;
for _ in 0..64 {
match p {
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => p = base,
PlaneRef::Construction { name } => return f(Ref::Feature(name)),
_ => return,
}
}
}
fn walk_geometry<'a>(&'a self, f: &mut dyn FnMut(GeoItem<'a>)) {
f(Geo::Plane(self))
}
fn walk_geometry_mut<'a>(&'a mut self, f: &mut dyn FnMut(GeoItemMut<'a>)) {
f(Geo::Plane(self))
}
}
/// Construction axes and points: the expressions of the planes they are built from (and a point
/// along a path's distance), and their references to the model. They name no sketches, features
/// or bodies: copies remap their geometry references on their own (`Feature::remap_clone`).
fn plane_exprs<'a>(refs: Vec<&'a GeoRef>, f: &mut dyn FnMut(ExprItem<'a>)) {
for r in refs {
if let GeoRef::Plane { plane } = r {
plane.walk_exprs(f);
}
}
}
fn plane_exprs_mut<'a>(refs: Vec<&'a mut GeoRef>, f: &mut dyn FnMut(ExprItemMut<'a>)) {
for r in refs {
if let GeoRef::Plane { plane } = r {
plane.walk_exprs_mut(f);
}
}
}
macro_rules! construct_geometry {
() => {
fn walk_geometry<'a>(&'a self, f: &mut dyn FnMut(GeoItem<'a>)) {
for r in self.refs() {
f(Geo::Construct(r));
}
}
fn walk_geometry_mut<'a>(&'a mut self, f: &mut dyn FnMut(GeoItemMut<'a>)) {
for r in self.refs_mut() {
f(Geo::Construct(r));
}
}
};
}
impl Walk for AxisDef {
fn walk_exprs<'a>(&'a self, f: &mut dyn FnMut(ExprItem<'a>)) {
plane_exprs(self.refs(), f)
}
fn walk_exprs_mut<'a>(&'a mut self, f: &mut dyn FnMut(ExprItemMut<'a>)) {
plane_exprs_mut(self.refs_mut(), f)
}
construct_geometry!();
}
impl Walk for PointDef {
fn walk_exprs<'a>(&'a self, f: &mut dyn FnMut(ExprItem<'a>)) {
match self {
PointDef::AlongPath { t, .. } => f(("Distance (0 to 1)", t, Kind::Unitless, ExprRole::Input)),
other => plane_exprs(other.refs(), f),
}
}
fn walk_exprs_mut<'a>(&'a mut self, f: &mut dyn FnMut(ExprItemMut<'a>)) {
match self {
PointDef::AlongPath { t, .. } => f(("Distance (0 to 1)", t, Kind::Unitless, ExprRole::Input)),
other => plane_exprs_mut(other.refs_mut(), f),
}
}
construct_geometry!();
}