MCP demo v2: examples/demo/enclosure_ui_mcp.py builds the enclosure through the UI like a person (toolbar clicks, real dialogs, sketches on the model's faces, Look At and back out, orbits between features) with the parameters and their dimensions over MCP; record_v2.sh and overlay.py record it with captions (UI click / MCP call) and a drawn pointer with click ripples. Toolbar buttons, drop-down items and panel labels publish handles (toolbar:<id>, panel:<tab>:<panel>); control method ui.handles lists them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bherbruckandClaude Opus 5.5 committed 2026-10-09 14:22:14 +00:00
1 parent 78dd776152
commit 1d11898a2c
9 files changed
+444

No files matched your search

+5
View File
@@ -252,6 +252,11 @@ pub fn handle(app: &mut SolveApp, ctx: &egui::Context, req: &ControlRequest) ->
_ => err("no dialog input with that index"),
}
}
"ui.handles" => {
// The handles drawn last frame (toolbar buttons `toolbar:<id>`, panels, browser rows…).
let list = crate::scenario::handles(s("prefix").unwrap_or(""));
ok(json!(list.into_iter().map(|(n, p)| json!({"name": n, "x": p.x, "y": p.y})).collect::<Vec<_>>()))
}
"ui.at" => {
// A scenario point: {world: [x,y,z]} | {sketch: [x,y]} | {plane: "XY"} (the middle of an
// origin plane's square) → screen point.
+5
View File
@@ -62,6 +62,11 @@ pub fn handle_at(name: &str) -> Option<egui::Pos2> {
HANDLES.with(|h| h.borrow().iter().rfind(|(n, _)| n == name).map(|x| x.1))
}
/// Every handle drawn last frame whose name starts with `prefix`.
pub fn handles(prefix: &str) -> Vec<(String, egui::Pos2)> {
HANDLES.with(|h| h.borrow().iter().filter(|(n, _)| n.starts_with(prefix)).cloned().collect())
}
/// A painted part's rect this frame, by name ("toolbar", "workspace_switcher").
pub fn publish_rect(name: &str, r: egui::Rect) {
RECTS.with(|h| h.borrow_mut().push((name.to_string(), r)));
+3
View File
@@ -297,6 +297,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
let br = Rect::from_min_size(pos2(px + 4.0 + i as f32 * 40.0, top + 2.0), vec2(38.0, 38.0));
let info = c.info(&app.session);
let resp = ui.interact(br, ui.id().with(("cmd", c.id)), Sense::click());
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), br.center());
if resp.hovered() && info.enabled {
painter.rect_filled(br, 4.0, t.hover);
}
@@ -324,6 +325,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
let lr = Rect::from_min_size(pos2(px, top + 42.0), vec2(width, 18.0));
let label = panel.to_string();
let lresp = ui.interact(lr, ui.id().with(("panel", panel)), Sense::click());
crate::scenario::publish_handle(&format!("panel:{tab}:{panel}"), lr.center());
if lresp.hovered() && enabled_panel {
painter.rect_filled(lr, 3.0, t.hover);
}
@@ -360,6 +362,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
let (ir, _) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover());
icons::paint(ui.painter(), ir, c.icon, t.icon, t.icon_fill, t.accent);
let b = ui.add_enabled(info.enabled, egui::Button::new(c.label).frame(false).shortcut_text(c.shortcut.unwrap_or("")));
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), b.rect.center());
if b.clicked() {
let id = c.id;
app.start(id);