diff --git a/addon.py b/addon.py index c07c248..4f9d82e 100644 --- a/addon.py +++ b/addon.py @@ -197,6 +197,7 @@ class BlenderMCPServer: handlers = { "get_scene_info": self.get_scene_info, "get_object_info": self.get_object_info, + "get_viewport_screenshot": self.get_viewport_screenshot, "execute_code": self.execute_code, "get_polyhaven_status": self.get_polyhaven_status, "get_hyper3d_status": self.get_hyper3d_status, @@ -330,6 +331,63 @@ class BlenderMCPServer: return obj_info + def get_viewport_screenshot(self, max_size=800, filepath=None, format="png"): + """ + Capture a screenshot of the current 3D viewport and save it to the specified path. + + Parameters: + - max_size: Maximum size in pixels for the largest dimension of the image + - filepath: Path where to save the screenshot file + - format: Image format (png, jpg, etc.) + + Returns success/error status + """ + try: + if not filepath: + return {"error": "No filepath provided"} + + # Find the active 3D viewport + area = None + for a in bpy.context.screen.areas: + if a.type == 'VIEW_3D': + area = a + break + + if not area: + return {"error": "No 3D viewport found"} + + # Take screenshot with proper context override + with bpy.context.temp_override(area=area): + bpy.ops.screen.screenshot_area(filepath=filepath) + + # Load and resize if needed + img = bpy.data.images.load(filepath) + width, height = img.size + + if max(width, height) > max_size: + scale = max_size / max(width, height) + new_width = int(width * scale) + new_height = int(height * scale) + img.scale(new_width, new_height) + + # Set format and save + img.file_format = format.upper() + img.save() + width, height = new_width, new_height + + # Cleanup Blender image data + bpy.data.images.remove(img) + + return { + "success": True, + "width": width, + "height": height, + "filepath": filepath + } + + except Exception as e: + return {"error": str(e)} + def execute_code(self, code): """Execute arbitrary Blender Python code""" # This is powerful but potentially dangerous - use with caution diff --git a/src/blender_mcp/server.py b/src/blender_mcp/server.py index 6614e10..9464d96 100644 --- a/src/blender_mcp/server.py +++ b/src/blender_mcp/server.py @@ -4,6 +4,7 @@ import socket import json import asyncio import logging +import tempfile from dataclasses import dataclass from contextlib import asynccontextmanager from typing import AsyncIterator, Dict, Any, List @@ -266,6 +267,47 @@ def get_object_info(ctx: Context, object_name: str) -> str: logger.error(f"Error getting object info from Blender: {str(e)}") return f"Error getting object info: {str(e)}" +@mcp.tool() +def get_viewport_screenshot(ctx: Context, max_size: int = 800) -> Image: + """ + Capture a screenshot of the current Blender 3D viewport. + + Parameters: + - max_size: Maximum size in pixels for the largest dimension (default: 800) + + Returns the screenshot as an Image. + """ + try: + blender = get_blender_connection() + + # Create temp file path + temp_dir = tempfile.gettempdir() + temp_path = os.path.join(temp_dir, f"blender_screenshot_{os.getpid()}.png") + + result = blender.send_command("get_viewport_screenshot", { + "max_size": max_size, + "filepath": temp_path, + "format": "png" + }) + + if "error" in result: + raise Exception(result["error"]) + + if not os.path.exists(temp_path): + raise Exception("Screenshot file was not created") + + # Read the file + with open(temp_path, 'rb') as f: + image_bytes = f.read() + + # Delete the temp file + os.remove(temp_path) + + return Image(data=image_bytes, format="png") + + except Exception as e: + logger.error(f"Error capturing screenshot: {str(e)}") + raise Exception(f"Screenshot failed: {str(e)}") @mcp.tool()