Retire legacy reference data and the external command catalog (#302)

- Remove xtask/data/fusion-catalog.tsv and xtask/data/fusion-ids.tsv.
- Remove the legacy command-id table (crates/engine/src/legacy_ids.rs) and its use in
  find_command and in the shortcut, recents and pinned-command loaders. Only SolidCraft's
  dotted ids name commands; any other id is an unknown command.
- Boundary Fill's id becomes solid.boundary_fill.
- Replace the parity report with SolidCraft's own feature checklist: `cargo xtask features`
  writes docs/features.md from the command registry.
- docs/oracle.md, docs/step-import.md (corpus section) and docs/iges-import.md: reference
  results are being rebuilt on analytic and open-source (FreeCAD) values; see #292.
  `cargo xtask oracle` and `cargo xtask step-corpus [--iges]` write these placeholders.
- AGENTS.md, CLAUDE.md, CONTRIBUTING.md, README.md, ROADMAP.md and the book describe
  SolidCraft as independent, with reference tests on analytic values and open-source tools,
  and agents do not drive or extract data from commercial CAD products.


Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-12 11:19:15 -04:00
1 parent c7f0139ad4
commit 3b8028a040
32 files changed
+695 -2324

No files matched your search

File diff suppressed because it is too large. Load diff
-184
View File
@@ -1,184 +0,0 @@
# Fusion catalog command id -> SolveCraft command id, for `cargo xtask parity` only (observation data).
# A catalog command is live when its SolveCraft command is registered.
SketchCreate sketch.create
Extrude solid.extrude
Revolve solid.revolve
Sweep solid.sweep
SolidLoft solid.loft
FusionRibCommand solid.rib
FusionWebCommand solid.web
EmbossCmd solid.emboss
FusionHoleCommand solid.hole
FusionThreadCommand solid.thread
PrimitiveBox solid.box
PrimitiveCylinder solid.cylinder
PrimitiveSphere solid.sphere
PrimitiveTorus solid.torus
PrimitiveCoil solid.coil
PrimitivePipe solid.pipe
StockModelCommand solid.bounding_solid
PatternRectangular solid.pattern.rectangular
PatternCircular solid.pattern.circular
PatternOnPath solid.pattern.path
MirrorCommand solid.mirror
FusionSurfaceThickenCommand surface.thicken
JointOrigin joint.origin
FusionPressPullCommand solid.press_pull
FusionFilletEdgesCommand solid.fillet
FusionChamferCommand solid.chamfer
FusionShellBodyCommand solid.shell
FusionDraftCommand solid.draft
ModifyScale solid.scale
FusionCombineCommand solid.combine
FusionOffsetFacesCommand solid.offset_face
FusionReplaceFaceCommand solid.replace_face
FusionSplitFaceCommand solid.split_face
FusionSplitBodyCommand solid.split_body
FusionMoveCommand solid.move
AlignCmd solid.align
FusionDeleteCommand timeline.delete
SoftDeleteCommand solid.remove
PhysicalMaterialCommand material.assign
AppearanceCommand appearance.assign
ChangeParameterCommand parameters.change
FusionComputeAllCommand timeline.compute_all
FusionImportCommandFromToolbar file.insert_step
FusionCreateNewComponentCommand component.create
JointAssembleCmdNew joint.create
JointAsBuiltCmd joint.as_built
RigidGroupCmd joint.rigid_group
FusionMoveJointsCommand joint.drive
FusionMotionRelationshipCommand joint.motion_link
FusionMotionStudyCommand motion.study
EnableContactSetsCmd contact.enable_sets
EnableAllContactCmd contact.enable_all
DisableAllContactCmd contact.disable_all
ContactSetCmd contact.create
FusionStartDesignConfigModeCmd config.configure
FusionShowDesignConfigPanelCmd config.table
FusionConfigurationRulesCmd config.rules
ConstructionPlaneOffsetFromPlaneCommand construct.plane.offset
ConstructionPlaneAtAngleCommand construct.plane.at_angle
ConstructionTangentPlaneCommand construct.plane.tangent
ConstructionMidPlaneCommand construct.plane.midplane
ConstructionPlaneFromTwoEdgesCommand construct.plane.two_edges
ConstructionPlaneFromThreePointsCommand construct.plane.three_points
ConstructionPlaneAlongPathCommand construct.plane.along_path
ConstructionAxisThroughCylinderCommand construct.axis.through_cylinder
ConstructionAxisNormalToFaceCommand construct.axis.normal_to_face
ConstructionAxisFromTwoPlanesCommand construct.axis.two_planes
ConstructionAxisThroughTwoPointsCommand construct.axis.two_points
ConstructionAxisThroughEdgeCommand construct.axis.edge
ConstructionPointFromPointCommand construct.point.vertex
ConstructionPointThroughTwoEdgesCommand construct.point.two_edges
ConstructionPointFromThreePlanesCommand construct.point.three_planes
ConstructionPointFromCircleOrSphereCommand construct.point.center
ConstructionPointAtEdgeAndPlaneCommand construct.point.edge_plane
ConstructionPointAlongPathCommand construct.point.along_path
MeasureCommand inspect.measure
InterferenceCheckCommand inspect.interference
FusionHalfSectionViewCommand inspect.section
FusionCurvatureCombAnalysisCommand inspect.curvature_comb
FusionCurvatureMapAnalysisCommand inspect.curvature_map
FusionDraftAnalysisCommand inspect.draft
FusionEnvironmentMapAnalysisCommand inspect.environment_map
FusionIsoCurveAnalysisCommand inspect.isocurve
FusionZebraAnalysisCommand inspect.zebra
FusionAccessibilityAnalysisCommand inspect.accessibility
FusionCenterOfMassCommand inspect.center_of_mass
FusionMinimumRadiusAnalysisCommand inspect.minimum_radius
FusionAddEditDecalCommand canvas.decal
FusionAddCanvasCommand canvas.insert
SketchImportSVG sketch.insert_svg
ImportDxfFileCommand sketch.insert_dxf
ParaMeshInsertAlignCommand file.insert_mesh
FusionFastenersCommand parts.fastener
SnapshotCmd component.capture_position
AsBuiltPositionsCmd component.revert_position
DrawPolyline sketch.line
SketchMidpointLine sketch.line.midpoint
SketchConstrainer sketch.constrainer
ShapeRectangleTwoPoint sketch.rectangle.two_point
ShapeRectangleThreePoint sketch.rectangle.three_point
ShapeRectangleCenter sketch.rectangle.center
CircleCenterRadius sketch.circle.center
CircleTwoPoint sketch.circle.two_point
CircleThreePoint sketch.circle.three_point
CircleTanTanRadius sketch.circle.two_tangent
CircleThreeTangent sketch.circle.three_tangent
ArcThreePoint sketch.arc.three_point
ArcCenterTwoPoint sketch.arc.center_point
ArcTangent sketch.arc.tangent
ShapePolygonCircumscribed sketch.polygon.circumscribed
ShapePolygonInscribed sketch.polygon.inscribed
ShapePolygonEdge sketch.polygon.edge
CircleElipse sketch.ellipse
ShapeSlotCenterToCenter sketch.slot.center_to_center
ShapeSlotOverall sketch.slot.overall
ShapeSlotCenterPoint sketch.slot.center_point
ShapeArcSlotThreePoint sketch.slot.arc_three_point
ShapeArcSlotCenterTwoPoint sketch.slot.arc_center
DrawSpline sketch.spline.fit_point
DrawCVMSpline3D sketch.spline.control_point
DrawCVMSpline5D sketch.spline.control_point_5
ConicCurveCmd sketch.conic
DrawPoint sketch.point
MTextCmd sketch.text
FitCurvesToSectionCommand sketch.fit_curves_to_section
MirrorSketchCommand sketch.mirror
CircularSketchPatternCommand sketch.pattern.circular
RectangularSketchPatternCommand sketch.pattern.rectangular
ProjectNewCmd sketch.project
IntersectCmd sketch.intersect
SpunProfileCmd sketch.spun_profile
Include3DGeometry sketch.include_3d
ProjectToSurface sketch.project_to_surface
IntersectionCurve sketch.intersection_curve
SketchIsoparametricCurve sketch.isoparametric_curve
SketchDimension sketch.dimension
FilletSketchCmd sketch.fillet
ChamferSketchEqualDistance sketch.chamfer.equal_distance
ChamferSketchDistanceAngle sketch.chamfer.distance_angle
ChamferSketchDistanceDistance sketch.chamfer.two_distance
BlendG1CurveSketchCmd sketch.blend_curve
Offset sketch.offset
TrimSketchCmd sketch.trim
ExtendSketchCmd sketch.extend
BreakSketchCmd sketch.break
SketchScaleCmd sketch.scale
SketchAutoConstraintAndDimCmd sketch.auto_constrain
SketchAutoConstraintAndDimFromDatumCmd sketch.auto_constrain_from_datum
ConstraintHorizontalVertical sketch.constraint.horizontal_vertical
ConstraintCoincident sketch.constraint.coincident
ConstraintTangent sketch.constraint.tangent
ConstraintEqual sketch.constraint.equal
ConstraintParallel sketch.constraint.parallel
ConstraintPerpendicular sketch.constraint.perpendicular
ConstraintFix sketch.constraint.fix
ConstraintMidPoint sketch.constraint.midpoint
ConstraintConcentric sketch.constraint.concentric
ConstraintCollinear sketch.constraint.collinear
ConstraintSymmetry sketch.constraint.symmetry
ConstraintSmooth sketch.constraint.curvature
SketchPolygonConstraintCmd sketch.constraint.polygon
SketchStop sketch.finish
SketchAutoConstrainAndFinish sketch.finish_auto_constrain
FusionSurfacePatchCommand surface.patch
FusionSurfaceTrimCommand surface.trim
FusionExtendCommand surface.extend
FusionSurfaceStitchCommand surface.stitch
FusionSheetMetalFlangeCommand sheet.flange
FusionSheetMetalHemFlangeCommand sheet.hem
SheetMetalFoldCmd sheet.fold
ConvertToSheetMetalCmd sheet.convert
FusionSheetMetalFlatPatternCmd sheet.flat_pattern
FusionSheetmetalUnfoldCommand sheet.unfold
FusionSheetMetalRulesCommand sheet.manage_rules
FusionAssignPlasticRuleCommand plastic.assign_rule
FusionManagePlasticRuleCommand plastic.manage_rules
FusionBossCommand plastic.boss
FusionSnapFitCommand plastic.snap_fit
FusionLipCommand plastic.lip
FusionRestCommand plastic.rest
ConstructionPlanePerpendicularToPlaneCommand construct.plane.perpendicular
ParaMeshConvertCommand mesh.convert
1 # Fusion catalog command id -> SolveCraft command id, for `cargo xtask parity` only (observation data).
2 # A catalog command is live when its SolveCraft command is registered.
3 SketchCreate sketch.create
4 Extrude solid.extrude
5 Revolve solid.revolve
6 Sweep solid.sweep
7 SolidLoft solid.loft
8 FusionRibCommand solid.rib
9 FusionWebCommand solid.web
10 EmbossCmd solid.emboss
11 FusionHoleCommand solid.hole
12 FusionThreadCommand solid.thread
13 PrimitiveBox solid.box
14 PrimitiveCylinder solid.cylinder
15 PrimitiveSphere solid.sphere
16 PrimitiveTorus solid.torus
17 PrimitiveCoil solid.coil
18 PrimitivePipe solid.pipe
19 StockModelCommand solid.bounding_solid
20 PatternRectangular solid.pattern.rectangular
21 PatternCircular solid.pattern.circular
22 PatternOnPath solid.pattern.path
23 MirrorCommand solid.mirror
24 FusionSurfaceThickenCommand surface.thicken
25 JointOrigin joint.origin
26 FusionPressPullCommand solid.press_pull
27 FusionFilletEdgesCommand solid.fillet
28 FusionChamferCommand solid.chamfer
29 FusionShellBodyCommand solid.shell
30 FusionDraftCommand solid.draft
31 ModifyScale solid.scale
32 FusionCombineCommand solid.combine
33 FusionOffsetFacesCommand solid.offset_face
34 FusionReplaceFaceCommand solid.replace_face
35 FusionSplitFaceCommand solid.split_face
36 FusionSplitBodyCommand solid.split_body
37 FusionMoveCommand solid.move
38 AlignCmd solid.align
39 FusionDeleteCommand timeline.delete
40 SoftDeleteCommand solid.remove
41 PhysicalMaterialCommand material.assign
42 AppearanceCommand appearance.assign
43 ChangeParameterCommand parameters.change
44 FusionComputeAllCommand timeline.compute_all
45 FusionImportCommandFromToolbar file.insert_step
46 FusionCreateNewComponentCommand component.create
47 JointAssembleCmdNew joint.create
48 JointAsBuiltCmd joint.as_built
49 RigidGroupCmd joint.rigid_group
50 FusionMoveJointsCommand joint.drive
51 FusionMotionRelationshipCommand joint.motion_link
52 FusionMotionStudyCommand motion.study
53 EnableContactSetsCmd contact.enable_sets
54 EnableAllContactCmd contact.enable_all
55 DisableAllContactCmd contact.disable_all
56 ContactSetCmd contact.create
57 FusionStartDesignConfigModeCmd config.configure
58 FusionShowDesignConfigPanelCmd config.table
59 FusionConfigurationRulesCmd config.rules
60 ConstructionPlaneOffsetFromPlaneCommand construct.plane.offset
61 ConstructionPlaneAtAngleCommand construct.plane.at_angle
62 ConstructionTangentPlaneCommand construct.plane.tangent
63 ConstructionMidPlaneCommand construct.plane.midplane
64 ConstructionPlaneFromTwoEdgesCommand construct.plane.two_edges
65 ConstructionPlaneFromThreePointsCommand construct.plane.three_points
66 ConstructionPlaneAlongPathCommand construct.plane.along_path
67 ConstructionAxisThroughCylinderCommand construct.axis.through_cylinder
68 ConstructionAxisNormalToFaceCommand construct.axis.normal_to_face
69 ConstructionAxisFromTwoPlanesCommand construct.axis.two_planes
70 ConstructionAxisThroughTwoPointsCommand construct.axis.two_points
71 ConstructionAxisThroughEdgeCommand construct.axis.edge
72 ConstructionPointFromPointCommand construct.point.vertex
73 ConstructionPointThroughTwoEdgesCommand construct.point.two_edges
74 ConstructionPointFromThreePlanesCommand construct.point.three_planes
75 ConstructionPointFromCircleOrSphereCommand construct.point.center
76 ConstructionPointAtEdgeAndPlaneCommand construct.point.edge_plane
77 ConstructionPointAlongPathCommand construct.point.along_path
78 MeasureCommand inspect.measure
79 InterferenceCheckCommand inspect.interference
80 FusionHalfSectionViewCommand inspect.section
81 FusionCurvatureCombAnalysisCommand inspect.curvature_comb
82 FusionCurvatureMapAnalysisCommand inspect.curvature_map
83 FusionDraftAnalysisCommand inspect.draft
84 FusionEnvironmentMapAnalysisCommand inspect.environment_map
85 FusionIsoCurveAnalysisCommand inspect.isocurve
86 FusionZebraAnalysisCommand inspect.zebra
87 FusionAccessibilityAnalysisCommand inspect.accessibility
88 FusionCenterOfMassCommand inspect.center_of_mass
89 FusionMinimumRadiusAnalysisCommand inspect.minimum_radius
90 FusionAddEditDecalCommand canvas.decal
91 FusionAddCanvasCommand canvas.insert
92 SketchImportSVG sketch.insert_svg
93 ImportDxfFileCommand sketch.insert_dxf
94 ParaMeshInsertAlignCommand file.insert_mesh
95 FusionFastenersCommand parts.fastener
96 SnapshotCmd component.capture_position
97 AsBuiltPositionsCmd component.revert_position
98 DrawPolyline sketch.line
99 SketchMidpointLine sketch.line.midpoint
100 SketchConstrainer sketch.constrainer
101 ShapeRectangleTwoPoint sketch.rectangle.two_point
102 ShapeRectangleThreePoint sketch.rectangle.three_point
103 ShapeRectangleCenter sketch.rectangle.center
104 CircleCenterRadius sketch.circle.center
105 CircleTwoPoint sketch.circle.two_point
106 CircleThreePoint sketch.circle.three_point
107 CircleTanTanRadius sketch.circle.two_tangent
108 CircleThreeTangent sketch.circle.three_tangent
109 ArcThreePoint sketch.arc.three_point
110 ArcCenterTwoPoint sketch.arc.center_point
111 ArcTangent sketch.arc.tangent
112 ShapePolygonCircumscribed sketch.polygon.circumscribed
113 ShapePolygonInscribed sketch.polygon.inscribed
114 ShapePolygonEdge sketch.polygon.edge
115 CircleElipse sketch.ellipse
116 ShapeSlotCenterToCenter sketch.slot.center_to_center
117 ShapeSlotOverall sketch.slot.overall
118 ShapeSlotCenterPoint sketch.slot.center_point
119 ShapeArcSlotThreePoint sketch.slot.arc_three_point
120 ShapeArcSlotCenterTwoPoint sketch.slot.arc_center
121 DrawSpline sketch.spline.fit_point
122 DrawCVMSpline3D sketch.spline.control_point
123 DrawCVMSpline5D sketch.spline.control_point_5
124 ConicCurveCmd sketch.conic
125 DrawPoint sketch.point
126 MTextCmd sketch.text
127 FitCurvesToSectionCommand sketch.fit_curves_to_section
128 MirrorSketchCommand sketch.mirror
129 CircularSketchPatternCommand sketch.pattern.circular
130 RectangularSketchPatternCommand sketch.pattern.rectangular
131 ProjectNewCmd sketch.project
132 IntersectCmd sketch.intersect
133 SpunProfileCmd sketch.spun_profile
134 Include3DGeometry sketch.include_3d
135 ProjectToSurface sketch.project_to_surface
136 IntersectionCurve sketch.intersection_curve
137 SketchIsoparametricCurve sketch.isoparametric_curve
138 SketchDimension sketch.dimension
139 FilletSketchCmd sketch.fillet
140 ChamferSketchEqualDistance sketch.chamfer.equal_distance
141 ChamferSketchDistanceAngle sketch.chamfer.distance_angle
142 ChamferSketchDistanceDistance sketch.chamfer.two_distance
143 BlendG1CurveSketchCmd sketch.blend_curve
144 Offset sketch.offset
145 TrimSketchCmd sketch.trim
146 ExtendSketchCmd sketch.extend
147 BreakSketchCmd sketch.break
148 SketchScaleCmd sketch.scale
149 SketchAutoConstraintAndDimCmd sketch.auto_constrain
150 SketchAutoConstraintAndDimFromDatumCmd sketch.auto_constrain_from_datum
151 ConstraintHorizontalVertical sketch.constraint.horizontal_vertical
152 ConstraintCoincident sketch.constraint.coincident
153 ConstraintTangent sketch.constraint.tangent
154 ConstraintEqual sketch.constraint.equal
155 ConstraintParallel sketch.constraint.parallel
156 ConstraintPerpendicular sketch.constraint.perpendicular
157 ConstraintFix sketch.constraint.fix
158 ConstraintMidPoint sketch.constraint.midpoint
159 ConstraintConcentric sketch.constraint.concentric
160 ConstraintCollinear sketch.constraint.collinear
161 ConstraintSymmetry sketch.constraint.symmetry
162 ConstraintSmooth sketch.constraint.curvature
163 SketchPolygonConstraintCmd sketch.constraint.polygon
164 SketchStop sketch.finish
165 SketchAutoConstrainAndFinish sketch.finish_auto_constrain
166 FusionSurfacePatchCommand surface.patch
167 FusionSurfaceTrimCommand surface.trim
168 FusionExtendCommand surface.extend
169 FusionSurfaceStitchCommand surface.stitch
170 FusionSheetMetalFlangeCommand sheet.flange
171 FusionSheetMetalHemFlangeCommand sheet.hem
172 SheetMetalFoldCmd sheet.fold
173 ConvertToSheetMetalCmd sheet.convert
174 FusionSheetMetalFlatPatternCmd sheet.flat_pattern
175 FusionSheetmetalUnfoldCommand sheet.unfold
176 FusionSheetMetalRulesCommand sheet.manage_rules
177 FusionAssignPlasticRuleCommand plastic.assign_rule
178 FusionManagePlasticRuleCommand plastic.manage_rules
179 FusionBossCommand plastic.boss
180 FusionSnapFitCommand plastic.snap_fit
181 FusionLipCommand plastic.lip
182 FusionRestCommand plastic.rest
183 ConstructionPlanePerpendicularToPlaneCommand construct.plane.perpendicular
184 ParaMeshConvertCommand mesh.convert
+104
View File
@@ -0,0 +1,104 @@
//! `cargo xtask features`: SolidCraft's own feature checklist, `docs/features.md`.
//!
//! The page lists every registered command by area (the first part of its dotted id), with its
//! label, id and toolbar place. It comes only from SolidCraft's command registry
//! (`solvecraft-cli commands`), so it can't drift from what the program does; it compares with
//! nothing else.
use std::path::Path;
use serde_json::Value;
/// Areas in the order the page lists them (id prefix, heading). Other prefixes follow,
/// alphabetically, under their prefix.
const AREAS: &[(&str, &str)] = &[
("sketch", "Sketch"),
("solid", "Solid modelling"),
("surface", "Surfaces"),
("construct", "Construction geometry"),
("sheet", "Sheet metal"),
("plastic", "Plastic parts"),
("component", "Components"),
("joint", "Assembly joints"),
("config", "Configurations"),
("timeline", "Timeline"),
("inspect", "Inspect"),
("file", "Files"),
("doc", "Documents"),
("edit", "Edit"),
];
pub fn run(root: &Path) -> Result<(), String> {
let out = crate::cargo()
.args(["run", "-q", "--release", "-p", "solvecraft-cli", "--", "commands"])
.output()
.map_err(|e| format!("solvecraft-cli: {e}"))?;
if !out.status.success() {
return Err(format!("solvecraft-cli commands failed:\n{}", String::from_utf8_lossy(&out.stderr)));
}
let cmds: Value = serde_json::from_slice(&out.stdout).map_err(|e| format!("commands JSON: {e}"))?;
let list = cmds.as_array().map(Vec::as_slice).unwrap_or_default();
let md = page(list);
std::fs::write(root.join("docs/features.md"), md).map_err(|e| format!("docs/features.md: {e}"))?;
println!("features: {} commands → docs/features.md", list.len());
Ok(())
}
fn area(id: &str) -> &str {
id.split('.').next().unwrap_or_default()
}
/// The checklist page for the registry's commands.
pub fn page(cmds: &[Value]) -> String {
let s = |c: &Value, k: &str| c[k].as_str().unwrap_or_default().to_string();
let mut prefixes: Vec<String> = cmds.iter().map(|c| area(&s(c, "id")).to_string()).collect();
prefixes.sort();
prefixes.dedup();
let mut order: Vec<(String, String)> =
AREAS.iter().filter(|(p, _)| prefixes.iter().any(|x| x == p)).map(|(p, h)| (p.to_string(), h.to_string())).collect();
for p in prefixes.iter().filter(|p| !AREAS.iter().any(|(a, _)| a == p)) {
order.push((p.clone(), format!("`{p}.*`")));
}
let mut md = format!(
"# Features\n\nSolidCraft's commands by area, generated by `cargo xtask features` from the command registry \
(`solvecraft-cli commands`). **{} commands.** Every command runs the same way from the toolbar, a script, \
`solvecraft-cli exec <id> '<json>'`, the control channel and MCP; parameters are in the \
[command reference](book/src/reference/commands.md) (`cargo xtask book`). Status and plans: [ROADMAP](../ROADMAP.md).\n",
cmds.len()
);
for (prefix, heading) in &order {
let in_area: Vec<&Value> = cmds.iter().filter(|c| area(&s(c, "id")) == prefix).collect();
md += &format!("\n## {heading} ({})\n\n| Command | Id | Toolbar |\n|---|---|---|\n", in_area.len());
for c in in_area {
let place = match (s(c, "tab"), s(c, "panel")) {
(t, _) if t.is_empty() => "–".to_string(),
(t, p) if p.is_empty() => t,
(t, p) => format!("{t} › {p}"),
};
let key = c["shortcut"].as_str().map(|k| format!(" ({k})")).unwrap_or_default();
md += &format!("| {}{key} | `{}` | {place} |\n", s(c, "label").replace('|', "/"), s(c, "id"));
}
}
md
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn page_groups_by_area() {
let cmds: Value = serde_json::from_str(
r#"[{"id":"solid.extrude","label":"Extrude","tab":"SOLID","panel":"CREATE","shortcut":"E"},
{"id":"sketch.line","label":"Line","tab":"SKETCH","panel":"CREATE"},
{"id":"view.fit","label":"Fit","tab":"","panel":""}]"#,
)
.unwrap();
let md = page(cmds.as_array().unwrap());
assert!(md.contains("**3 commands.**"));
let (sk, so, other) = (md.find("## Sketch (1)").unwrap(), md.find("## Solid modelling (1)").unwrap(), md.find("## `view.*` (1)").unwrap());
assert!(sk < so && so < other);
assert!(md.contains("| Extrude (E) | `solid.extrude` | SOLID › CREATE |"));
assert!(md.contains("| Fit | `view.fit` | – |"));
}
}
+3 -5
View File
@@ -5,9 +5,9 @@
mod assets;
mod book;
mod features;
mod layers;
mod licences;
mod parity;
mod test;
use std::path::{Path, PathBuf};
@@ -19,9 +19,7 @@ usage: cargo xtask <command>
commands:
layers enforce the crate dependency layering (plan/architecture.md §3)
assets check that every non-code asset file is attributed in ATTRIBUTION.md
parity [--refresh]
recompute Fusion command parity in docs/parity.md (--refresh rebuilds
xtask/data/fusion-catalog.tsv from plan/fusion/menu-tree.json)
features list SolidCraft's commands by area in docs/features.md (from the registry)
oracle [--cli BIN]
replay the reference tests (tests/oracle/*/recipe.json), compare with each
case's expected.json and write docs/oracle.md (--cli: a solvecraft-cli
@@ -46,7 +44,7 @@ fn main() -> ExitCode {
let result = match args.first().map(String::as_str) {
Some("layers") => cmd_layers(),
Some("assets") => assets::run(&root()),
Some("parity") => parity::run(&root(), rest.contains(&"--refresh")),
Some("features") => features::run(&root()),
Some("oracle") => cmd_oracle(rest.iter().position(|a| *a == "--cli").and_then(|i| rest.get(i + 1)).copied()),
Some("book") => book::run(&root(), !rest.contains(&"--no-build")),
Some("licences") => licences::run(&root(), rest.contains(&"--check")),
-220
View File
@@ -1,220 +0,0 @@
//! `cargo xtask parity`: Fusion Design-workspace command parity.
//!
//! `xtask/data/fusion-catalog.tsv` lists the toolbar commands of Fusion's Design workspace as
//! `tab \t panel \t command id \t command name` (ids and names only, nothing else). It is
//! derived from the local, uncommitted `plan/fusion/menu-tree.json` with `--refresh`. A catalog
//! entry is live when SolidCraft registers the command `xtask/data/fusion-ids.tsv` maps its id to
//! (SolidCraft uses its own ids).
use std::path::Path;
use std::process::Command;
use serde_json::Value;
/// Design workspace tabs in the catalog (menu-tree tab id, display name).
const TABS: &[(&str, &str)] = &[
("SolidTab", "SOLID"),
("SketchTab", "SKETCH"),
("SurfaceTab", "SURFACE"),
("ParaMeshOuterTab", "MESH"),
("FormTab", "FORM"),
("SheetMetalTab", "SHEET METAL"),
("PlasticTab", "PLASTIC"),
("AssemblyTab", "ASSEMBLY"),
("ManageTab", "MANAGE"),
("ToolsTab", "UTILITIES"),
];
/// The tabs the headline number counts (now: SOLID and SKETCH).
const HEADLINE: &[&str] = &["SOLID", "SKETCH"];
/// The owner's scope: these tabs (and the PLASTIC subset below) count toward the in-scope
/// figure; the rest of Fusion is deferred and listed separately.
const IN_SCOPE_TABS: &[&str] = &["SOLID", "SKETCH", "ASSEMBLY", "SHEET METAL"];
/// The PLASTIC commands in scope (enclosure features).
/// (SolidCraft ids, matched through the catalog-id map.)
const PLASTIC_SUBSET: &[&str] = &["plastic.boss", "solid.rib", "solid.web", "plastic.lip", "plastic.snap_fit"];
/// Catalog id → SolidCraft id.
fn id_map(s: &str) -> std::collections::HashMap<String, String> {
s.lines()
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
.filter_map(|l| l.split_once('\t').map(|(a, b)| (a.trim().to_string(), b.trim().to_string())))
.collect()
}
fn in_plastic_subset(e: &Entry, map: &std::collections::HashMap<String, String>) -> bool {
e.tab == "PLASTIC" && map.get(&e.id).is_some_and(|ours| PLASTIC_SUBSET.contains(&ours.as_str()))
}
#[derive(Clone, Debug)]
pub struct Entry {
pub tab: String,
pub panel: String,
pub id: String,
pub name: String,
}
fn clean(s: &str) -> String {
s.replace(['\t', '\n', '\r'], " ").trim().to_string()
}
fn walk(controls: &[Value], defs: &[Value], tab: &str, panel: &str, out: &mut Vec<Entry>) {
for c in controls {
match c["type"].as_str() {
Some("CommandControl") => {
let id = c["command"]["id"].as_str().or(c["id"].as_str()).unwrap_or("");
if id.is_empty() {
continue;
}
let name = c["command"]["name"]
.as_str()
.filter(|n| !n.is_empty())
.or_else(|| defs.iter().find(|d| d["id"].as_str() == Some(id)).and_then(|d| d["name"].as_str()))
.unwrap_or(id);
if !out.iter().any(|e| e.tab == tab && e.id == id) {
out.push(Entry { tab: tab.into(), panel: panel.into(), id: clean(id), name: clean(name) });
}
}
Some("DropDownControl") => walk(c["controls"].as_array().map(Vec::as_slice).unwrap_or(&[]), defs, tab, panel, out),
_ => {}
}
}
}
/// Catalog entries from Fusion's menu-tree JSON.
pub fn from_menu_tree(v: &Value) -> Vec<Entry> {
let defs = v["commandDefinitions"].as_array().map(Vec::as_slice).unwrap_or(&[]);
let mut out = Vec::new();
for (tid, tname) in TABS {
let tab = &v["tabs"][format!("DesignProductType/{tid}")];
for p in tab["panels"].as_array().into_iter().flatten() {
let panel = clean(p["name"].as_str().unwrap_or("")).to_uppercase();
walk(p["controls"].as_array().map(Vec::as_slice).unwrap_or(&[]), defs, tname, &panel, &mut out);
}
}
out
}
pub fn parse_catalog(s: &str) -> Vec<Entry> {
s.lines()
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
.filter_map(|l| {
let mut it = l.split('\t');
Some(Entry { tab: it.next()?.into(), panel: it.next()?.into(), id: it.next()?.into(), name: it.next().unwrap_or("").into() })
})
.collect()
}
pub fn run(root: &Path, refresh: bool) -> Result<(), String> {
let cat_path = root.join("xtask/data/fusion-catalog.tsv");
if refresh {
let mt = root.join("plan/fusion/menu-tree.json");
let s = std::fs::read_to_string(&mt).map_err(|e| format!("{}: {e}", mt.display()))?;
let v: Value = serde_json::from_str(&s).map_err(|e| format!("menu-tree.json: {e}"))?;
let entries = from_menu_tree(&v);
let mut out = String::from(
"# Fusion Design workspace toolbar: tab, panel, command id, command name (ids and names only).\n# Regenerate with `cargo xtask parity --refresh` (needs the local plan/fusion/menu-tree.json).\n",
);
for e in &entries {
out += &format!("{}\t{}\t{}\t{}\n", e.tab, e.panel, e.id, e.name);
}
std::fs::create_dir_all(root.join("xtask/data")).map_err(|e| e.to_string())?;
std::fs::write(&cat_path, out).map_err(|e| format!("{}: {e}", cat_path.display()))?;
println!("parity: catalog refreshed with {} entries", entries.len());
}
let catalog = parse_catalog(&std::fs::read_to_string(&cat_path).map_err(|e| format!("{}: {e}", cat_path.display()))?);
let out = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()))
.current_dir(root)
.args(["run", "-q", "--release", "-p", "solvecraft-cli", "--", "commands"])
.output()
.map_err(|e| format!("solvecraft-cli: {e}"))?;
let cmds: Value = serde_json::from_slice(&out.stdout).map_err(|e| format!("commands JSON: {e}"))?;
let ids: Vec<String> = cmds.as_array().into_iter().flatten().filter_map(|c| c["id"].as_str().map(str::to_string)).collect();
let map_path = root.join("xtask/data/fusion-ids.tsv");
let map = id_map(&std::fs::read_to_string(&map_path).map_err(|e| format!("{}: {e}", map_path.display()))?);
let live = |e: &Entry| map.get(&e.id).is_some_and(|ours| ids.contains(ours));
let in_scope = |e: &Entry| IN_SCOPE_TABS.contains(&e.tab.as_str()) || in_plastic_subset(e, &map);
let pct = |a: usize, b: usize| if b == 0 { 0.0 } else { 100.0 * a as f64 / b as f64 };
let head: Vec<&Entry> = catalog.iter().filter(|e| HEADLINE.contains(&e.tab.as_str())).collect();
let head_live = head.iter().filter(|e| live(e)).count();
let all_live = catalog.iter().filter(|e| live(e)).count();
let scope: Vec<&Entry> = catalog.iter().filter(|e| in_scope(e)).collect();
let scope_live = scope.iter().filter(|e| live(e)).count();
let mut md = format!(
"# Fusion command parity\n\nGenerated by `cargo xtask parity` from `xtask/data/fusion-catalog.tsv` (the toolbar commands of Fusion's Design workspace, ids and names only) and the live command registry (`solvecraft-cli commands`). A command counts as live when SolidCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth.\n\n**In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): {scope_live} / {} commands live ({:.0}%).** SOLID + SKETCH: {head_live} / {} ({:.0}%). Registered commands: {}.\n\nDeferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE beyond what solids need, PCB, render, animation, simulation, manufacture, drawings, MANAGE, UTILITIES and the rest of PLASTIC. All Design tabs together: {all_live} / {} ({:.0}%).\n\n| Tab | Panel | Live | Total | % |\n|---|---|---:|---:|---:|\n",
scope.len(),
pct(scope_live, scope.len()),
head.len(),
pct(head_live, head.len()),
ids.len(),
catalog.len(),
pct(all_live, catalog.len()),
);
let mut groups: Vec<(String, String, usize, usize)> = Vec::new();
for e in &catalog {
let l = usize::from(live(e));
match groups.iter_mut().find(|g| g.0 == e.tab && g.1 == e.panel) {
Some(g) => {
g.2 += l;
g.3 += 1;
}
None => groups.push((e.tab.clone(), e.panel.clone(), l, 1)),
}
}
for (t, p, l, n) in groups.iter().filter(|g| IN_SCOPE_TABS.contains(&g.0.as_str())) {
md += &format!("| {t} | {p} | {l} | {n} | {:.0}% |\n", pct(*l, *n));
}
let plastic: Vec<&Entry> = catalog.iter().filter(|e| in_plastic_subset(e, &map)).collect();
md += &format!(
"| PLASTIC | enclosure subset | {} | {} | {:.0}% |\n",
plastic.iter().filter(|e| live(e)).count(),
plastic.len(),
pct(plastic.iter().filter(|e| live(e)).count(), plastic.len())
);
md += "\n### Deferred tabs\n\n| Tab | Panel | Live | Total | % |\n|---|---|---:|---:|---:|\n";
for (t, p, l, n) in groups.iter().filter(|g| !IN_SCOPE_TABS.contains(&g.0.as_str())) {
md += &format!("| {t} | {p} | {l} | {n} | {:.0}% |\n", pct(*l, *n));
}
md += "\n## Live (SOLID, SKETCH)\n\n";
for e in head.iter().filter(|e| live(e)) {
md += &format!("- {} › {} › {}\n", e.tab, e.panel, e.name);
}
md += "\n## Not yet (SOLID, SKETCH)\n\n";
for e in head.iter().filter(|e| !live(e)) {
md += &format!("- {} › {} › {}\n", e.tab, e.panel, e.name);
}
std::fs::write(root.join("docs/parity.md"), md).map_err(|e| format!("docs/parity.md: {e}"))?;
println!(
"parity: in scope {scope_live}/{} ({:.0}%), SOLID+SKETCH {head_live}/{} ({:.0}%), all tabs {all_live}/{} → docs/parity.md",
scope.len(),
pct(scope_live, scope.len()),
head.len(),
pct(head_live, head.len()),
catalog.len()
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_from_menu_tree() {
let v: Value = serde_json::from_str(
r#"{"commandDefinitions":[{"id":"B","name":"Bee"}],"tabs":{"DesignProductType/SolidTab":{"panels":[{"name":"Create","controls":[
{"type":"CommandControl","id":"A","command":{"id":"A","name":"Aye"}},
{"type":"SeparatorControl","id":"s"},
{"type":"DropDownControl","id":"d","controls":[{"type":"CommandControl","id":"B","command":{"id":"B"}}]}]}]}}}"#,
)
.unwrap();
let e = from_menu_tree(&v);
assert_eq!(e.len(), 2);
assert_eq!((e[0].tab.as_str(), e[0].panel.as_str(), e[0].id.as_str(), e[0].name.as_str()), ("SOLID", "CREATE", "A", "Aye"));
assert_eq!(e[1].name, "Bee");
let tsv = "# c\nSOLID\tCREATE\tA\tAye\n";
assert_eq!(parse_catalog(tsv).len(), 1);
}
}