fix(control,mcp): background non-intrusive viewport capture and architectural MCP directives

This commit is contained in:
Karim Jerbi committed 2026-10-02 12:41:24 +01:00
1 parent a21aef2a11
commit b8def31145
3 files changed
+55 -15

No files matched your search

+4 -1
View File
@@ -1070,7 +1070,10 @@ impl OpenCADStudio {
.main_window
.ok_or_else(|| failure("gui_required", "Capture requires a GUI window"))?;
let path = string(req, "path")?.to_owned();
crate::sys::restore_window_if_minimized();
let was_minimized = crate::sys::restore_window_if_minimized();
if was_minimized {
std::thread::sleep(std::time::Duration::from_millis(60));
}
// Optional framing / selection adjustments prior to snapshot:
if let Some(handles) = req.get("highlight_handles").and_then(Value::as_array) {
+3 -3
View File
@@ -24,7 +24,7 @@ const MAX_REQUEST: usize = 1_048_576;
const MAX_RESPONSE: u64 = 16 * 1024 * 1024;
const CACHE_TTL_MS: u64 = 3_600_000;
const TASK_TTL_MS: u64 = 3_600_000;
pub(crate) const INSTRUCTIONS: &str = "Call ocs_sessions, then pass its session_id as ocs_session_id to ocs_read, ocs_execute and ocs_capture. Read capabilities to discover the complete CAD automation surface. Call record_schema to discover every record type, property path, JSON type, enum, unit, constraint and write rule before editing unfamiliar data. Use records to inspect every serializable entity, object, table, header and document record; filter with RFC 6901 JSON Pointer paths. Use set_properties for atomic, type-checked record edits and preserve document_id, revision and request_id. Use commands with parameters.name for a command manifest. Use batch when several steps are known, and request changed_entities when resulting geometry is needed. For interactive work, call start and follow state.command.accepts, options and input_example. To have the person at the screen pick entities for you, call user_select and keep polling until it completes; running means they are still picking. A run.cmd contains the command name followed by prompt answers separated by spaces; points use x,y or x,y,z. After a timeout, query the existing operation and never replay a mutation with a new request_id. waiting_input and running are not completion. Let OCS and its geometry kernel calculate geometry; use query near, contains_point and intersections for exact relationships. Verify important results with queries and a viewport capture, and save only to an explicit path. Viewports and captured snapshots are available as MCP resources under cad://session/{session_id}/viewport.png and cad://session/{session_id}/snapshot/{hash}.png; ocs_capture accepts delivery: 'resource' to avoid large inline base64 payloads, supplies standardized spatial grounding in _spatial, supports diff: true for streaming dirty visual regions, and provides multiscale DeepZoom pyramidal tiling via tile: {level, x, y} or cad://session/{session_id}/pyramid/manifest.json and cad://session/{session_id}/tile/{level}/{x}/{y}.png. Before delivery call audit with the intended target_format and target_version; use save_verified with an explicit absolute path to save, reopen, hash and compare the semantic manifest.";
pub(crate) const INSTRUCTIONS: &str = "Call ocs_sessions, then pass its session_id as ocs_session_id to ocs_read, ocs_execute and ocs_capture. Read capabilities to discover the complete CAD automation surface. Call record_schema to discover every record type, property path, JSON type, enum, unit, constraint and write rule before editing unfamiliar data. Use records to inspect every serializable entity, object, table, header and document record; filter with RFC 6901 JSON Pointer paths. Use set_properties for atomic, type-checked record edits and preserve document_id, revision and request_id. Use commands with parameters.name for a command manifest. Use batch when several steps are known, and request changed_entities when resulting geometry is needed. For interactive work, call start and follow state.command.accepts, options and input_example. To have the person at the screen pick entities for you, call user_select and keep polling until it completes; running means they are still picking. A run.cmd contains the command name followed by prompt answers separated by spaces; points use x,y or x,y,z. After a timeout, query the existing operation and never replay a mutation with a new request_id. waiting_input and running are not completion. Let OCS and its geometry kernel calculate geometry; use query near, contains_point and intersections for exact relationships. ARCHITECTURAL VECTORIZATION DIRECTIVE: When converting or vectorizing a floorplan from an image or sketch: 1. Attach reference images as Xref underlays via embed_image on layer _XREF and lock it. 2. NEVER draw loose lines or arcs for doors or windows; always query records (collection: 'block_records') and insert Block References (type: 'INSERT') on A-DOOR and A-GLAZ. If a block is missing, draft standard geometry at origin (0,0) and register it with block_define before inserting. 3. Categorize layers cleanly: A-WALL-EXTR, A-WALL-INTR, A-WALL-HATCH, A-DOOR, A-GLAZ, A-ANNO-TEXT, A-ANNO-DIMS. 4. Always verify drafted geometry using ocs_capture with annotate: true (Set-of-Marks entity IDs) and diff: true (visual dirty streaming). ocs_capture operates quietly in background and overlapped window states without stealing user focus. Viewports and captured snapshots are available as MCP resources under cad://session/{session_id}/viewport.png and cad://session/{session_id}/snapshot/{hash}.png; ocs_capture accepts delivery: 'resource' to avoid large inline base64 payloads, supplies standardized spatial grounding in _spatial, supports diff: true for streaming dirty visual regions, and provides multiscale DeepZoom pyramidal tiling via tile: {level, x, y} or cad://session/{session_id}/pyramid/manifest.json and cad://session/{session_id}/tile/{level}/{x}/{y}.png. Before delivery call audit with the intended target_format and target_version; use save_verified with an explicit absolute path to save, reopen, hash and compare the semantic manifest.";
const READ_OPS: &[&str] = &[
"state",
"hello",
@@ -1665,14 +1665,14 @@ pub(crate) fn tool_definitions() -> Value {
},
{
"name":"ocs_execute",
"description":"Execute semantic OCS actions. Use current state fields and a unique request_id. Use run for one complete command, batch to remove round trips, or start plus input for guided steps. user_select asks the person at the screen to pick entities and stays running until they press Enter (answer with entities) or Escape (cancelled). accepted, running and waiting_input are not completion.",
"description":"Execute semantic OCS actions, batch drafting, and mutations. Supports batch creation (entities_create), reference image underlays (embed_image), block definitions (block_define), and block reference insertions (INSERT). MANDATORY: For architectural drafting, all doors and windows must be inserted as CAD blocks (type: INSERT), never loose lines.",
"inputSchema":{"type":"object","properties":{"ocs_session_id":{"type":"string","minLength":1,"description":"Value of session_id returned by ocs_sessions."},"request":execute_request_schema(),"wait_seconds":{"type":"number","minimum":0,"maximum":60,"default":30,"description":"Total time to wait for completion before returning."},"response_detail":{"type":"string","enum":["compact","changed_entities","full"],"default":"compact","description":"compact returns only state needed for the next edit; changed_entities also returns current geometry for changed handles; full preserves the complete editor state."}},"required":["ocs_session_id","request"],"additionalProperties":false},
"outputSchema":execute_output_schema(),
"annotations":{"title":"Execute OCS action","readOnlyHint":false,"destructiveHint":true,"idempotentHint":true,"openWorldHint":false}
},
{
"name":"ocs_capture",
"description":"Capture the actual current OCS drawing viewport or window as a bounded PNG with optional camera framing, entity highlighting, spatial coordinates and Set-of-Marks annotations.",
"description":"Capture the actual current OCS drawing viewport or window as a bounded PNG with optional camera framing, entity highlighting, spatial coordinates and Set-of-Marks annotations. Works quietly in background and overlapped window states without stealing focus. MANDATORY FOR CAD ACCURACY: Use annotate: true to render Set-of-Marks entity IDs directly on the drawing for precision alignment feedback, and diff: true to stream visual changes between steps.",
"inputSchema":{
"type":"object",
"properties":{
+48 -11
View File
@@ -194,37 +194,74 @@ pub fn reveal_in_file_manager(path: &std::path::Path) -> Result<(), String> {
}
}
/// If the main application window is minimized, restore it so that viewport captures succeed.
/// If the main application window is minimized, restore it quietly in the background
/// without activating it or stealing user focus, so viewport captures succeed.
#[cfg(target_os = "windows")]
pub fn restore_window_if_minimized() {
pub fn restore_window_if_minimized() -> bool {
use windows_sys::Win32::Foundation::{HWND, LPARAM};
use windows_sys::Win32::System::Threading::GetCurrentProcessId;
use windows_sys::Win32::UI::WindowsAndMessaging::{
EnumWindows, GetWindowThreadProcessId, IsIconic, IsWindowVisible, SetForegroundWindow,
ShowWindow, SW_RESTORE,
EnumWindows, GetClassNameW, GetForegroundWindow, GetWindowThreadProcessId, IsIconic,
SetWindowPos, ShowWindow, HWND_BOTTOM, SWP_NOACTIVATE, SWP_NOMOVE,
SWP_NOSIZE, SW_SHOWNOACTIVATE,
};
type BOOL = i32;
struct Context {
pid: u32,
restored: bool,
}
unsafe {
let current_pid = GetCurrentProcessId();
let mut ctx = Context {
pid: current_pid,
restored: false,
};
unsafe extern "system" fn enum_wnd(hwnd: HWND, lparam: LPARAM) -> BOOL {
let target_pid = lparam as u32;
let ctx = &mut *(lparam as *mut Context);
let mut wnd_pid: u32 = 0;
GetWindowThreadProcessId(hwnd, &mut wnd_pid);
if wnd_pid == target_pid && IsWindowVisible(hwnd) != 0 {
if IsIconic(hwnd) != 0 {
ShowWindow(hwnd, SW_RESTORE);
SetForegroundWindow(hwnd);
if wnd_pid == ctx.pid {
let mut class_buf = [0u16; 64];
let len = GetClassNameW(hwnd, class_buf.as_mut_ptr(), 64);
let class_str = String::from_utf16_lossy(&class_buf[..len as usize]);
// CRITICAL FIX: Only touch the main application window ("Window Class").
// NEVER touch "Winit Thread Event Target" or hidden helper windows!
if class_str.starts_with("Window Class") {
if IsIconic(hwnd) != 0 {
let fg = GetForegroundWindow();
// Restore in background without activation (never steal user focus)
ShowWindow(hwnd, SW_SHOWNOACTIVATE);
// Ensure it stays behind active user applications
if fg != hwnd && !fg.is_null() {
SetWindowPos(
hwnd,
HWND_BOTTOM,
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
);
}
ctx.restored = true;
}
}
}
1
}
EnumWindows(Some(enum_wnd), current_pid as LPARAM);
EnumWindows(Some(enum_wnd), (&mut ctx as *mut Context) as LPARAM);
ctx.restored
}
}
#[cfg(not(target_os = "windows"))]
pub fn restore_window_if_minimized() {}
pub fn restore_window_if_minimized() -> bool {
false
}
/// Copy the rendered web canvas during the frame callback, before the browser
/// clears its drawing buffer. Canvas readback avoids Iced's synchronous GPU map.