Skip to content

MCP: add viewer.* namespace (camera, screenshots, input events)#5980

Merged
Grantim merged 6 commits into
masterfrom
viewer-mcp-camera-screenshots
Apr 24, 2026
Merged

MCP: add viewer.* namespace (camera, screenshots, input events)#5980
Grantim merged 6 commits into
masterfrom
viewer-mcp-camera-screenshots

Conversation

@Grantim
Copy link
Copy Markdown
Contributor

@Grantim Grantim commented Apr 24, 2026

Summary

Adds the viewer.* MCP namespace: camera control, screenshots, and input-event injection. Extends issue MeshInspector/MeshInspectorCode#7205.

New tools:

  • viewer.fit — frame objects/points/whole scene (uses preciseFit*ToScreenBorder, preserves camera angle)
  • viewer.setupCamera — set camera forward/up direction; auto-orthogonalizes upDir via Gram-Schmidt
  • viewer.captureScreenshot — 3D viewport (sync) or whole window (async, blocks on promise/future); dual output mode (server-side filePath or inline base64 bytes)
  • viewer.sendMouseEvent — unified mouse injection (down/up/click/move/scroll)
  • viewer.sendKeyboardEvent — unified keyboard injection (down/up/press/repeat); string key names (\"a\", \"Escape\", \"F5\", \"ArrowUp\"); modifiers as string array

Also extracts resolveId/idOf from MRSceneMcp.cpp into a shared MRMcpCommon.h/.cpp now that a second caller (viewer.fit) needs them, and renames the existing MRViewerMcp.cpp (which hosts ui.*) to MRUiMcp.cpp so the new viewer-namespace TU can take the clearer name.

Commits

  1. Extract resolveId/idOf into MRMcpCommon.h/.cpp; rename MRViewerMcp.cppMRUiMcp.cpp.
  2. viewer.fit + viewer.setupCamera.
  3. viewer.captureScreenshot.
  4. Switch both fit paths to preciseFit*ToScreenBorder; drop the snapView param (always false now).
  5. viewer.sendMouseEvent + viewer.sendKeyboardEvent — all events go through Viewer::emplaceEvent (same pattern as test_ui/scenarios/scenario_helpers.py), with skipFramesAfterInput between steps.

Test plan

All 6 tools verified live via MCP:

  • viewer.captureScreenshot file-mode → PNG on disk with 89 50 4E 47 magic
  • viewer.captureScreenshot bytes-mode at custom resolution → base64 PNG
  • viewer.captureScreenshot with includeUi: true → full window including ribbon/scene tree/panels (async promise path)
  • viewer.setupCamera top/left/isometric (forwardDir=[-1,-1,-1]) directions — camera angle preserved, navigation cube matches
  • viewer.setupCamera rejects zero forwardDir and upDir parallel to forwardDir with typed errors
  • viewer.fit whole-scene, per-object, per-point, mixed — all frame correctly; unknown object id → typed error
  • viewer.sendMouseEvent type=scroll → view zoom changed; error paths (unknown type, missing button for down, missing scrollDelta for scroll, unknown modifier)
  • viewer.sendKeyboardEvent Ctrl+Z → undoes last scene mutation; unknown key name → typed error

🤖 Generated with Claude Code

Grantim added 5 commits April 24, 2026 17:35
… MRUiMcp.cpp

Prep for viewer.* namespace: the existing MRViewerMcp.cpp hosts ui.* tools,
rename to MRUiMcp.cpp so the new viewer.* TU can take the old name.
resolveId is about to get a second caller (viewer.fit), so lift it and idOf
to a shared MRMcpCommon.h/.cpp.
- viewer.fit takes optional objectIds and/or 3D points and unions their
  boxes; falls back to whole-scene fitData when neither is given.
- viewer.setupCamera takes mandatory forwardDir + upDir, orthogonalizes
  upDir against forwardDir, and always refits after reorientation.
One tool, two modes: default captures just the 3D viewport via the sync
captureSceneScreenShot path; `includeUi: true` captures the whole window
via captureUIScreenShot, blocking on a promise until the async callback
fires on the next frame. Dual output: `filePath` -> write server-side and
return the path; omitted -> return inline base64 PNG bytes.
preciseFitDataToScreenBorder / preciseFitBoxToScreenBorder give exact
framing to the viewport edges. Both default to snapView=false, so the
camera angle is preserved — setupCamera's direction is no longer silently
snapped to canonical. Drops the snapView tool param entirely.
Unified per-domain event injection. `sendMouseEvent` dispatches on
`type` (down/up/click/move/scroll); `click` = down+up with an optional
pre-hover move. `sendKeyboardEvent` takes a string key — single printable
char or a named key like `Escape` / `ArrowUp` / `F5`. Modifiers are string
arrays (`["ctrl","shift"]`). All events go through Viewer::emplaceEvent
(the same path Python UI tests use) with skipFramesAfterInput between
steps to give plugins a frame to react.
…creenshots

# Conflicts:
#	source/MRViewer/MRViewerMcp.cpp
@Grantim Grantim merged commit b6386b1 into master Apr 24, 2026
35 checks passed
@Grantim Grantim deleted the viewer-mcp-camera-screenshots branch April 24, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants