Send a command to Blender and return the response
(self, command_type: str, params: Dict[str, Any] = None)
| 115 | raise Exception("No data received") |
| 116 | |
| 117 | def send_command(self, command_type: str, params: Dict[str, Any] = None) -> Dict[str, Any]: |
| 118 | """Send a command to Blender and return the response""" |
| 119 | if not self.sock and not self.connect(): |
| 120 | raise ConnectionError("Not connected to Blender") |
| 121 | |
| 122 | command = { |
| 123 | "type": command_type, |
| 124 | "params": params or {} |
| 125 | } |
| 126 | |
| 127 | try: |
| 128 | # Log the command being sent |
| 129 | logger.info(f"Sending command: {command_type} with params: {params}") |
| 130 | |
| 131 | # Send the command |
| 132 | self.sock.sendall(json.dumps(command).encode('utf-8')) |
| 133 | logger.info(f"Command sent, waiting for response...") |
| 134 | |
| 135 | # Set a timeout for receiving - use the same timeout as in receive_full_response |
| 136 | self.sock.settimeout(180.0) # Match the addon's timeout |
| 137 | |
| 138 | # Receive the response using the improved receive_full_response method |
| 139 | response_data = self.receive_full_response(self.sock) |
| 140 | logger.info(f"Received {len(response_data)} bytes of data") |
| 141 | |
| 142 | response = json.loads(response_data.decode('utf-8')) |
| 143 | logger.info(f"Response parsed, status: {response.get('status', 'unknown')}") |
| 144 | |
| 145 | if response.get("status") == "error": |
| 146 | logger.error(f"Blender error: {response.get('message')}") |
| 147 | raise Exception(response.get("message", "Unknown error from Blender")) |
| 148 | |
| 149 | return response.get("result", {}) |
| 150 | except socket.timeout: |
| 151 | logger.error("Socket timeout while waiting for response from Blender") |
| 152 | # Don't try to reconnect here - let the get_blender_connection handle reconnection |
| 153 | # Just invalidate the current socket so it will be recreated next time |
| 154 | self.sock = None |
| 155 | raise Exception("Timeout waiting for Blender response - try simplifying your request. If Blender is running headless (blender -b), commands never execute; run Blender with a GUI or via 'xvfb-run -a blender' instead") |
| 156 | except (ConnectionError, BrokenPipeError, ConnectionResetError) as e: |
| 157 | logger.error(f"Socket connection error: {str(e)}") |
| 158 | self.sock = None |
| 159 | raise Exception(f"Connection to Blender lost: {str(e)}") |
| 160 | except json.JSONDecodeError as e: |
| 161 | logger.error(f"Invalid JSON response from Blender: {str(e)}") |
| 162 | # Try to log what was received |
| 163 | if 'response_data' in locals() and response_data: |
| 164 | logger.error(f"Raw response (first 200 bytes): {response_data[:200]}") |
| 165 | raise Exception(f"Invalid response from Blender: {str(e)}") |
| 166 | except Exception as e: |
| 167 | logger.error(f"Error communicating with Blender: {str(e)}") |
| 168 | # Don't try to reconnect here - let the get_blender_connection handle reconnection |
| 169 | self.sock = None |
| 170 | raise Exception(f"Communication error with Blender: {str(e)}") |
| 171 | |
| 172 | @asynccontextmanager |
| 173 | async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]: |
no test coverage detected