Command ids are SolveCraft's own: 173 ids that were another product's internal command ids become dotted, lower-case ids (<area>.<thing>[.<variant>]: solid.extrude, sketch.rectangle.two_point, sheet.flange, joint.drive, ...), in the registry, toolbar, dialogs, shortcuts, palette, control channel, MCP, examples, scenarios and docs

The old ids live only in crates/engine/src/legacy_ids.rs (the legacy migration table): find_command maps them, so old scripts, recipes and control calls keep working, and saved shortcuts, recent and pinned commands are migrated on load (designs store no command ids). Parity maps the observed catalog ids to ours through xtask/data/fusion-ids.tsv. The agent rule now reads: our own dotted id, never another product's. Cargo.toml's repository points at github.com/bherbruck/solvecraft.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-09 12:17:23 +00:00
1 parent 740360d7c3
commit d10312b7fd
158 files changed
+3611 -3205

No files matched your search

+65 -65
View File
@@ -38,33 +38,33 @@ pub struct Tool {
impl Tool {
pub fn for_command(id: &str) -> Option<Tool> {
let (cmd, kind): (&'static str, Kind) = match id {
"DrawPolyline" => ("DrawPolyline", Kind::Draw(0)),
"ShapeRectangleTwoPoint" => ("ShapeRectangleTwoPoint", Kind::Draw(2)),
"ShapeRectangleCenter" => ("ShapeRectangleCenter", Kind::Draw(2)),
"ShapeRectangleThreePoint" => ("ShapeRectangleThreePoint", Kind::Draw(3)),
"CircleCenterRadius" => ("CircleCenterRadius", Kind::Draw(2)),
"CircleTwoPoint" => ("CircleTwoPoint", Kind::Draw(2)),
"CircleThreePoint" => ("CircleThreePoint", Kind::Draw(3)),
"ArcThreePoint" => ("ArcThreePoint", Kind::Draw(3)),
"ArcCenterTwoPoint" => ("ArcCenterTwoPoint", Kind::Draw(3)),
"ShapePolygonInscribed" => ("ShapePolygonInscribed", Kind::Draw(2)),
"ShapePolygonCircumscribed" => ("ShapePolygonCircumscribed", Kind::Draw(2)),
"ShapePolygonEdge" => ("ShapePolygonEdge", Kind::Draw(2)),
"ShapeSlotCenterToCenter" => ("ShapeSlotCenterToCenter", Kind::Draw(3)),
"ShapeSlotOverall" => ("ShapeSlotOverall", Kind::Draw(3)),
"DrawPoint" => ("DrawPoint", Kind::Draw(1)),
"SketchDimension" => ("SketchDimension", Kind::Dimension),
"ConstraintHorizontalVertical" => ("ConstraintHorizontalVertical", Kind::Pick(1)),
"ConstraintFix" => ("ConstraintFix", Kind::Pick(1)),
"ConstraintCoincident" => ("ConstraintCoincident", Kind::Pick(2)),
"ConstraintTangent" => ("ConstraintTangent", Kind::Pick(2)),
"ConstraintEqual" => ("ConstraintEqual", Kind::Pick(2)),
"ConstraintParallel" => ("ConstraintParallel", Kind::Pick(2)),
"ConstraintPerpendicular" => ("ConstraintPerpendicular", Kind::Pick(2)),
"ConstraintConcentric" => ("ConstraintConcentric", Kind::Pick(2)),
"ConstraintCollinear" => ("ConstraintCollinear", Kind::Pick(2)),
"ConstraintMidPoint" => ("ConstraintMidPoint", Kind::Pick(2)),
"ConstraintSymmetry" => ("ConstraintSymmetry", Kind::Pick(3)),
"sketch.line" => ("sketch.line", Kind::Draw(0)),
"sketch.rectangle.two_point" => ("sketch.rectangle.two_point", Kind::Draw(2)),
"sketch.rectangle.center" => ("sketch.rectangle.center", Kind::Draw(2)),
"sketch.rectangle.three_point" => ("sketch.rectangle.three_point", Kind::Draw(3)),
"sketch.circle.center" => ("sketch.circle.center", Kind::Draw(2)),
"sketch.circle.two_point" => ("sketch.circle.two_point", Kind::Draw(2)),
"sketch.circle.three_point" => ("sketch.circle.three_point", Kind::Draw(3)),
"sketch.arc.three_point" => ("sketch.arc.three_point", Kind::Draw(3)),
"sketch.arc.center_point" => ("sketch.arc.center_point", Kind::Draw(3)),
"sketch.polygon.inscribed" => ("sketch.polygon.inscribed", Kind::Draw(2)),
"sketch.polygon.circumscribed" => ("sketch.polygon.circumscribed", Kind::Draw(2)),
"sketch.polygon.edge" => ("sketch.polygon.edge", Kind::Draw(2)),
"sketch.slot.center_to_center" => ("sketch.slot.center_to_center", Kind::Draw(3)),
"sketch.slot.overall" => ("sketch.slot.overall", Kind::Draw(3)),
"sketch.point" => ("sketch.point", Kind::Draw(1)),
"sketch.dimension" => ("sketch.dimension", Kind::Dimension),
"sketch.constraint.horizontal_vertical" => ("sketch.constraint.horizontal_vertical", Kind::Pick(1)),
"sketch.constraint.fix" => ("sketch.constraint.fix", Kind::Pick(1)),
"sketch.constraint.coincident" => ("sketch.constraint.coincident", Kind::Pick(2)),
"sketch.constraint.tangent" => ("sketch.constraint.tangent", Kind::Pick(2)),
"sketch.constraint.equal" => ("sketch.constraint.equal", Kind::Pick(2)),
"sketch.constraint.parallel" => ("sketch.constraint.parallel", Kind::Pick(2)),
"sketch.constraint.perpendicular" => ("sketch.constraint.perpendicular", Kind::Pick(2)),
"sketch.constraint.concentric" => ("sketch.constraint.concentric", Kind::Pick(2)),
"sketch.constraint.collinear" => ("sketch.constraint.collinear", Kind::Pick(2)),
"sketch.constraint.midpoint" => ("sketch.constraint.midpoint", Kind::Pick(2)),
"sketch.constraint.symmetry" => ("sketch.constraint.symmetry", Kind::Pick(3)),
_ => return crate::sketch_tools::tool_for(id),
};
Some(Tool { cmd, kind, pts: Vec::new(), hover: None, picks: Vec::new(), dims: Vec::new(), dims_stage: usize::MAX, dims_focus: false })
@@ -73,16 +73,16 @@ impl Tool {
pub fn hint(id: &str) -> String {
match id {
"DrawPolyline" => "Line: click points; right-click or Esc to finish".into(),
"ShapeRectangleTwoPoint" => "Rectangle: click two opposite corners".into(),
"ShapeRectangleCenter" => "Center rectangle: click the centre, then a corner".into(),
"ShapeRectangleThreePoint" => "3-point rectangle: click two points of one edge, then the width".into(),
"CircleCenterRadius" => "Circle: click the centre, then a point on the circle".into(),
"ArcThreePoint" => "Arc: click start, end, then a point on the arc".into(),
"ArcCenterTwoPoint" => "Arc: click centre, start, then end (counter-clockwise)".into(),
"SketchDimension" => "Dimension: pick a line, circle or arc (or two points/lines)".into(),
"ShapeSlotCenterToCenter" | "ShapeSlotOverall" => "Slot: click both ends, then the width".into(),
_ if id.starts_with("Constraint") => "Constraint: pick the sketch entities".into(),
"sketch.line" => "Line: click points; right-click or Esc to finish".into(),
"sketch.rectangle.two_point" => "Rectangle: click two opposite corners".into(),
"sketch.rectangle.center" => "Center rectangle: click the centre, then a corner".into(),
"sketch.rectangle.three_point" => "3-point rectangle: click two points of one edge, then the width".into(),
"sketch.circle.center" => "Circle: click the centre, then a point on the circle".into(),
"sketch.arc.three_point" => "Arc: click start, end, then a point on the arc".into(),
"sketch.arc.center_point" => "Arc: click centre, start, then end (counter-clockwise)".into(),
"sketch.dimension" => "Dimension: pick a line, circle or arc (or two points/lines)".into(),
"sketch.slot.center_to_center" | "sketch.slot.overall" => "Slot: click both ends, then the width".into(),
_ if id.starts_with("sketch.constraint.") => "Constraint: pick the sketch entities".into(),
_ => crate::sketch_tools::hint(id).unwrap_or_else(|| "Click in the sketch".into()),
}
}
@@ -117,7 +117,7 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2) {
Kind::Draw(_) => {
if let Some(mut p) = sketch_point_at(app, proj, pos) {
// Typed values win over the cursor; lines snap to horizontal/vertical.
if tool.dims.iter().any(|b| b.locked) || (tool.cmd == "DrawPolyline" && p.1.is_none()) {
if tool.dims.iter().any(|b| b.locked) || (tool.cmd == "sketch.line" && p.1.is_none()) {
p = (crate::sketch_dims::effective(app, &tool, p.0), None);
}
app.tool = Some(tool);
@@ -159,7 +159,7 @@ pub fn on_click(app: &mut SolveApp, proj: &Proj, pos: Pos2) {
let single_line = tool.picks.len() == 1 && is_line == Some(true);
if ready || single_line {
// A single line gets its length right away; a second pick (line or point) replaces it.
if let Ok(v) = app.run("SketchDimension", json!({"entities": tool.picks}))
if let Ok(v) = app.run("sketch.dimension", json!({"entities": tool.picks}))
&& let Some(p) = v["param"].as_str()
{
// The value is edited in place, selected so typing replaces it.
@@ -182,10 +182,10 @@ pub fn place(app: &mut SolveApp, p: (Vec2, Option<String>)) {
return;
};
tool.pts.push(p);
if tool.cmd == "DrawPolyline" {
if tool.cmd == "sketch.line" {
if tool.pts.len() >= 2 {
let (a, b) = (tool.pts[tool.pts.len() - 2].clone(), tool.pts[tool.pts.len() - 1].clone());
if let Ok(v) = app.run("DrawPolyline", json!({"points": [arg(&a), arg(&b)], "infer": true})) {
if let Ok(v) = app.run("sketch.line", json!({"points": [arg(&a), arg(&b)], "infer": true})) {
let curves: Vec<String> = v["curves"].as_array().into_iter().flatten().filter_map(|c| c.as_str().map(str::to_string)).collect();
dimension(app, &tool, &curves);
// Chain: the next segment starts at this one's end point.
@@ -207,7 +207,7 @@ pub fn place(app: &mut SolveApp, p: (Vec2, Option<String>)) {
/// Typed values become dimension constraints on the new curves.
fn dimension(app: &mut SolveApp, t: &Tool, curves: &[String]) {
for (ents, ty, value) in crate::sketch_dims::constraints(t, curves) {
let _ = app.run("SketchDimension", crate::sketch_dims::dimension_params(&ents, ty, &value));
let _ = app.run("sketch.dimension", crate::sketch_dims::dimension_params(&ents, ty, &value));
}
}
@@ -216,25 +216,25 @@ fn run_shape(app: &mut SolveApp, t: &Tool) -> Vec<String> {
let d = |a: usize, b: usize| p[a].0.dist(p[b].0);
let sides = crate::sketch_dims::locked_value(app, t, "Sides").map_or(6, |v| v.round().clamp(3.0, 200.0) as usize);
let params = match t.cmd {
"ShapeRectangleTwoPoint" => json!({"p0": xy(&p[0]), "p1": xy(&p[1])}),
"ShapeRectangleCenter" => json!({"center": xy(&p[0]), "corner": xy(&p[1])}),
"ShapeRectangleThreePoint" => json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "p2": xy(&p[2])}),
"CircleCenterRadius" => json!({"center": arg(&p[0]), "radius": d(0, 1)}),
"CircleTwoPoint" => json!({"p0": xy(&p[0]), "p1": xy(&p[1])}),
"CircleThreePoint" => json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "p2": xy(&p[2])}),
"ArcThreePoint" => json!({"start": arg(&p[0]), "end": arg(&p[1]), "through": xy(&p[2])}),
"ArcCenterTwoPoint" => json!({"center": arg(&p[0]), "start": arg(&p[1]), "end": xy(&p[2])}),
"ShapePolygonInscribed" | "ShapePolygonCircumscribed" => {
"sketch.rectangle.two_point" => json!({"p0": xy(&p[0]), "p1": xy(&p[1])}),
"sketch.rectangle.center" => json!({"center": xy(&p[0]), "corner": xy(&p[1])}),
"sketch.rectangle.three_point" => json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "p2": xy(&p[2])}),
"sketch.circle.center" => json!({"center": arg(&p[0]), "radius": d(0, 1)}),
"sketch.circle.two_point" => json!({"p0": xy(&p[0]), "p1": xy(&p[1])}),
"sketch.circle.three_point" => json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "p2": xy(&p[2])}),
"sketch.arc.three_point" => json!({"start": arg(&p[0]), "end": arg(&p[1]), "through": xy(&p[2])}),
"sketch.arc.center_point" => json!({"center": arg(&p[0]), "start": arg(&p[1]), "end": xy(&p[2])}),
"sketch.polygon.inscribed" | "sketch.polygon.circumscribed" => {
let a = (p[1].0 - p[0].0).angle().to_degrees();
json!({"center": xy(&p[0]), "radius": d(0, 1), "sides": sides, "angle": a})
}
"ShapePolygonEdge" => json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "sides": sides}),
"ShapeSlotCenterToCenter" | "ShapeSlotOverall" => {
"sketch.polygon.edge" => json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "sides": sides}),
"sketch.slot.center_to_center" | "sketch.slot.overall" => {
let dir = (p[1].0 - p[0].0).normalized().unwrap_or(Vec2::X);
let w = 2.0 * dir.cross(p[2].0 - p[0].0).abs();
json!({"p0": xy(&p[0]), "p1": xy(&p[1]), "width": w.max(1e-3)})
}
"DrawPoint" => json!({"point": xy(&p[0])}),
"sketch.point" => json!({"point": xy(&p[0])}),
_ => return Vec::new(),
};
let curves: Vec<String> = app
@@ -270,18 +270,18 @@ fn tie_snaps(app: &mut SolveApp, t: &Tool, curves: &[String]) {
}
}
for (a, b) in ties {
let _ = app.run("ConstraintCoincident", json!({"a": a, "b": b}));
let _ = app.run("sketch.constraint.coincident", json!({"a": a, "b": b}));
}
}
fn run_pick(app: &mut SolveApp, t: &Tool) {
let k = &t.picks;
let params = match t.cmd {
"ConstraintHorizontalVertical" => json!({"line": k[0]}),
"ConstraintFix" => json!({"entity": k[0]}),
"ConstraintCoincident" => json!({"a": k[0], "b": k[1]}),
"ConstraintMidPoint" => json!({"point": k[0], "line": k[1]}),
"ConstraintSymmetry" => json!({"a": k[0], "b": k[1], "line": k[2]}),
"sketch.constraint.horizontal_vertical" => json!({"line": k[0]}),
"sketch.constraint.fix" => json!({"entity": k[0]}),
"sketch.constraint.coincident" => json!({"a": k[0], "b": k[1]}),
"sketch.constraint.midpoint" => json!({"point": k[0], "line": k[1]}),
"sketch.constraint.symmetry" => json!({"a": k[0], "b": k[1], "line": k[2]}),
_ => json!({"a": k[0], "b": k[1]}),
};
let _ = app.run(t.cmd, params);
@@ -347,7 +347,7 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
painter.add(Shape::line(v, stroke));
};
match (t.cmd, pts.as_slice()) {
("DrawPolyline", [.., last]) => {
("sketch.line", [.., last]) => {
line(*last, h);
// Horizontal/vertical inference marker.
if let Some(horizontal) = crate::sketch_dims::axis_aligned(*last, h)
@@ -363,14 +363,14 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
);
}
}
("ShapeRectangleTwoPoint", [a]) => {
("sketch.rectangle.two_point", [a]) => {
let (b, c) = (Vec2::new(h.x, a.y), Vec2::new(a.x, h.y));
line(*a, b);
line(b, h);
line(h, c);
line(c, *a);
}
("ShapeRectangleCenter", [c]) => {
("sketch.rectangle.center", [c]) => {
let d = h - *c;
let k =
[Vec2::new(c.x - d.x, c.y - d.y), Vec2::new(c.x + d.x, c.y - d.y), Vec2::new(c.x + d.x, c.y + d.y), Vec2::new(c.x - d.x, c.y + d.y)];
@@ -378,8 +378,8 @@ pub fn preview(app: &SolveApp, t: &Tool, painter: &egui::Painter, proj: &Proj) {
line(k[i], k[(i + 1) % 4]);
}
}
("CircleCenterRadius" | "ShapePolygonInscribed" | "ShapePolygonCircumscribed", [c]) => circle(*c, c.dist(h)),
("CircleTwoPoint", [a]) => circle((*a + h) * 0.5, a.dist(h) * 0.5),
("sketch.circle.center" | "sketch.polygon.inscribed" | "sketch.polygon.circumscribed", [c]) => circle(*c, c.dist(h)),
("sketch.circle.two_point", [a]) => circle((*a + h) * 0.5, a.dist(h) * 0.5),
(_, [a]) => line(*a, h),
(_, [a, b]) => {
line(*a, *b);