inputs_mut, inputs and expressions are now built on one exhaustive walk (walk_exprs / walk_exprs_mut) instead of two hand-written matches over FeatureKind. inputs() no longer clones the feature, plane chains are walked with the same nesting cap for inputs as for expressions, and a new variant without a walk arm does not compile. A golden record of every variant's expressions guards the walk.
This commit is contained in:
1 parent
35e58d18ed
commit
3341e2d60b
6 files changed
+896
-486
No files matched your search
@@ -575,15 +575,6 @@ impl GeoRef {
|
||||
}
|
||||
}
|
||||
|
||||
impl GeoRef {
|
||||
fn plane_ref(&self) -> Option<&PlaneRef> {
|
||||
match self {
|
||||
GeoRef::Plane { plane } => Some(plane),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AxisDef {
|
||||
pub fn refs(&self) -> Vec<&GeoRef> {
|
||||
match self {
|
||||
@@ -594,11 +585,6 @@ impl AxisDef {
|
||||
}
|
||||
}
|
||||
|
||||
/// Planes it refers to (whose expressions it depends on).
|
||||
pub fn planes(&self) -> Vec<&PlaneRef> {
|
||||
self.refs().into_iter().filter_map(GeoRef::plane_ref).collect()
|
||||
}
|
||||
|
||||
pub fn refs_mut(&mut self) -> Vec<&mut GeoRef> {
|
||||
match self {
|
||||
AxisDef::ThroughFace { face } => vec![face],
|
||||
@@ -621,10 +607,6 @@ impl PointDef {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn planes(&self) -> Vec<&PlaneRef> {
|
||||
self.refs().into_iter().filter_map(GeoRef::plane_ref).collect()
|
||||
}
|
||||
|
||||
pub fn refs_mut(&mut self) -> Vec<&mut GeoRef> {
|
||||
match self {
|
||||
PointDef::At { point } => vec![point],
|
||||
|
||||
@@ -1247,27 +1247,6 @@ pub enum PatternKind {
|
||||
},
|
||||
}
|
||||
|
||||
fn plane_exprs<'a>(p: &'a PlaneRef, v: &mut Vec<&'a str>) {
|
||||
let mut p = p;
|
||||
for _ in 0..64 {
|
||||
match p {
|
||||
PlaneRef::Offset { base, distance } => {
|
||||
v.push(distance);
|
||||
p = base;
|
||||
}
|
||||
PlaneRef::AtAngle { base, angle, .. } => {
|
||||
v.push(angle);
|
||||
p = base;
|
||||
}
|
||||
PlaneRef::AlongPath { t, .. } => {
|
||||
v.push(t);
|
||||
break;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn z_axis() -> Vec3 {
|
||||
Vec3::Z
|
||||
}
|
||||
@@ -1615,179 +1594,6 @@ impl FeatureKind {
|
||||
let mut k = self.clone();
|
||||
k.face_points_mut().into_iter().map(|p| *p).collect()
|
||||
}
|
||||
|
||||
pub fn expressions(&self) -> Vec<&str> {
|
||||
let mut v: Vec<&str> = Vec::new();
|
||||
match self {
|
||||
FeatureKind::ConstructionAxis { def } => def.planes().into_iter().for_each(|p| plane_exprs(p, &mut v)),
|
||||
FeatureKind::ConstructionPoint { def } => {
|
||||
if let PointDef::AlongPath { t, .. } = def {
|
||||
v.push(t);
|
||||
}
|
||||
def.planes().into_iter().for_each(|p| plane_exprs(p, &mut v));
|
||||
}
|
||||
FeatureKind::Sketch { plane, .. }
|
||||
| FeatureKind::ConstructionPlane { plane }
|
||||
| FeatureKind::Split { plane, .. }
|
||||
| FeatureKind::SplitFace { plane, .. } => plane_exprs(plane, &mut v),
|
||||
FeatureKind::Extrude { extent, .. } | FeatureKind::SurfaceExtrude { extent, .. } => {
|
||||
v.push(&extent.distance);
|
||||
if let Some(d) = &extent.distance2 {
|
||||
v.push(d);
|
||||
}
|
||||
if let Some(d) = &extent.start_offset {
|
||||
v.push(d);
|
||||
}
|
||||
if let Some(d) = &extent.taper {
|
||||
v.push(d);
|
||||
}
|
||||
if let Some(d) = &extent.to_offset {
|
||||
v.push(d);
|
||||
}
|
||||
}
|
||||
FeatureKind::Revolve { angle, angle2, .. } | FeatureKind::SurfaceRevolve { angle, angle2, .. } => {
|
||||
v.push(angle);
|
||||
v.extend(angle2.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Fillet { radius, style, .. } => {
|
||||
v.push(radius);
|
||||
match style {
|
||||
FilletStyle::Variable { radius2, mid, .. } => {
|
||||
v.push(radius2);
|
||||
v.extend(mid.iter().map(|(_, r)| r.as_str()));
|
||||
}
|
||||
FilletStyle::Spline { radii, .. } => v.extend(radii.iter().map(String::as_str)),
|
||||
FilletStyle::Constant | FilletStyle::Chord | FilletStyle::Curvature => {}
|
||||
}
|
||||
}
|
||||
FeatureKind::Chamfer { distance, distance2, angle, .. } => {
|
||||
v.push(distance);
|
||||
v.extend(distance2.iter().map(String::as_str));
|
||||
v.extend(angle.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Box { length, width, height, corner, .. } => {
|
||||
v.extend([length.as_str(), width, height]);
|
||||
v.extend(corner.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Cylinder { radius, height, base, .. } => {
|
||||
v.extend([radius.as_str(), height]);
|
||||
v.extend(base.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Sphere { radius, center, .. } => {
|
||||
v.push(radius);
|
||||
v.extend(center.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Torus { major, minor, center, .. } => {
|
||||
v.extend([major.as_str(), minor]);
|
||||
v.extend(center.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Combine { .. } => {}
|
||||
FeatureKind::Pattern { pattern, .. } => match pattern {
|
||||
PatternKind::Rectangular { count1, spacing1, count2, spacing2, .. } => {
|
||||
v.extend([count1.as_str(), spacing1]);
|
||||
v.extend(count2.iter().map(String::as_str));
|
||||
v.extend(spacing2.iter().map(String::as_str));
|
||||
}
|
||||
PatternKind::Circular { count, angle, .. } => v.extend([count.as_str(), angle]),
|
||||
PatternKind::Path { count, spacing, .. } => v.extend([count.as_str(), spacing]),
|
||||
},
|
||||
FeatureKind::Mirror { plane, .. } => plane_exprs(plane, &mut v),
|
||||
FeatureKind::Shell { thickness, .. } => v.push(thickness),
|
||||
FeatureKind::Loft { .. } | FeatureKind::Sweep { .. } | FeatureKind::SurfaceSweep { .. } | FeatureKind::SurfaceLoft { .. } => {}
|
||||
FeatureKind::Draft { angle, neutral, .. } => {
|
||||
v.push(angle);
|
||||
plane_exprs(neutral, &mut v);
|
||||
}
|
||||
FeatureKind::Hole { diameter, depth, hole, .. } => {
|
||||
v.push(diameter);
|
||||
v.extend(depth.iter().map(String::as_str));
|
||||
match hole {
|
||||
HoleKind::Simple => {}
|
||||
HoleKind::Drilled { tip_angle } => v.push(tip_angle),
|
||||
HoleKind::Counterbore { cb_diameter, cb_depth } => v.extend([cb_diameter.as_str(), cb_depth]),
|
||||
HoleKind::Countersink { cs_diameter, cs_angle } => v.extend([cs_diameter.as_str(), cs_angle]),
|
||||
}
|
||||
}
|
||||
FeatureKind::Thread { length, .. } => v.extend(length.iter().map(String::as_str)),
|
||||
FeatureKind::Scale { factor, factors, .. } => {
|
||||
v.push(factor);
|
||||
v.extend(factors.iter().flatten().map(String::as_str));
|
||||
}
|
||||
FeatureKind::OffsetFace { distance, .. } => v.push(distance),
|
||||
FeatureKind::BoundingSolid { margin, .. } => v.push(margin),
|
||||
FeatureKind::Pipe { diameter, wall, .. } => {
|
||||
v.push(diameter);
|
||||
v.extend(wall.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Move { translate, angle, .. } => {
|
||||
v.extend(translate.iter().map(String::as_str));
|
||||
if let Some(a) = angle {
|
||||
v.push(a);
|
||||
}
|
||||
}
|
||||
FeatureKind::Emboss { depth, .. } => v.push(depth),
|
||||
FeatureKind::Boss { diameter, height, hole_diameter, hole_depth, draft, fillet, ribs, rib_thickness, rib_length, rib_offset, .. } => {
|
||||
v.extend([diameter.as_str(), height]);
|
||||
for o in [hole_diameter, hole_depth, draft, fillet, ribs, rib_thickness, rib_length, rib_offset].into_iter().flatten() {
|
||||
v.push(o);
|
||||
}
|
||||
}
|
||||
FeatureKind::Lip { width, height, gap, .. } => {
|
||||
v.extend([width.as_str(), height]);
|
||||
v.extend(gap.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::SnapFit { length, thickness, width, catch_depth, catch_length, .. } => {
|
||||
v.extend([length.as_str(), thickness, width, catch_depth, catch_length]);
|
||||
}
|
||||
FeatureKind::Rest { width, length, height, draft, thickness, .. } => {
|
||||
v.extend([width.as_str(), height]);
|
||||
v.extend([length, draft, thickness].into_iter().flatten().map(String::as_str));
|
||||
}
|
||||
FeatureKind::SheetContour { distance, .. } => v.push(distance),
|
||||
FeatureKind::SheetFlange { edges, height, angle, radius, .. } => {
|
||||
v.extend([height.as_str(), angle]);
|
||||
v.extend(radius.iter().map(String::as_str));
|
||||
for e in edges {
|
||||
v.extend([&e.angle, &e.height].into_iter().flatten().map(String::as_str));
|
||||
}
|
||||
}
|
||||
FeatureKind::SheetFold { angle, radius, .. } => {
|
||||
v.push(angle);
|
||||
v.extend(radius.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::SheetHem { length, gap, .. } => {
|
||||
v.push(length);
|
||||
v.extend(gap.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::SheetBase { .. } | FeatureKind::SheetUnfold { .. } | FeatureKind::SheetConvert { .. } => {}
|
||||
FeatureKind::Coil { diameter, pitch, turns, section_size, start_angle, base, .. } => {
|
||||
v.extend([diameter.as_str(), pitch, turns, section_size]);
|
||||
v.extend(start_angle.iter().map(String::as_str));
|
||||
v.extend(base.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::Rib { thickness, depth, .. } => {
|
||||
v.push(thickness);
|
||||
v.extend(depth.iter().map(String::as_str));
|
||||
}
|
||||
FeatureKind::ReplaceFace { target, .. } => plane_exprs(target, &mut v),
|
||||
FeatureKind::Align { .. } | FeatureKind::Remove { .. } | FeatureKind::BoundaryFill { .. } | FeatureKind::DeleteFace { .. } => {}
|
||||
FeatureKind::SurfaceOffset { distance, tolerance, .. } => {
|
||||
v.push(distance);
|
||||
if let Some(t) = tolerance {
|
||||
v.push(t);
|
||||
}
|
||||
}
|
||||
FeatureKind::Stitch { tolerance, .. } => v.push(tolerance),
|
||||
FeatureKind::MeshConvert { tolerance, .. } => v.push(tolerance),
|
||||
FeatureKind::Thicken { thickness, .. } => v.push(thickness),
|
||||
FeatureKind::SurfaceTrim { plane, .. } => plane_exprs(plane, &mut v),
|
||||
FeatureKind::SurfaceExtend { distance, .. } => v.push(distance),
|
||||
FeatureKind::Unstitch { .. } | FeatureKind::ReverseNormal { .. } => {}
|
||||
FeatureKind::Patch { .. } => {}
|
||||
FeatureKind::Import { .. } | FeatureKind::MeshImport { .. } | FeatureKind::BodyTransfer { .. } => {}
|
||||
}
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
//! Golden record of what the walk over a feature's expressions reports, for every variant (and
|
||||
//! every fillet style, pattern, hole and construction definition): `inputs` (label, expression,
|
||||
//! kind, in order: the order names the `dN` parameters) and `expressions` (every expression,
|
||||
//! placement points included). Each expression field holds its own name, so a moved, missing or
|
||||
//! relabelled input shows in the diff. The records live in `feature_walk_tests/*.txt`; to record
|
||||
//! an intended change, run the tests with `FEATURE_WALK_BLESS=1`.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::FeatureKind;
|
||||
|
||||
fn plane(chain: &str) -> Value {
|
||||
json!({"type": "at_angle", "angle": format!("{chain}.angle"), "axis_origin": [0, 0, 0], "axis_dir": [1, 0, 0],
|
||||
"base": {"type": "offset", "distance": format!("{chain}.offset"), "base": {"type": "origin", "name": "XY"}}})
|
||||
}
|
||||
|
||||
fn geo_plane(chain: &str) -> Value {
|
||||
json!({"type": "plane", "plane": plane(chain)})
|
||||
}
|
||||
|
||||
fn along(chain: &str) -> Value {
|
||||
json!({"type": "along_path", "path": {"type": "edge", "at": [0, 0, 0]}, "t": format!("{chain}.t")})
|
||||
}
|
||||
|
||||
fn fixtures() -> Vec<(&'static str, Value)> {
|
||||
let o = [0, 0, 0];
|
||||
let sk = serde_json::to_value(solidcraft_sketch::Sketch::new()).unwrap();
|
||||
let extent = |n: &str| {
|
||||
json!({"distance": format!("{n}.distance"), "distance2": format!("{n}.distance2"), "start_offset": format!("{n}.start_offset"),
|
||||
"taper": format!("{n}.taper"), "to_offset": format!("{n}.to_offset")})
|
||||
};
|
||||
let fillet = |style: Value| json!({"type": "fillet", "edges": [o], "radius": "fillet.radius", "style": style});
|
||||
let pattern = |p: Value| json!({"type": "pattern", "features": ["F"], "pattern": p});
|
||||
let hole =
|
||||
|h: Value| json!({"type": "hole", "position": o, "direction": [0, 0, 1], "diameter": "hole.diameter", "depth": "hole.depth", "hole": h});
|
||||
vec![
|
||||
(
|
||||
"body_transfer",
|
||||
json!({"type": "body_transfer", "bodies": ["B"], "from": 0, "to": 1, "transform": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]}),
|
||||
),
|
||||
("sketch", json!({"type": "sketch", "plane": plane("sketch"), "sketch": sk.clone()})),
|
||||
("sketch_along_path", json!({"type": "sketch", "plane": along("sketch"), "sketch": sk})),
|
||||
("extrude", json!({"type": "extrude", "sketch": 1, "profiles": {"type": "all"}, "extent": extent("extrude")})),
|
||||
(
|
||||
"revolve",
|
||||
json!({"type": "revolve", "sketch": 1, "profiles": {"type": "all"}, "axis": {"type": "world", "axis": "X"}, "angle": "revolve.angle", "angle2": "revolve.angle2"}),
|
||||
),
|
||||
("fillet_constant", fillet(json!({"type": "constant"}))),
|
||||
("fillet_chord", fillet(json!({"type": "chord"}))),
|
||||
("fillet_curvature", fillet(json!({"type": "curvature"}))),
|
||||
("fillet_variable", fillet(json!({"type": "variable", "radius2": "fillet.radius2", "start": o, "mid": [[0.5, "fillet.mid"]]}))),
|
||||
("fillet_spline", fillet(json!({"type": "spline", "start": o, "knots": [0.0, 1.0], "radii": ["fillet.r1", "fillet.r2", "fillet.r3"]}))),
|
||||
(
|
||||
"chamfer",
|
||||
json!({"type": "chamfer", "edges": [o], "distance": "chamfer.distance", "distance2": "chamfer.distance2", "angle": "chamfer.angle"}),
|
||||
),
|
||||
("box", json!({"type": "box", "corner": ["box.x", "box.y", "box.z"], "length": "box.length", "width": "box.width", "height": "box.height"})),
|
||||
("cylinder", json!({"type": "cylinder", "base": ["cyl.x", "cyl.y", "cyl.z"], "radius": "cyl.radius", "height": "cyl.height"})),
|
||||
("sphere", json!({"type": "sphere", "center": ["sph.x", "sph.y", "sph.z"], "radius": "sph.radius"})),
|
||||
("torus", json!({"type": "torus", "center": ["tor.x", "tor.y", "tor.z"], "major": "tor.major", "minor": "tor.minor"})),
|
||||
("combine", json!({"type": "combine", "target": "A", "tools": ["B"], "operation": "join"})),
|
||||
(
|
||||
"pattern_rectangular",
|
||||
pattern(
|
||||
json!({"type": "rectangular", "dir1": [1, 0, 0], "count1": "rect.count1", "spacing1": "rect.spacing1", "dir2": [0, 1, 0], "count2": "rect.count2", "spacing2": "rect.spacing2"}),
|
||||
),
|
||||
),
|
||||
("pattern_circular", pattern(json!({"type": "circular", "origin": o, "axis": [0, 0, 1], "count": "circ.count", "angle": "circ.angle"}))),
|
||||
("pattern_path", pattern(json!({"type": "path", "path_sketch": 1, "path": ["l1"], "count": "path.count", "spacing": "path.spacing"}))),
|
||||
("mirror", json!({"type": "mirror", "features": ["F"], "plane": plane("mirror")})),
|
||||
("hole_simple", hole(json!({"type": "simple"}))),
|
||||
("hole_drilled", hole(json!({"type": "drilled", "tip_angle": "hole.tip_angle"}))),
|
||||
("hole_counterbore", hole(json!({"type": "counterbore", "cb_diameter": "hole.cb_diameter", "cb_depth": "hole.cb_depth"}))),
|
||||
("hole_countersink", hole(json!({"type": "countersink", "cs_diameter": "hole.cs_diameter", "cs_angle": "hole.cs_angle"}))),
|
||||
("thread", json!({"type": "thread", "face": o, "length": "thread.length"})),
|
||||
("loft", json!({"type": "loft", "sections": []})),
|
||||
("sweep", json!({"type": "sweep", "sketch": 1, "profiles": {"type": "all"}, "path_sketch": 2, "path": ["l1"]})),
|
||||
("shell", json!({"type": "shell", "faces": [o], "thickness": "shell.thickness"})),
|
||||
("draft", json!({"type": "draft", "faces": [o], "angle": "draft.angle", "neutral": plane("draft_neutral"), "pull": [0, 0, 1]})),
|
||||
("construction_plane", json!({"type": "construction_plane", "plane": plane("cplane")})),
|
||||
("split", json!({"type": "split", "body": "B", "plane": plane("split")})),
|
||||
(
|
||||
"construction_axis",
|
||||
json!({"type": "construction_axis", "def": {"type": "two_planes", "a": geo_plane("axis_a"), "b": geo_plane("axis_b")}}),
|
||||
),
|
||||
(
|
||||
"construction_point_planes",
|
||||
json!({"type": "construction_point", "def": {"type": "three_planes", "a": geo_plane("pt_a"), "b": geo_plane("pt_b"), "c": geo_plane("pt_c")}}),
|
||||
),
|
||||
(
|
||||
"construction_point_along",
|
||||
json!({"type": "construction_point", "def": {"type": "along_path", "path": {"type": "edge", "at": o}, "t": "pt.t"}}),
|
||||
),
|
||||
("split_face", json!({"type": "split_face", "faces": [o], "plane": plane("split_face")})),
|
||||
("scale", json!({"type": "scale", "bodies": ["B"], "factor": "scale.factor", "factors": ["scale.x", "scale.y", "scale.z"]})),
|
||||
("offset_face", json!({"type": "offset_face", "faces": [o], "distance": "offset_face.distance"})),
|
||||
("delete_face", json!({"type": "delete_face", "faces": [o]})),
|
||||
("bounding_solid", json!({"type": "bounding_solid", "bodies": ["B"], "margin": "bounding.margin"})),
|
||||
("pipe", json!({"type": "pipe", "path_sketch": 1, "path": ["l1"], "diameter": "pipe.diameter", "wall": "pipe.wall"})),
|
||||
("emboss", json!({"type": "emboss", "sketch": 1, "profiles": {"type": "all"}, "depth": "emboss.depth"})),
|
||||
("rib", json!({"type": "rib", "sketch": 1, "curves": ["l1"], "thickness": "rib.thickness", "depth": "rib.depth"})),
|
||||
("replace_face", json!({"type": "replace_face", "faces": [o], "target": plane("replace_face")})),
|
||||
("align", json!({"type": "align", "bodies": ["B"], "from": o, "to": o})),
|
||||
(
|
||||
"coil",
|
||||
json!({"type": "coil", "base": ["coil.x", "coil.y", "coil.z"], "diameter": "coil.diameter", "pitch": "coil.pitch", "turns": "coil.turns", "section_size": "coil.section_size", "start_angle": "coil.start_angle"}),
|
||||
),
|
||||
("sheet_base", json!({"type": "sheet_base", "sketch": 1, "profiles": {"type": "all"}})),
|
||||
("sheet_contour", json!({"type": "sheet_contour", "sketch": 1, "curves": ["l1"], "distance": "contour.distance"})),
|
||||
(
|
||||
"sheet_flange",
|
||||
json!({"type": "sheet_flange", "edges": [{"point": o, "angle": "flange.e1_angle", "height": "flange.e1_height"}, {"point": o, "angle": null, "height": "flange.e2_height"}], "height": "flange.height", "angle": "flange.angle", "radius": "flange.radius"}),
|
||||
),
|
||||
("sheet_hem", json!({"type": "sheet_hem", "edges": [o], "length": "hem.length", "gap": "hem.gap"})),
|
||||
(
|
||||
"sheet_fold",
|
||||
json!({"type": "sheet_fold", "a": o, "b": [1, 0, 0], "angle": "fold.angle", "radius": "fold.radius", "position": "centerline"}),
|
||||
),
|
||||
("sheet_unfold", json!({"type": "sheet_unfold"})),
|
||||
("sheet_convert", json!({"type": "sheet_convert", "body": "B", "face": o})),
|
||||
(
|
||||
"boss",
|
||||
json!({"type": "boss", "position": o, "diameter": "boss.diameter", "height": "boss.height", "hole_diameter": "boss.hole_diameter", "hole_depth": "boss.hole_depth",
|
||||
"draft": "boss.draft", "fillet": "boss.fillet", "ribs": "boss.ribs", "rib_thickness": "boss.rib_thickness", "rib_length": "boss.rib_length", "rib_offset": "boss.rib_offset"}),
|
||||
),
|
||||
("lip", json!({"type": "lip", "face": o, "width": "lip.width", "height": "lip.height", "gap": "lip.gap"})),
|
||||
(
|
||||
"snap_fit",
|
||||
json!({"type": "snap_fit", "position": o, "hook": [1, 0, 0], "length": "snap.length", "thickness": "snap.thickness", "width": "snap.width", "catch_depth": "snap.catch_depth", "catch_length": "snap.catch_length"}),
|
||||
),
|
||||
(
|
||||
"rest",
|
||||
json!({"type": "rest", "position": o, "width": "rest.width", "length": "rest.length", "height": "rest.height", "draft": "rest.draft", "thickness": "rest.thickness"}),
|
||||
),
|
||||
("boundary_fill", json!({"type": "boundary_fill", "tools": ["B"], "cells": [o]})),
|
||||
("remove", json!({"type": "remove", "bodies": ["B"]})),
|
||||
("patch", json!({"type": "patch"})),
|
||||
("stitch", json!({"type": "stitch", "bodies": ["B"], "tolerance": "stitch.tolerance"})),
|
||||
("thicken", json!({"type": "thicken", "bodies": ["B"], "thickness": "thicken.thickness", "operation": "new_body"})),
|
||||
("surface_extrude", json!({"type": "surface_extrude", "sketch": 1, "extent": extent("surface_extrude")})),
|
||||
(
|
||||
"surface_revolve",
|
||||
json!({"type": "surface_revolve", "sketch": 1, "axis": {"type": "world", "axis": "X"}, "angle": "srev.angle", "angle2": "srev.angle2"}),
|
||||
),
|
||||
("surface_sweep", json!({"type": "surface_sweep", "sketch": 1, "path_sketch": 2, "path": ["l1"]})),
|
||||
("surface_loft", json!({"type": "surface_loft", "sections": []})),
|
||||
("surface_offset", json!({"type": "surface_offset", "faces": [o], "distance": "soff.distance", "tolerance": "soff.tolerance"})),
|
||||
("unstitch", json!({"type": "unstitch", "body": "B"})),
|
||||
("reverse_normal", json!({"type": "reverse_normal", "body": "B"})),
|
||||
("surface_extend", json!({"type": "surface_extend", "body": "B", "edges": [o], "distance": "extend.distance"})),
|
||||
("surface_trim", json!({"type": "surface_trim", "body": "B", "plane": plane("trim"), "keep": o})),
|
||||
("move", json!({"type": "move", "bodies": ["B"], "translate": ["move.x", "move.y", "move.z"], "angle": "move.angle"})),
|
||||
("import", json!({"type": "import", "file": "a.step", "step": ""})),
|
||||
("mesh_convert", json!({"type": "mesh_convert", "body": "B", "tolerance": "mesh_convert.tolerance"})),
|
||||
("mesh_import", json!({"type": "mesh_import", "file": "a.stl", "meshes": []})),
|
||||
]
|
||||
}
|
||||
|
||||
/// Per fixture: its inputs, one per line (`label = expression (kind)`), then every expression.
|
||||
fn record() -> String {
|
||||
let mut out = String::new();
|
||||
for (name, v) in fixtures() {
|
||||
let kind: FeatureKind = serde_json::from_value(v).unwrap_or_else(|e| panic!("{name}: {e}"));
|
||||
out += &format!("{name}\n");
|
||||
let inputs = kind.inputs();
|
||||
if !inputs.is_empty() {
|
||||
out += " inputs:\n";
|
||||
for (label, e, k) in inputs {
|
||||
out += &format!(" {label} = {e} ({k:?})\n");
|
||||
}
|
||||
}
|
||||
let expressions = kind.expressions();
|
||||
if !expressions.is_empty() {
|
||||
out += &format!(" expressions: {}\n", expressions.join(", "));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Compares `got` with the golden file `name` in `feature_walk_tests/`, line by line. With
|
||||
/// `FEATURE_WALK_BLESS` set it rewrites the file instead, for an intended change (say why in the
|
||||
/// pull request).
|
||||
fn check_golden(name: &str, want: &str, got: &str) {
|
||||
if std::env::var_os("FEATURE_WALK_BLESS").is_some() {
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/feature_walk_tests").join(name);
|
||||
std::fs::write(&path, got).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
|
||||
return;
|
||||
}
|
||||
for (line, (want, got)) in want.lines().zip(got.lines()).enumerate() {
|
||||
assert_eq!(want, got, "{name} line {}; rerun with FEATURE_WALK_BLESS=1 if the change is intended", line + 1);
|
||||
}
|
||||
assert_eq!(want.lines().count(), got.lines().count(), "{name}: the record has a different number of lines");
|
||||
}
|
||||
|
||||
/// Recorded from the hand-written `inputs_mut` and `expressions` the walk replaced.
|
||||
const GOLDEN_EXPRS: &str = include_str!("feature_walk_tests/golden_exprs.txt");
|
||||
|
||||
#[test]
|
||||
fn inputs_and_expressions_match_the_golden_record() {
|
||||
check_golden("golden_exprs.txt", GOLDEN_EXPRS, &record());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_record_covers_every_variant() {
|
||||
let src = include_str!("document.rs");
|
||||
let body = src.split("pub enum FeatureKind {").nth(1).and_then(|s| s.split("\n}\n").next()).unwrap();
|
||||
let names: Vec<String> = fixtures().into_iter().map(|(_, v)| v["type"].as_str().unwrap().replace('_', "")).collect();
|
||||
for line in body.lines().filter(|l| l.starts_with(" ") && !l.starts_with(" ")) {
|
||||
let t = line.trim();
|
||||
if t.chars().next().is_some_and(|c| c.is_ascii_uppercase()) {
|
||||
let variant = t.split(|c: char| !c.is_alphanumeric()).next().unwrap_or("").to_lowercase();
|
||||
assert!(names.contains(&variant), "FeatureKind::{t} has no fixture in feature_walk_tests");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
body_transfer
|
||||
sketch
|
||||
inputs:
|
||||
Angle = sketch.angle (Angle)
|
||||
Offset = sketch.offset (Length)
|
||||
expressions: sketch.angle, sketch.offset
|
||||
sketch_along_path
|
||||
inputs:
|
||||
Distance (0 to 1) = sketch.t (Unitless)
|
||||
expressions: sketch.t
|
||||
extrude
|
||||
inputs:
|
||||
Distance = extrude.distance (Length)
|
||||
Distance 2 = extrude.distance2 (Length)
|
||||
Start offset = extrude.start_offset (Length)
|
||||
Taper angle = extrude.taper (Angle)
|
||||
To offset = extrude.to_offset (Length)
|
||||
expressions: extrude.distance, extrude.distance2, extrude.start_offset, extrude.taper, extrude.to_offset
|
||||
revolve
|
||||
inputs:
|
||||
Angle = revolve.angle (Angle)
|
||||
Angle 2 = revolve.angle2 (Angle)
|
||||
expressions: revolve.angle, revolve.angle2
|
||||
fillet_constant
|
||||
inputs:
|
||||
Radius = fillet.radius (Length)
|
||||
expressions: fillet.radius
|
||||
fillet_chord
|
||||
inputs:
|
||||
Chord length = fillet.radius (Length)
|
||||
expressions: fillet.radius
|
||||
fillet_curvature
|
||||
inputs:
|
||||
Equivalent radius = fillet.radius (Length)
|
||||
expressions: fillet.radius
|
||||
fillet_variable
|
||||
inputs:
|
||||
Start radius = fillet.radius (Length)
|
||||
End radius = fillet.radius2 (Length)
|
||||
Mid radius = fillet.mid (Length)
|
||||
expressions: fillet.radius, fillet.radius2, fillet.mid
|
||||
fillet_spline
|
||||
inputs:
|
||||
Start radius = fillet.radius (Length)
|
||||
Control radius = fillet.r1 (Length)
|
||||
Control radius = fillet.r2 (Length)
|
||||
End radius = fillet.r3 (Length)
|
||||
expressions: fillet.radius, fillet.r1, fillet.r2, fillet.r3
|
||||
chamfer
|
||||
inputs:
|
||||
Distance = chamfer.distance (Length)
|
||||
Distance 2 = chamfer.distance2 (Length)
|
||||
Angle = chamfer.angle (Angle)
|
||||
expressions: chamfer.distance, chamfer.distance2, chamfer.angle
|
||||
box
|
||||
inputs:
|
||||
Length = box.length (Length)
|
||||
Width = box.width (Length)
|
||||
Height = box.height (Length)
|
||||
expressions: box.length, box.width, box.height, box.x, box.y, box.z
|
||||
cylinder
|
||||
inputs:
|
||||
Radius = cyl.radius (Length)
|
||||
Height = cyl.height (Length)
|
||||
expressions: cyl.radius, cyl.height, cyl.x, cyl.y, cyl.z
|
||||
sphere
|
||||
inputs:
|
||||
Radius = sph.radius (Length)
|
||||
expressions: sph.radius, sph.x, sph.y, sph.z
|
||||
torus
|
||||
inputs:
|
||||
Major radius = tor.major (Length)
|
||||
Minor radius = tor.minor (Length)
|
||||
expressions: tor.major, tor.minor, tor.x, tor.y, tor.z
|
||||
combine
|
||||
pattern_rectangular
|
||||
inputs:
|
||||
Quantity = rect.count1 (Unitless)
|
||||
Spacing = rect.spacing1 (Length)
|
||||
Quantity 2 = rect.count2 (Unitless)
|
||||
Spacing 2 = rect.spacing2 (Length)
|
||||
expressions: rect.count1, rect.spacing1, rect.count2, rect.spacing2
|
||||
pattern_circular
|
||||
inputs:
|
||||
Quantity = circ.count (Unitless)
|
||||
Total angle = circ.angle (Angle)
|
||||
expressions: circ.count, circ.angle
|
||||
pattern_path
|
||||
inputs:
|
||||
Quantity = path.count (Unitless)
|
||||
Distance = path.spacing (Length)
|
||||
expressions: path.count, path.spacing
|
||||
mirror
|
||||
inputs:
|
||||
Angle = mirror.angle (Angle)
|
||||
Offset = mirror.offset (Length)
|
||||
expressions: mirror.angle, mirror.offset
|
||||
hole_simple
|
||||
inputs:
|
||||
Diameter = hole.diameter (Length)
|
||||
Depth = hole.depth (Length)
|
||||
expressions: hole.diameter, hole.depth
|
||||
hole_drilled
|
||||
inputs:
|
||||
Diameter = hole.diameter (Length)
|
||||
Depth = hole.depth (Length)
|
||||
Tip angle = hole.tip_angle (Angle)
|
||||
expressions: hole.diameter, hole.depth, hole.tip_angle
|
||||
hole_counterbore
|
||||
inputs:
|
||||
Diameter = hole.diameter (Length)
|
||||
Depth = hole.depth (Length)
|
||||
Counterbore diameter = hole.cb_diameter (Length)
|
||||
Counterbore depth = hole.cb_depth (Length)
|
||||
expressions: hole.diameter, hole.depth, hole.cb_diameter, hole.cb_depth
|
||||
hole_countersink
|
||||
inputs:
|
||||
Diameter = hole.diameter (Length)
|
||||
Depth = hole.depth (Length)
|
||||
Countersink diameter = hole.cs_diameter (Length)
|
||||
Countersink angle = hole.cs_angle (Angle)
|
||||
expressions: hole.diameter, hole.depth, hole.cs_diameter, hole.cs_angle
|
||||
thread
|
||||
inputs:
|
||||
Length = thread.length (Length)
|
||||
expressions: thread.length
|
||||
loft
|
||||
sweep
|
||||
shell
|
||||
inputs:
|
||||
Thickness = shell.thickness (Length)
|
||||
expressions: shell.thickness
|
||||
draft
|
||||
inputs:
|
||||
Angle = draft.angle (Angle)
|
||||
Angle = draft_neutral.angle (Angle)
|
||||
Offset = draft_neutral.offset (Length)
|
||||
expressions: draft.angle, draft_neutral.angle, draft_neutral.offset
|
||||
construction_plane
|
||||
inputs:
|
||||
Angle = cplane.angle (Angle)
|
||||
Offset = cplane.offset (Length)
|
||||
expressions: cplane.angle, cplane.offset
|
||||
split
|
||||
inputs:
|
||||
Angle = split.angle (Angle)
|
||||
Offset = split.offset (Length)
|
||||
expressions: split.angle, split.offset
|
||||
construction_axis
|
||||
inputs:
|
||||
Angle = axis_a.angle (Angle)
|
||||
Offset = axis_a.offset (Length)
|
||||
Angle = axis_b.angle (Angle)
|
||||
Offset = axis_b.offset (Length)
|
||||
expressions: axis_a.angle, axis_a.offset, axis_b.angle, axis_b.offset
|
||||
construction_point_planes
|
||||
inputs:
|
||||
Angle = pt_a.angle (Angle)
|
||||
Offset = pt_a.offset (Length)
|
||||
Angle = pt_b.angle (Angle)
|
||||
Offset = pt_b.offset (Length)
|
||||
Angle = pt_c.angle (Angle)
|
||||
Offset = pt_c.offset (Length)
|
||||
expressions: pt_a.angle, pt_a.offset, pt_b.angle, pt_b.offset, pt_c.angle, pt_c.offset
|
||||
construction_point_along
|
||||
inputs:
|
||||
Distance (0 to 1) = pt.t (Unitless)
|
||||
expressions: pt.t
|
||||
split_face
|
||||
inputs:
|
||||
Angle = split_face.angle (Angle)
|
||||
Offset = split_face.offset (Length)
|
||||
expressions: split_face.angle, split_face.offset
|
||||
scale
|
||||
inputs:
|
||||
Scale factor = scale.factor (Unitless)
|
||||
X scale = scale.x (Unitless)
|
||||
Y scale = scale.y (Unitless)
|
||||
Z scale = scale.z (Unitless)
|
||||
expressions: scale.factor, scale.x, scale.y, scale.z
|
||||
offset_face
|
||||
inputs:
|
||||
Distance = offset_face.distance (Length)
|
||||
expressions: offset_face.distance
|
||||
delete_face
|
||||
bounding_solid
|
||||
inputs:
|
||||
Margin = bounding.margin (Length)
|
||||
expressions: bounding.margin
|
||||
pipe
|
||||
inputs:
|
||||
Diameter = pipe.diameter (Length)
|
||||
Wall = pipe.wall (Length)
|
||||
expressions: pipe.diameter, pipe.wall
|
||||
emboss
|
||||
inputs:
|
||||
Depth = emboss.depth (Length)
|
||||
expressions: emboss.depth
|
||||
rib
|
||||
inputs:
|
||||
Thickness = rib.thickness (Length)
|
||||
Depth = rib.depth (Length)
|
||||
expressions: rib.thickness, rib.depth
|
||||
replace_face
|
||||
inputs:
|
||||
Angle = replace_face.angle (Angle)
|
||||
Offset = replace_face.offset (Length)
|
||||
expressions: replace_face.angle, replace_face.offset
|
||||
align
|
||||
coil
|
||||
inputs:
|
||||
Diameter = coil.diameter (Length)
|
||||
Pitch = coil.pitch (Length)
|
||||
Revolutions = coil.turns (Unitless)
|
||||
Section size = coil.section_size (Length)
|
||||
Start angle = coil.start_angle (Angle)
|
||||
expressions: coil.diameter, coil.pitch, coil.turns, coil.section_size, coil.start_angle, coil.x, coil.y, coil.z
|
||||
sheet_base
|
||||
sheet_contour
|
||||
inputs:
|
||||
Distance = contour.distance (Length)
|
||||
expressions: contour.distance
|
||||
sheet_flange
|
||||
inputs:
|
||||
Height = flange.height (Length)
|
||||
Angle = flange.angle (Angle)
|
||||
Bend radius = flange.radius (Length)
|
||||
Edge angle = flange.e1_angle (Angle)
|
||||
Edge height = flange.e1_height (Length)
|
||||
Edge height = flange.e2_height (Length)
|
||||
expressions: flange.height, flange.angle, flange.radius, flange.e1_angle, flange.e1_height, flange.e2_height
|
||||
sheet_hem
|
||||
inputs:
|
||||
Length = hem.length (Length)
|
||||
Gap = hem.gap (Length)
|
||||
expressions: hem.length, hem.gap
|
||||
sheet_fold
|
||||
inputs:
|
||||
Angle = fold.angle (Angle)
|
||||
Bend radius = fold.radius (Length)
|
||||
expressions: fold.angle, fold.radius
|
||||
sheet_unfold
|
||||
sheet_convert
|
||||
boss
|
||||
inputs:
|
||||
Diameter = boss.diameter (Length)
|
||||
Height = boss.height (Length)
|
||||
Hole diameter = boss.hole_diameter (Length)
|
||||
Hole depth = boss.hole_depth (Length)
|
||||
Draft angle = boss.draft (Angle)
|
||||
Root fillet = boss.fillet (Length)
|
||||
Ribs = boss.ribs (Unitless)
|
||||
Rib thickness = boss.rib_thickness (Length)
|
||||
Rib length = boss.rib_length (Length)
|
||||
Rib offset = boss.rib_offset (Length)
|
||||
expressions: boss.diameter, boss.height, boss.hole_diameter, boss.hole_depth, boss.draft, boss.fillet, boss.ribs, boss.rib_thickness, boss.rib_length, boss.rib_offset
|
||||
lip
|
||||
inputs:
|
||||
Width = lip.width (Length)
|
||||
Height = lip.height (Length)
|
||||
Gap = lip.gap (Length)
|
||||
expressions: lip.width, lip.height, lip.gap
|
||||
snap_fit
|
||||
inputs:
|
||||
Length = snap.length (Length)
|
||||
Thickness = snap.thickness (Length)
|
||||
Width = snap.width (Length)
|
||||
Catch depth = snap.catch_depth (Length)
|
||||
Catch length = snap.catch_length (Length)
|
||||
expressions: snap.length, snap.thickness, snap.width, snap.catch_depth, snap.catch_length
|
||||
rest
|
||||
inputs:
|
||||
Width = rest.width (Length)
|
||||
Height = rest.height (Length)
|
||||
Length = rest.length (Length)
|
||||
Draft angle = rest.draft (Angle)
|
||||
Wall thickness = rest.thickness (Length)
|
||||
expressions: rest.width, rest.height, rest.length, rest.draft, rest.thickness
|
||||
boundary_fill
|
||||
remove
|
||||
patch
|
||||
stitch
|
||||
inputs:
|
||||
Tolerance = stitch.tolerance (Length)
|
||||
expressions: stitch.tolerance
|
||||
thicken
|
||||
inputs:
|
||||
Thickness = thicken.thickness (Length)
|
||||
expressions: thicken.thickness
|
||||
surface_extrude
|
||||
inputs:
|
||||
Distance = surface_extrude.distance (Length)
|
||||
Distance 2 = surface_extrude.distance2 (Length)
|
||||
Start offset = surface_extrude.start_offset (Length)
|
||||
Taper angle = surface_extrude.taper (Angle)
|
||||
To offset = surface_extrude.to_offset (Length)
|
||||
expressions: surface_extrude.distance, surface_extrude.distance2, surface_extrude.start_offset, surface_extrude.taper, surface_extrude.to_offset
|
||||
surface_revolve
|
||||
inputs:
|
||||
Angle = srev.angle (Angle)
|
||||
Angle 2 = srev.angle2 (Angle)
|
||||
expressions: srev.angle, srev.angle2
|
||||
surface_sweep
|
||||
surface_loft
|
||||
surface_offset
|
||||
inputs:
|
||||
Distance = soff.distance (Length)
|
||||
Tolerance = soff.tolerance (Length)
|
||||
expressions: soff.distance, soff.tolerance
|
||||
unstitch
|
||||
reverse_normal
|
||||
surface_extend
|
||||
inputs:
|
||||
Distance = extend.distance (Length)
|
||||
expressions: extend.distance
|
||||
surface_trim
|
||||
inputs:
|
||||
Angle = trim.angle (Angle)
|
||||
Offset = trim.offset (Length)
|
||||
expressions: trim.angle, trim.offset
|
||||
move
|
||||
inputs:
|
||||
X distance = move.x (Length)
|
||||
Y distance = move.y (Length)
|
||||
Z distance = move.z (Length)
|
||||
Angle = move.angle (Angle)
|
||||
expressions: move.x, move.y, move.z, move.angle
|
||||
import
|
||||
mesh_convert
|
||||
inputs:
|
||||
Tolerance = mesh_convert.tolerance (Length)
|
||||
expressions: mesh_convert.tolerance
|
||||
mesh_import
|
||||
@@ -71,6 +71,8 @@ pub enum DocError {
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DocError>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod feature_walk_tests;
|
||||
#[cfg(test)]
|
||||
mod named_projection_tests;
|
||||
#[cfg(test)]
|
||||
|
||||
+346
-274
@@ -11,297 +11,379 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::expr::{self, Kind, ParamDef, Value};
|
||||
use crate::{DocError, Document, FeatureKind, HoleKind, MAX_PARAMS, PatternKind, PlaneRef, Result};
|
||||
|
||||
fn plane_inputs_mut<'a>(p: &'a mut PlaneRef, v: &mut Vec<(&'static str, &'a mut String, Kind)>) {
|
||||
match p {
|
||||
PlaneRef::Offset { base, distance } => {
|
||||
v.push(("Offset", distance, Kind::Length));
|
||||
plane_inputs_mut(base, v);
|
||||
}
|
||||
PlaneRef::AtAngle { base, angle, .. } => {
|
||||
v.push(("Angle", angle, Kind::Angle));
|
||||
plane_inputs_mut(base, v);
|
||||
}
|
||||
PlaneRef::AlongPath { t, .. } => v.push(("Distance (0 to 1)", t, Kind::Unitless)),
|
||||
_ => {}
|
||||
}
|
||||
/// 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
|
||||
/// keeps bare numbers in mm whatever the design's units.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ExprRole {
|
||||
Input,
|
||||
Placement,
|
||||
}
|
||||
|
||||
impl FeatureKind {
|
||||
/// The feature's numeric inputs: (label, expression, kind), in a fixed order.
|
||||
pub fn inputs_mut(&mut self) -> Vec<(&'static str, &mut String, Kind)> {
|
||||
use Kind::{Angle as A, Length as L, Unitless as U};
|
||||
let mut v: Vec<(&'static str, &mut String, Kind)> = Vec::new();
|
||||
match self {
|
||||
FeatureKind::Sketch { plane, .. }
|
||||
| FeatureKind::ConstructionPlane { plane }
|
||||
| FeatureKind::Split { plane, .. }
|
||||
| FeatureKind::SplitFace { plane, .. }
|
||||
| FeatureKind::Mirror { plane, .. } => plane_inputs_mut(plane, &mut v),
|
||||
FeatureKind::ConstructionAxis { def } => {
|
||||
for r in def.refs_mut() {
|
||||
if let crate::construct::GeoRef::Plane { plane } = r {
|
||||
plane_inputs_mut(plane, &mut v);
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
FeatureKind::ConstructionPoint { def } => match def {
|
||||
crate::construct::PointDef::AlongPath { t, .. } => v.push(("Distance (0 to 1)", t, U)),
|
||||
other => {
|
||||
for r in other.refs_mut() {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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_inputs_mut(plane, &mut v);
|
||||
$plane(plane, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
FeatureKind::Extrude { extent, .. } | FeatureKind::SurfaceExtrude { extent, .. } => {
|
||||
v.push(("Distance", &mut extent.distance, L));
|
||||
if let Some(d) = &mut extent.distance2 {
|
||||
v.push(("Distance 2", d, L));
|
||||
}
|
||||
if let Some(d) = &mut extent.start_offset {
|
||||
v.push(("Start offset", d, L));
|
||||
}
|
||||
if let Some(d) = &mut extent.taper {
|
||||
v.push(("Taper angle", d, A));
|
||||
}
|
||||
if let Some(d) = &mut extent.to_offset {
|
||||
v.push(("To offset", d, L));
|
||||
}
|
||||
}
|
||||
FeatureKind::Revolve { angle, angle2, .. } | FeatureKind::SurfaceRevolve { angle, angle2, .. } => {
|
||||
v.push(("Angle", angle, A));
|
||||
if let Some(a) = angle2 {
|
||||
v.push(("Angle 2", a, A));
|
||||
}
|
||||
}
|
||||
FeatureKind::Fillet { radius, style, .. } => match style {
|
||||
crate::FilletStyle::Constant => v.push(("Radius", radius, L)),
|
||||
crate::FilletStyle::Curvature => v.push(("Equivalent radius", radius, L)),
|
||||
crate::FilletStyle::Chord => v.push(("Chord length", radius, L)),
|
||||
crate::FilletStyle::Variable { radius2, mid, .. } => {
|
||||
v.push(("Start radius", radius, L));
|
||||
v.push(("End radius", radius2, L));
|
||||
v.extend(mid.iter_mut().map(|(_, r)| ("Mid radius", r, L)));
|
||||
}
|
||||
crate::FilletStyle::Spline { radii, .. } => {
|
||||
v.push(("Start radius", radius, L));
|
||||
let n = radii.len();
|
||||
v.extend(radii.iter_mut().enumerate().map(|(i, r)| (if i + 1 == n { "End radius" } else { "Control radius" }, r, L)));
|
||||
}
|
||||
},
|
||||
FeatureKind::Chamfer { distance, distance2, angle, .. } => {
|
||||
v.push(("Distance", distance, L));
|
||||
if let Some(d) = distance2 {
|
||||
v.push(("Distance 2", d, L));
|
||||
}
|
||||
if let Some(a) = angle {
|
||||
v.push(("Angle", a, A));
|
||||
}
|
||||
}
|
||||
FeatureKind::Box { length, width, height, .. } => {
|
||||
v.push(("Length", length, L));
|
||||
v.push(("Width", width, L));
|
||||
v.push(("Height", height, L));
|
||||
}
|
||||
FeatureKind::Cylinder { radius, height, .. } => {
|
||||
v.push(("Radius", radius, L));
|
||||
v.push(("Height", height, L));
|
||||
}
|
||||
FeatureKind::Sphere { radius, .. } => v.push(("Radius", radius, L)),
|
||||
FeatureKind::Torus { major, minor, .. } => {
|
||||
v.push(("Major radius", major, L));
|
||||
v.push(("Minor radius", minor, L));
|
||||
}
|
||||
FeatureKind::Pattern { pattern, .. } => match pattern {
|
||||
PatternKind::Rectangular { count1, spacing1, count2, spacing2, .. } => {
|
||||
v.push(("Quantity", count1, U));
|
||||
v.push(("Spacing", spacing1, L));
|
||||
if let Some(c) = count2 {
|
||||
v.push(("Quantity 2", c, U));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(sp) = spacing2 {
|
||||
v.push(("Spacing 2", sp, L));
|
||||
},
|
||||
FeatureKind::Extrude { extent, .. } | 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);
|
||||
}
|
||||
}
|
||||
PatternKind::Path { count, spacing, .. } => {
|
||||
v.push(("Quantity", count, U));
|
||||
v.push(("Distance", spacing, L));
|
||||
}
|
||||
PatternKind::Circular { count, angle, .. } => {
|
||||
v.push(("Quantity", count, U));
|
||||
v.push(("Total angle", angle, A));
|
||||
}
|
||||
},
|
||||
FeatureKind::Shell { thickness, .. } => v.push(("Thickness", thickness, L)),
|
||||
FeatureKind::Draft { angle, neutral, .. } => {
|
||||
v.push(("Angle", angle, A));
|
||||
plane_inputs_mut(neutral, &mut v);
|
||||
}
|
||||
FeatureKind::Hole { diameter, depth, hole, .. } => {
|
||||
v.push(("Diameter", diameter, L));
|
||||
if let Some(d) = depth {
|
||||
v.push(("Depth", d, L));
|
||||
}
|
||||
match hole {
|
||||
HoleKind::Simple => {}
|
||||
HoleKind::Drilled { tip_angle } => v.push(("Tip angle", tip_angle, A)),
|
||||
HoleKind::Counterbore { cb_diameter, cb_depth } => {
|
||||
v.push(("Counterbore diameter", cb_diameter, L));
|
||||
v.push(("Counterbore depth", cb_depth, L));
|
||||
}
|
||||
HoleKind::Countersink { cs_diameter, cs_angle } => {
|
||||
v.push(("Countersink diameter", cs_diameter, L));
|
||||
v.push(("Countersink angle", cs_angle, A));
|
||||
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::Thread { length, .. } => {
|
||||
if let Some(l) = length {
|
||||
v.push(("Length", l, L));
|
||||
}
|
||||
}
|
||||
FeatureKind::Scale { factor, factors, .. } => {
|
||||
v.push(("Scale factor", factor, U));
|
||||
if let Some([x, y, z]) = factors {
|
||||
v.push(("X scale", x, U));
|
||||
v.push(("Y scale", y, U));
|
||||
v.push(("Z scale", z, U));
|
||||
}
|
||||
}
|
||||
FeatureKind::OffsetFace { distance, .. } => v.push(("Distance", distance, L)),
|
||||
FeatureKind::BoundingSolid { margin, .. } => v.push(("Margin", margin, L)),
|
||||
FeatureKind::Pipe { diameter, wall, .. } => {
|
||||
v.push(("Diameter", diameter, L));
|
||||
if let Some(w) = wall {
|
||||
v.push(("Wall", w, L));
|
||||
}
|
||||
}
|
||||
FeatureKind::Move { translate, angle, .. } => {
|
||||
let [x, y, z] = translate;
|
||||
v.push(("X distance", x, L));
|
||||
v.push(("Y distance", y, L));
|
||||
v.push(("Z distance", z, L));
|
||||
if let Some(a) = angle {
|
||||
v.push(("Angle", a, A));
|
||||
}
|
||||
}
|
||||
FeatureKind::Emboss { depth, .. } => v.push(("Depth", depth, L)),
|
||||
FeatureKind::Boss { diameter, height, hole_diameter, hole_depth, draft, fillet, ribs, rib_thickness, rib_length, rib_offset, .. } => {
|
||||
v.push(("Diameter", diameter, L));
|
||||
v.push(("Height", height, L));
|
||||
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 {
|
||||
v.push((l, e, k));
|
||||
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::Lip { width, height, gap, .. } => {
|
||||
v.push(("Width", width, L));
|
||||
v.push(("Height", height, L));
|
||||
if let Some(g) = gap {
|
||||
v.push(("Gap", g, L));
|
||||
}
|
||||
}
|
||||
FeatureKind::Rest { width, length, height, draft, thickness, .. } => {
|
||||
v.push(("Width", width, L));
|
||||
v.push(("Height", height, L));
|
||||
for (l, o, k) in [("Length", length, L), ("Draft angle", draft, A), ("Wall thickness", thickness, L)] {
|
||||
if let Some(e) = o {
|
||||
v.push((l, e, k));
|
||||
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::SnapFit { length, thickness, width, catch_depth, catch_length, .. } => {
|
||||
v.push(("Length", length, L));
|
||||
v.push(("Thickness", thickness, L));
|
||||
v.push(("Width", width, L));
|
||||
v.push(("Catch depth", catch_depth, L));
|
||||
v.push(("Catch length", catch_length, L));
|
||||
}
|
||||
FeatureKind::SheetContour { distance, .. } => v.push(("Distance", distance, L)),
|
||||
FeatureKind::SheetFlange { edges, height, angle, radius, .. } => {
|
||||
v.push(("Height", height, L));
|
||||
v.push(("Angle", angle, A));
|
||||
if let Some(r) = radius {
|
||||
v.push(("Bend radius", r, L));
|
||||
}
|
||||
// The edges' own values come last, so the inputs above keep their names.
|
||||
for e in edges {
|
||||
if let Some(a) = &mut e.angle {
|
||||
v.push(("Edge angle", a, A));
|
||||
}
|
||||
if let Some(h) = &mut e.height {
|
||||
v.push(("Edge height", h, L));
|
||||
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::SheetFold { angle, radius, .. } => {
|
||||
v.push(("Angle", angle, A));
|
||||
if let Some(r) = radius {
|
||||
v.push(("Bend radius", r, L));
|
||||
FeatureKind::Sphere { radius, center, .. } => {
|
||||
f("Radius", radius, L, I);
|
||||
for (l, c) in XYZ.into_iter().zip(center) {
|
||||
f(l, c, L, P);
|
||||
}
|
||||
}
|
||||
}
|
||||
FeatureKind::SheetHem { length, gap, .. } => {
|
||||
v.push(("Length", length, L));
|
||||
if let Some(g) = gap {
|
||||
v.push(("Gap", g, L));
|
||||
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::Coil { diameter, pitch, turns, section_size, start_angle, .. } => {
|
||||
v.push(("Diameter", diameter, L));
|
||||
v.push(("Pitch", pitch, L));
|
||||
v.push(("Revolutions", turns, U));
|
||||
v.push(("Section size", section_size, L));
|
||||
if let Some(a) = start_angle {
|
||||
v.push(("Start angle", a, A));
|
||||
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::Rib { thickness, depth, .. } => {
|
||||
v.push(("Thickness", thickness, L));
|
||||
if let Some(d) = depth {
|
||||
v.push(("Depth", d, L));
|
||||
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::ReplaceFace { target, .. } => plane_inputs_mut(target, &mut v),
|
||||
FeatureKind::Stitch { tolerance, .. } => v.push(("Tolerance", tolerance, L)),
|
||||
FeatureKind::MeshConvert { tolerance, .. } => v.push(("Tolerance", tolerance, L)),
|
||||
FeatureKind::Thicken { thickness, .. } => v.push(("Thickness", thickness, L)),
|
||||
FeatureKind::SurfaceTrim { plane, .. } => plane_inputs_mut(plane, &mut v),
|
||||
FeatureKind::SurfaceExtend { distance, .. } => v.push(("Distance", distance, L)),
|
||||
FeatureKind::Unstitch { .. } | FeatureKind::ReverseNormal { .. } => {}
|
||||
FeatureKind::SurfaceOffset { distance, tolerance, .. } => {
|
||||
v.push(("Distance", distance, L));
|
||||
if let Some(t) = tolerance {
|
||||
v.push(("Tolerance", t, L));
|
||||
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::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, .. } => f("Distance", distance, L, I),
|
||||
FeatureKind::Unstitch { .. } | FeatureKind::ReverseNormal { .. } => {}
|
||||
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 { .. } => {}
|
||||
}
|
||||
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| {
|
||||
if role == ExprRole::Input {
|
||||
v.push((label, e, kind));
|
||||
}
|
||||
});
|
||||
v
|
||||
}
|
||||
|
||||
/// 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| {
|
||||
if role == ExprRole::Input {
|
||||
v.push((label, e.clone(), kind));
|
||||
}
|
||||
});
|
||||
v
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
@@ -330,16 +412,6 @@ impl FeatureKind {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read-only view of [`FeatureKind::inputs_mut`]: (label, expression, kind).
|
||||
pub fn inputs(&self) -> Vec<(&'static str, String, Kind)> {
|
||||
// Imports have no inputs (and their STEP text is large: don't clone it).
|
||||
if matches!(self, FeatureKind::Import { .. } | FeatureKind::MeshImport { .. }) {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut c = self.clone();
|
||||
c.inputs_mut().into_iter().map(|(l, e, k)| (l, e.clone(), k)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// One row of the parameters table.
|
||||
|
||||
Reference in new issue
Block a user