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 <jijinbei@users.noreply.github.com>
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

Generated
+10
View File
@@ -3479,6 +3479,15 @@ dependencies = [
"solidcraft-mcp",
]
[[package]]
name = "solidcraft-derive"
version = "0.11.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "solidcraft-doc"
version = "0.11.0"
@@ -3487,6 +3496,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"solidcraft-derive",
"solidcraft-geom",
"solidcraft-kernel",
"solidcraft-render",
+5
View File
@@ -24,10 +24,15 @@ solidcraft-io = { path = "crates/io" }
solidcraft-engine = { path = "crates/engine" }
solidcraft-ui-egui = { path = "crates/ui-egui" }
solidcraft-mcp = { path = "crates/mcp" }
solidcraft-derive = { path = "crates/derive" }
# foundation
thiserror = "2"
serde = { version = "1", features = ["derive", "rc"] }
# derive macros (crates/derive); already in the tree through serde_derive
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }
serde_json = { version = "1", features = ["float_roundtrip"] }
log = "0.4"
sha2 = "0.10"
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "solidcraft-derive"
description = "SolidCraft derive macros: the walk over a feature's geometry, from role attributes on its fields"
version.workspace = true
edition.workspace = true
license.workspace = true
[lib]
proc-macro = true
[lints]
workspace = true
[dependencies]
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
+294
View File
@@ -0,0 +1,294 @@
//! `#[derive(Walk)]`: the walks over a feature (`crate::walk::Walk`), generated from the fields of
//! a struct or an enum's variants. Each walk visits the fields in declaration order, and comes in
//! `&` and `&mut`:
//!
//! - expressions (`walk_exprs`): each with its label, kind and role; the inputs' order names their
//! parameters (`dN`);
//! - references (`walk_refs`): sketches by id, features and bodies by name;
//! - geometry (`walk_geometry`): points with their roles, planes, construction references,
//! placement points and profile selections.
//!
//! What a field is, is written once, on the field, with `#[walk(...)]`:
//!
//! - no attribute: the field's own type walks itself (`impl Walk`): a plane, a nested struct, a
//! list of them, or nothing for names, flags and numbers. A bare `Vec3` has no walk, so a point
//! that does not say its role does not compile;
//! - `length = label`, `angle = label`, `unitless = label`: an expression input (a `String`, an
//! optional one, a list or an array of them, or `(f64, String)` pairs). The label is any
//! expression: a `&'static str`, an array of them (one per element), or a closure
//! `|i: usize, n: usize| -> &'static str`; it may read the other fields;
//! - `sketch`, `feature`, `body`: a reference (an id, or a name, optional or a list);
//! - `pick`, `pinned`, `pos`, `dir`: a point (`Vec3`, optional or a list) with that role;
//! `pick_if = cond`: a pick when `cond` holds, else a position;
//! - `placement`: a primitive's placement point (its X, Y and Z expressions are not inputs);
//! - `profiles_of = sketch_field`, optionally with `when = cond`: a profile selection;
//! - `after = field`: visit this field right after `field` instead of in declaration order (where
//! the parameter names already given need it);
//! - `picks_faces`, on a nested value: its positions pick faces here (a `FaceAt` that names the
//! face it is on).
//!
//! Conditions and labels are worked out before the walk starts. The generated code names
//! `crate::walk`, so the derive is for `solidcraft-doc`.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as Tokens};
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Expr, Fields, Ident, Member, parse_macro_input};
#[proc_macro_derive(Walk, attributes(walk))]
pub fn derive_walk(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match expand(&input) {
Ok(t) => t.into(),
Err(e) => e.to_compile_error().into(),
}
}
/// A field's role in the geometry walk.
enum Geo {
Points(&'static str),
PickIf(Expr),
Placement,
Profiles { sketch: Ident, when: Option<Expr> },
}
/// What a field is, from its `#[walk(...)]` attributes.
#[derive(Default)]
struct Roles {
geo: Option<Geo>,
/// The kind (`Length`, `Angle`, `Unitless`) and the label.
expr: Option<(&'static str, Expr)>,
/// `Sketch`, `Feature` or `Body`.
reference: Option<&'static str>,
after: Option<Ident>,
/// The positions inside this (nested) value pick faces here.
picks_faces: bool,
}
impl Roles {
/// No role given: the field's type walks itself.
fn nested(&self) -> bool {
self.geo.is_none() && self.expr.is_none() && self.reference.is_none()
}
}
fn roles(field: &syn::Field) -> syn::Result<Roles> {
let mut r = Roles::default();
for attr in field.attrs.iter().filter(|a| a.path().is_ident("walk")) {
let mut sketch = None;
let mut when = None;
attr.parse_nested_meta(|meta| {
let name = meta.path.get_ident().map(Ident::to_string).unwrap_or_default();
match name.as_str() {
"pick" => r.geo = Some(Geo::Points("Pick")),
"pinned" => r.geo = Some(Geo::Points("Pinned")),
"pos" => r.geo = Some(Geo::Points("Pos")),
"dir" => r.geo = Some(Geo::Points("Dir")),
"placement" => r.geo = Some(Geo::Placement),
"pick_if" => r.geo = Some(Geo::PickIf(meta.value()?.parse()?)),
"profiles_of" => sketch = Some(meta.value()?.parse::<Ident>()?),
"when" => when = Some(meta.value()?.parse::<Expr>()?),
"length" => r.expr = Some(("Length", meta.value()?.parse()?)),
"angle" => r.expr = Some(("Angle", meta.value()?.parse()?)),
"unitless" => r.expr = Some(("Unitless", meta.value()?.parse()?)),
"sketch" => r.reference = Some("Sketch"),
"feature" => r.reference = Some("Feature"),
"body" => r.reference = Some("Body"),
"after" => r.after = Some(meta.value()?.parse()?),
"picks_faces" => r.picks_faces = true,
_ => return Err(meta.error("unknown walk role")),
}
Ok(())
})?;
if let Some(sketch) = sketch {
r.geo = Some(Geo::Profiles { sketch, when });
} else if when.is_some() {
return Err(syn::Error::new_spanned(attr, "`when` goes with `profiles_of`"));
}
}
Ok(r)
}
#[derive(Clone, Copy, PartialEq)]
enum Which {
Exprs,
Refs,
Geometry,
}
/// A value worked out before the walk (a condition, a label, a sketch id) for field `b`.
fn pre(b: &Ident, what: &str) -> Ident {
format_ident!("__{}_{}", what, b)
}
/// The statements that walk field `b` in walk `which`.
fn walk_field(b: &Ident, r: &Roles, which: Which, mutable: bool) -> Tokens {
let w = quote!(crate::walk);
let each = if mutable { quote!(each_mut) } else { quote!(each) };
if r.nested() {
let m = match which {
Which::Exprs => "walk_exprs",
Which::Refs => "walk_refs",
Which::Geometry => "walk_geometry",
};
let m = if mutable { format_ident!("{}_mut", m) } else { format_ident!("{}", m) };
if r.picks_faces && which == Which::Geometry {
return quote! {
#w::Walk::#m(#b, &mut |g| f(match g { #w::Geo::Pos(p) => #w::Geo::Pick(p), g => g }));
};
}
return quote! { #w::Walk::#m(#b, f); };
}
match which {
Which::Exprs => {
let label = pre(b, "label");
match (&r.expr, &r.geo) {
(Some((kind, _)), _) => {
let kind = Ident::new(kind, Span::call_site());
quote! {
#w::ExprField::#each(#b, &mut |i, n, e| f((#w::Label::at(&#label, i, n), e, #w::Kind::#kind, #w::ExprRole::Input)));
}
}
(None, Some(Geo::Placement)) => quote! {
#w::ExprField::#each(#b, &mut |i, n, e| f((#w::Label::at(&["X", "Y", "Z"], i, n), e, #w::Kind::Length, #w::ExprRole::Placement)));
},
_ => quote!(),
}
}
Which::Refs => match r.reference {
Some(kind) => {
let t = if kind == "Sketch" { quote!(u64) } else { quote!(String) };
let kind = Ident::new(kind, Span::call_site());
quote! { #w::Each::<#t>::#each(#b, &mut |x| f(#w::Ref::#kind(x))); }
}
None => quote!(),
},
Which::Geometry => match &r.geo {
Some(Geo::Points(v)) => {
let v = Ident::new(v, Span::call_site());
quote! { #w::Each::<#w::Vec3>::#each(#b, &mut |p| f(#w::Geo::#v(p))); }
}
Some(Geo::PickIf(_)) => {
let pick = pre(b, "pick");
quote! {
#w::Each::<#w::Vec3>::#each(#b, &mut |p| f(if #pick { #w::Geo::Pick(p) } else { #w::Geo::Pos(p) }));
}
}
Some(Geo::Placement) => quote! { f(#w::Geo::Placement(#b)); },
Some(Geo::Profiles { .. }) => {
let (id, when) = (pre(b, "sketch"), pre(b, "when"));
quote! {
if #when && let Some(id) = #id {
#w::Each::<#w::ProfileSel>::#each(#b, &mut |sel| f(#w::Geo::Profiles(id, sel)));
}
}
}
None => quote!(),
},
}
}
/// The values field `b`'s walk needs, worked out before the walk starts (the walk may hand out
/// the fields they read).
fn pre_values(b: &Ident, r: &Roles, which: Which) -> Tokens {
let w = quote!(crate::walk);
match (which, &r.expr, &r.geo) {
(Which::Exprs, Some((_, label)), _) => {
let l = pre(b, "label");
quote! { let #l = #label; }
}
(Which::Geometry, _, Some(Geo::PickIf(cond))) => {
let p = pre(b, "pick");
quote! { let #p: bool = #cond; }
}
(Which::Geometry, _, Some(Geo::Profiles { sketch, when })) => {
let (id, wh) = (pre(b, "sketch"), pre(b, "when"));
let when = when.as_ref().map_or(quote!(true), |c| quote!(#c));
quote! {
let #wh: bool = #when;
let mut #id: Option<u64> = None;
#w::Each::<u64>::each(&*#sketch, &mut |s| #id = Some(*s));
}
}
_ => quote!(),
}
}
/// A pattern that binds every field of `fields` by name (`_0`, `_1`… for tuple fields), and the
/// body of walk `which` over them.
fn arm(fields: &Fields, which: Which, mutable: bool) -> syn::Result<(Tokens, Tokens)> {
let mut members = Vec::new();
let mut walked: Vec<(Ident, Roles)> = Vec::new();
for (i, field) in fields.iter().enumerate() {
let b = field.ident.clone().unwrap_or_else(|| format_ident!("_{}", i));
members.push(field.ident.clone().map_or_else(|| Member::Unnamed(i.into()), Member::Named));
walked.push((b, roles(field)?));
}
let bindings: Vec<Ident> = walked.iter().map(|(b, _)| b.clone()).collect();
// `after = x` moves a field to just after `x`.
for b in &bindings {
let Some(pos) = walked.iter().position(|(x, _)| x == b) else { continue };
let Some(after) = walked.get(pos).and_then(|(_, r)| r.after.clone()) else { continue };
let moved = walked.remove(pos);
let Some(at) = walked.iter().position(|(x, _)| *x == after) else {
return Err(syn::Error::new_spanned(after, "no such field"));
};
walked.insert(at + 1, moved);
}
let mut body = Tokens::new();
for (b, r) in &walked {
body.extend(pre_values(b, r, which));
}
for (b, r) in &walked {
body.extend(walk_field(b, r, which, mutable));
}
let pattern = match fields {
Fields::Named(_) => quote!({ #(#bindings),* }),
Fields::Unnamed(_) => quote!({ #(#members: #bindings),* }),
Fields::Unit => quote!(),
};
Ok((pattern, body))
}
fn expand(input: &DeriveInput) -> syn::Result<Tokens> {
let name = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let w = quote!(crate::walk);
let mut methods = Tokens::new();
for (which, method, item) in
[(Which::Exprs, "walk_exprs", "ExprItem"), (Which::Refs, "walk_refs", "RefItem"), (Which::Geometry, "walk_geometry", "GeoItem")]
{
for mutable in [false, true] {
let body = match &input.data {
Data::Struct(s) => {
let (pattern, body) = arm(&s.fields, which, mutable)?;
quote! { let Self #pattern = self; #body }
}
Data::Enum(e) => {
let mut arms = Tokens::new();
for v in &e.variants {
let ident = &v.ident;
let (pattern, body) = arm(&v.fields, which, mutable)?;
arms.extend(quote! { Self::#ident #pattern => { #body } });
}
quote! { match self { #arms } }
}
Data::Union(u) => return Err(syn::Error::new_spanned(u.union_token, "unions are not walked")),
};
let (method, item, r) = if mutable {
(format_ident!("{}_mut", method), format_ident!("{}Mut", item), quote!(&'a mut))
} else {
(format_ident!("{}", method), format_ident!("{}", item), quote!(&'a))
};
methods.extend(quote! {
#[allow(unused_variables, unused_mut, clippy::needless_borrow)]
fn #method<'a>(#r self, f: &mut dyn FnMut(#w::#item<'a>)) { #body }
});
}
}
Ok(quote! {
impl #impl_generics #w::Walk for #name #ty_generics #where_clause { #methods }
})
}
+1
View File
@@ -13,6 +13,7 @@ solidcraft-geom = { workspace = true }
solidcraft-sketch = { workspace = true }
solidcraft-kernel = { workspace = true }
solidcraft-render = { workspace = true }
solidcraft-derive = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
+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!();
}
+2
View File
@@ -36,6 +36,8 @@ impl Class {
/// prefix.
pub const TABLE: &[(&str, Class)] = &[
("geom", Class::Layer(0)),
// the derive macros (proc-macro, no workspace dependencies)
("derive", Class::Standalone),
("sketch", Class::Layer(1)),
("kernel", Class::Layer(1)),
("render", Class::Layer(1)),