Skip to main content
Glama
blender_socket_server.py15.7 kB
""" Blender Socket Server - Run inside Blender ========================================== Simple socket server that runs inside Blender to handle requests from Griptape nodes. Based on socket architecture (more reliable than stdio). Paste this script into Blender's Text Editor and run it. """ import socket import threading import json import time import base64 import io import logging import os try: import bpy BLENDER_AVAILABLE = True except ImportError: BLENDER_AVAILABLE = False print("ERROR: This script must be run inside Blender") # Setup logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class BlenderSocketServer: """Simple socket server for Blender communication""" def __init__(self, host='localhost', port=8765): self.host = host self.port = port self.server_socket = None self.running = False self.server_thread = None def start(self): """Start the socket server""" if self.running: logger.info("Server is already running") return if not BLENDER_AVAILABLE: logger.error("Blender not available. Run this script inside Blender.") return try: self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.server_socket.bind((self.host, self.port)) self.server_socket.listen(5) self.running = True self.server_thread = threading.Thread(target=self._run_server, daemon=True) self.server_thread.start() logger.info(f"✓ Blender Socket Server started on {self.host}:{self.port}") logger.info("Ready to receive commands from Griptape nodes") except Exception as e: logger.error(f"Failed to start server: {e}") self.running = False def stop(self): """Stop the socket server""" if not self.running: logger.info("Server is not running") return logger.info("Stopping Blender Socket Server...") self.running = False if self.server_socket: try: self.server_socket.close() except: pass logger.info("✓ Blender Socket Server stopped") def _run_server(self): """Main server loop""" while self.running: try: if not self.server_socket: break client_socket, addr = self.server_socket.accept() logger.info(f"Connection from {addr}") # Handle client in separate thread client_thread = threading.Thread( target=self._handle_client, args=(client_socket,), daemon=True ) client_thread.start() except Exception as e: if self.running: # Only log if we're not shutting down logger.error(f"Server error: {e}") break def _handle_client(self, client_socket): """Handle individual client connection using threading approach like reference project""" print("DEBUG SERVER: Client handler started") try: # Set timeout for receive operations client_socket.settimeout(5.0) # Receive all data buffer = b'' while True: try: chunk = client_socket.recv(1024) if not chunk: break buffer += chunk # Try to parse as complete JSON try: request_str = buffer.decode('utf-8') request = json.loads(request_str) print(f"DEBUG SERVER: Complete request received: {request.get('command', 'NO_COMMAND')}") break except (json.JSONDecodeError, UnicodeDecodeError): # Need more data or incomplete continue except socket.timeout: print("DEBUG SERVER: Timeout waiting for data") break except Exception as e: print(f"DEBUG SERVER: Error receiving data: {e}") break if not buffer: print("DEBUG SERVER: No data received") return # Process the request try: response = self._process_request(request) print(f"DEBUG SERVER: Response ready: {response.get('success', 'Unknown')}") except Exception as e: print(f"DEBUG SERVER: Processing error: {e}") response = {"success": False, "error": f"Processing failed: {str(e)}"} # Send response back try: response_json = json.dumps(response) response_bytes = response_json.encode('utf-8') print(f"DEBUG SERVER: Sending {len(response_bytes)} bytes") client_socket.sendall(response_bytes) print("DEBUG SERVER: Response sent successfully") # Important: Signal end of transmission client_socket.shutdown(socket.SHUT_WR) except Exception as e: print(f"DEBUG SERVER: Failed to send response: {e}") except Exception as e: print(f"DEBUG SERVER: Client handler error: {e}") finally: try: client_socket.close() print("DEBUG SERVER: Client socket closed") except: pass def _process_request(self, request): """Process incoming request and return response""" command = request.get('command') params = request.get('params', {}) logger.info(f"Processing command: {command}") print(f"DEBUG SERVER: Received command '{command}' with params: {list(params.keys())}") try: if command == 'health_check': result = self._health_check() elif command == 'get_scene_info': result = self._get_scene_info() elif command == 'list_cameras': result = self._list_cameras() elif command == 'execute_code': result = self._execute_code(params.get('code', '')) else: result = {"success": False, "error": f"Unknown command: {command}"} print(f"DEBUG SERVER: Command '{command}' completed, success: {result.get('success', 'Unknown')}") return result except Exception as e: error_result = {"success": False, "error": f"Command processing failed: {str(e)}"} print(f"DEBUG SERVER: Command '{command}' failed with error: {str(e)}") return error_result def _health_check(self): """Health check endpoint""" return { "success": True, "status": "healthy", "blender_version": bpy.app.version_string, "timestamp": time.time() } def _get_scene_info(self): """Get scene information""" try: scene = bpy.context.scene # Safely get build info try: build_date = bpy.app.build_date.decode('utf-8') if bpy.app.build_date else "Unknown" except (AttributeError, UnicodeDecodeError): build_date = str(bpy.app.build_date) if bpy.app.build_date else "Unknown" try: build_hash = bpy.app.build_hash.decode('utf-8') if bpy.app.build_hash else "Unknown" except (AttributeError, UnicodeDecodeError): build_hash = str(bpy.app.build_hash) if bpy.app.build_hash else "Unknown" return { "success": True, "scene": { "name": scene.name, "frame_current": scene.frame_current, "frame_start": scene.frame_start, "frame_end": scene.frame_end }, "blender": { "version": bpy.app.version_string, "build_date": build_date, "build_hash": build_hash }, "render": { "engine": scene.render.engine, "resolution_x": scene.render.resolution_x, "resolution_y": scene.render.resolution_y, "resolution_percentage": scene.render.resolution_percentage } } except Exception as e: return {"success": False, "error": str(e)} def _list_cameras(self): """List all cameras in the scene""" try: cameras = [] for obj in bpy.data.objects: if obj.type == 'CAMERA': cameras.append({ "name": obj.name, "location": list(obj.location), "rotation": list(obj.rotation_euler), "active": obj == bpy.context.scene.camera }) return { "success": True, "cameras": cameras, "count": len(cameras), "active_camera": bpy.context.scene.camera.name if bpy.context.scene.camera else None } except Exception as e: return {"success": False, "error": str(e)} def _execute_code(self, code: str): """Execute arbitrary Python code in Blender with safety measures""" try: # Add safety measures for large/complex scenes import gc # Create a safe namespace namespace = { 'bpy': bpy, 'result': None } try: # Execute the code with additional error catching exec(code, namespace) # Force garbage collection to help with memory issues in large scenes gc.collect() # Return the result result = namespace.get('result') if result is not None: return {"success": True, "result": result} else: return {"success": True, "message": "Code executed successfully"} except MemoryError: gc.collect() # Try to free memory return {"success": False, "error": "Out of memory - scene too complex for this operation"} except RecursionError: return {"success": False, "error": "Operation too complex - recursion limit exceeded"} except Exception as e: error_msg = str(e) # Check for dependency graph related errors if "dependency" in error_msg.lower() or "depsgraph" in error_msg.lower(): return {"success": False, "error": f"Scene dependency error: {error_msg}. Try with a simpler scene."} else: return {"success": False, "error": f"Code execution failed: {error_msg}"} except Exception as e: # Ultimate fallback return {"success": False, "error": f"Code execution failed with system error: {str(e)}"} # Note: Complex operations like rendering should be done via execute_code # following the reference project pattern of keeping the server minimal # and stable. The external client handles complex rendering logic. # Global server instance blender_server = BlenderSocketServer() # UI Integration (Blender operators and panel) if BLENDER_AVAILABLE: class BLENDER_OT_start_socket_server(bpy.types.Operator): """Start the Blender Socket Server""" bl_idname = "blender.start_socket_server" bl_label = "Start Socket Server" bl_description = "Start the Blender Socket Server for Griptape nodes" def execute(self, context): blender_server.start() return {'FINISHED'} class BLENDER_OT_stop_socket_server(bpy.types.Operator): """Stop the Blender Socket Server""" bl_idname = "blender.stop_socket_server" bl_label = "Stop Socket Server" bl_description = "Stop the Blender Socket Server" def execute(self, context): blender_server.stop() return {'FINISHED'} class BLENDER_PT_socket_server_panel(bpy.types.Panel): """Socket Server control panel""" bl_label = "Blender Socket Server" bl_idname = "BLENDER_PT_socket_server" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "Griptape" def draw(self, context): layout = self.layout if blender_server.running: layout.label(text="Status: Running ✓", icon='CHECKMARK') layout.label(text=f"Port: {blender_server.port}") layout.operator("blender.stop_socket_server", icon='CANCEL') else: layout.label(text="Status: Stopped", icon='CANCEL') layout.operator("blender.start_socket_server", icon='PLAY') layout.separator() layout.label(text="For Griptape Nodes") def register(): """Register Blender classes""" bpy.utils.register_class(BLENDER_OT_start_socket_server) bpy.utils.register_class(BLENDER_OT_stop_socket_server) bpy.utils.register_class(BLENDER_PT_socket_server_panel) def unregister(): """Unregister Blender classes""" blender_server.stop() bpy.utils.unregister_class(BLENDER_PT_socket_server_panel) bpy.utils.unregister_class(BLENDER_OT_stop_socket_server) bpy.utils.unregister_class(BLENDER_OT_start_socket_server) # Convenience functions def start_server(): """Start the socket server""" blender_server.start() def stop_server(): """Stop the socket server""" blender_server.stop() def server_status(): """Check server status""" if blender_server.running: print(f"✓ Socket Server running on {blender_server.host}:{blender_server.port}") else: print("✗ Socket Server stopped") # Auto-start when script is run if __name__ == "__main__": if BLENDER_AVAILABLE: print("=" * 50) print("BLENDER SOCKET SERVER") print("=" * 50) print("Simple socket server for Griptape node communication") print() print("Functions:") print("- start_server() # Start the server") print("- stop_server() # Stop the server") print("- server_status() # Check status") print() # Register UI register() # Auto-start server start_server() else: print("ERROR: This script must be run inside Blender")

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/griptape-ai/griptape-nodes-blender'

If you have feedback or need assistance with the MCP directory API, please join our Discord server