connection established! it gets data properly now
This commit is contained in:
parent
4372edf2a7
commit
2efcd9c7f6
@ -57,7 +57,7 @@ class BlenderMCPServer:
|
|||||||
try:
|
try:
|
||||||
# Set a timeout for receiving data
|
# Set a timeout for receiving data
|
||||||
self.client.settimeout(15.0)
|
self.client.settimeout(15.0)
|
||||||
data = self.client.recv(4096)
|
data = self.client.recv(8192) # Increased buffer size
|
||||||
if not data:
|
if not data:
|
||||||
print("Empty data received, client may have disconnected")
|
print("Empty data received, client may have disconnected")
|
||||||
break
|
break
|
||||||
@ -65,9 +65,19 @@ class BlenderMCPServer:
|
|||||||
try:
|
try:
|
||||||
print(f"Received data: {data.decode('utf-8')}")
|
print(f"Received data: {data.decode('utf-8')}")
|
||||||
command = json.loads(data.decode('utf-8'))
|
command = json.loads(data.decode('utf-8'))
|
||||||
|
|
||||||
|
# Process the command
|
||||||
|
print(f"Processing command: {command.get('type')}")
|
||||||
response = self.execute_command(command)
|
response = self.execute_command(command)
|
||||||
print(f"Sending response: {json.dumps(response)[:100]}...") # Truncate long responses in log
|
|
||||||
self.client.sendall(json.dumps(response).encode('utf-8'))
|
# Send the response - handle large responses by chunking if needed
|
||||||
|
response_json = json.dumps(response)
|
||||||
|
print(f"Sending response: {response_json[:100]}...") # Truncate long responses in log
|
||||||
|
|
||||||
|
# Send in one go - most responses should be small enough
|
||||||
|
self.client.sendall(response_json.encode('utf-8'))
|
||||||
|
print("Response sent successfully")
|
||||||
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
print(f"Invalid JSON received: {data.decode('utf-8')}")
|
print(f"Invalid JSON received: {data.decode('utf-8')}")
|
||||||
self.client.sendall(json.dumps({
|
self.client.sendall(json.dumps({
|
||||||
@ -89,8 +99,9 @@ class BlenderMCPServer:
|
|||||||
print(f"Error receiving data: {str(e)}")
|
print(f"Error receiving data: {str(e)}")
|
||||||
break
|
break
|
||||||
|
|
||||||
self.client.close()
|
if self.client:
|
||||||
self.client = None
|
self.client.close()
|
||||||
|
self.client = None
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
# This is normal - just continue the loop
|
# This is normal - just continue the loop
|
||||||
continue
|
continue
|
||||||
@ -133,9 +144,14 @@ class BlenderMCPServer:
|
|||||||
handler = handlers.get(cmd_type)
|
handler = handlers.get(cmd_type)
|
||||||
if handler:
|
if handler:
|
||||||
try:
|
try:
|
||||||
|
print(f"Executing handler for {cmd_type}")
|
||||||
result = handler(**params)
|
result = handler(**params)
|
||||||
|
print(f"Handler execution complete")
|
||||||
return {"status": "success", "result": result}
|
return {"status": "success", "result": result}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(f"Error in handler: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return {"status": "error", "message": str(e)}
|
return {"status": "error", "message": str(e)}
|
||||||
else:
|
else:
|
||||||
return {"status": "error", "message": f"Unknown command type: {cmd_type}"}
|
return {"status": "error", "message": f"Unknown command type: {cmd_type}"}
|
||||||
@ -153,57 +169,30 @@ class BlenderMCPServer:
|
|||||||
"""Get information about the current Blender scene"""
|
"""Get information about the current Blender scene"""
|
||||||
try:
|
try:
|
||||||
print("Getting scene info...")
|
print("Getting scene info...")
|
||||||
|
# Simplify the scene info to reduce data size
|
||||||
scene_info = {
|
scene_info = {
|
||||||
|
"name": bpy.context.scene.name,
|
||||||
|
"object_count": len(bpy.context.scene.objects),
|
||||||
"objects": [],
|
"objects": [],
|
||||||
"materials": [],
|
"materials_count": len(bpy.data.materials),
|
||||||
"camera": {},
|
|
||||||
"render_settings": {},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Collect object information (limit to first 20 objects to prevent oversized responses)
|
# Collect minimal object information (limit to first 10 objects)
|
||||||
for i, obj in enumerate(bpy.context.scene.objects):
|
for i, obj in enumerate(bpy.context.scene.objects):
|
||||||
if i >= 20: # Limit to 20 objects to prevent oversized responses
|
if i >= 10: # Reduced from 20 to 10
|
||||||
break
|
break
|
||||||
|
|
||||||
obj_info = {
|
obj_info = {
|
||||||
"name": obj.name,
|
"name": obj.name,
|
||||||
"type": obj.type,
|
"type": obj.type,
|
||||||
"location": [float(obj.location.x), float(obj.location.y), float(obj.location.z)],
|
# Only include basic location data
|
||||||
"rotation": [float(obj.rotation_euler.x), float(obj.rotation_euler.y), float(obj.rotation_euler.z)],
|
"location": [round(float(obj.location.x), 2),
|
||||||
"scale": [float(obj.scale.x), float(obj.scale.y), float(obj.scale.z)],
|
round(float(obj.location.y), 2),
|
||||||
"visible": obj.visible_get(),
|
round(float(obj.location.z), 2)],
|
||||||
}
|
}
|
||||||
scene_info["objects"].append(obj_info)
|
scene_info["objects"].append(obj_info)
|
||||||
|
|
||||||
# Collect material information (limit to first 10 materials)
|
print(f"Scene info collected: {len(scene_info['objects'])} objects")
|
||||||
for i, mat in enumerate(bpy.data.materials):
|
|
||||||
if i >= 10:
|
|
||||||
break
|
|
||||||
|
|
||||||
mat_info = {
|
|
||||||
"name": mat.name,
|
|
||||||
"use_nodes": bool(mat.use_nodes),
|
|
||||||
}
|
|
||||||
scene_info["materials"].append(mat_info)
|
|
||||||
|
|
||||||
# Camera information
|
|
||||||
camera = bpy.context.scene.camera
|
|
||||||
if camera:
|
|
||||||
scene_info["camera"] = {
|
|
||||||
"name": camera.name,
|
|
||||||
"location": [float(camera.location.x), float(camera.location.y), float(camera.location.z)],
|
|
||||||
"rotation": [float(camera.rotation_euler.x), float(camera.rotation_euler.y), float(camera.rotation_euler.z)],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Render settings (simplified)
|
|
||||||
render = bpy.context.scene.render
|
|
||||||
scene_info["render_settings"] = {
|
|
||||||
"engine": render.engine,
|
|
||||||
"resolution_x": int(render.resolution_x),
|
|
||||||
"resolution_y": int(render.resolution_y),
|
|
||||||
}
|
|
||||||
|
|
||||||
print(f"Scene info collected: {len(scene_info['objects'])} objects, {len(scene_info['materials'])} materials")
|
|
||||||
return scene_info
|
return scene_info
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in get_scene_info: {str(e)}")
|
print(f"Error in get_scene_info: {str(e)}")
|
||||||
|
|||||||
@ -44,41 +44,61 @@ class BlenderConnection:
|
|||||||
finally:
|
finally:
|
||||||
self.sock = None
|
self.sock = None
|
||||||
|
|
||||||
# Replace the single recv call with this chunked approach
|
|
||||||
def receive_full_response(self, sock, buffer_size=8192):
|
def receive_full_response(self, sock, buffer_size=8192):
|
||||||
"""Receive the complete response, potentially in multiple chunks"""
|
"""Receive the complete response, potentially in multiple chunks"""
|
||||||
chunks = []
|
chunks = []
|
||||||
sock.settimeout(10.0)
|
# Use a consistent timeout value that matches the addon's timeout
|
||||||
|
sock.settimeout(15.0) # Match the addon's timeout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
chunk = sock.recv(buffer_size)
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
chunks.append(chunk)
|
|
||||||
|
|
||||||
# Check if we've received a complete JSON object
|
|
||||||
try:
|
try:
|
||||||
data = b''.join(chunks)
|
chunk = sock.recv(buffer_size)
|
||||||
json.loads(data.decode('utf-8'))
|
if not chunk:
|
||||||
# If we get here, it parsed successfully
|
# If we get an empty chunk, the connection might be closed
|
||||||
logger.info(f"Received complete response ({len(data)} bytes)")
|
if not chunks: # If we haven't received anything yet, this is an error
|
||||||
return data
|
raise Exception("Connection closed before receiving any data")
|
||||||
except json.JSONDecodeError:
|
break
|
||||||
# Incomplete JSON, continue receiving
|
|
||||||
continue
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
# Check if we've received a complete JSON object
|
||||||
|
try:
|
||||||
|
data = b''.join(chunks)
|
||||||
|
json.loads(data.decode('utf-8'))
|
||||||
|
# If we get here, it parsed successfully
|
||||||
|
logger.info(f"Received complete response ({len(data)} bytes)")
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Incomplete JSON, continue receiving
|
||||||
|
continue
|
||||||
|
except socket.timeout:
|
||||||
|
# If we hit a timeout during receiving, break the loop and try to use what we have
|
||||||
|
logger.warning("Socket timeout during chunked receive")
|
||||||
|
break
|
||||||
|
except (ConnectionError, BrokenPipeError, ConnectionResetError) as e:
|
||||||
|
logger.error(f"Socket connection error during receive: {str(e)}")
|
||||||
|
raise # Re-raise to be handled by the caller
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
logger.warning("Socket timeout during chunked receive")
|
logger.warning("Socket timeout during chunked receive")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error during receive: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
# Return whatever we got
|
# If we get here, we either timed out or broke out of the loop
|
||||||
data = b''.join(chunks)
|
# Try to use what we have
|
||||||
if not data:
|
if chunks:
|
||||||
raise Exception("Empty response received")
|
data = b''.join(chunks)
|
||||||
return data
|
logger.info(f"Returning data after receive completion ({len(data)} bytes)")
|
||||||
|
try:
|
||||||
# Then in send_command:
|
# Try to parse what we have
|
||||||
# response_data = self.sock.recv(65536)
|
json.loads(data.decode('utf-8'))
|
||||||
|
return data
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# If we can't parse it, it's incomplete
|
||||||
|
raise Exception("Incomplete JSON response received")
|
||||||
|
else:
|
||||||
|
raise Exception("No data received")
|
||||||
|
|
||||||
def send_command(self, command_type: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
def send_command(self, command_type: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
|
||||||
"""Send a command to Blender and return the response"""
|
"""Send a command to Blender and return the response"""
|
||||||
@ -98,11 +118,10 @@ class BlenderConnection:
|
|||||||
self.sock.sendall(json.dumps(command).encode('utf-8'))
|
self.sock.sendall(json.dumps(command).encode('utf-8'))
|
||||||
logger.info(f"Command sent, waiting for response...")
|
logger.info(f"Command sent, waiting for response...")
|
||||||
|
|
||||||
# Set a timeout for receiving
|
# Set a timeout for receiving - use the same timeout as in receive_full_response
|
||||||
self.sock.settimeout(30.0) # Increased timeout
|
self.sock.settimeout(15.0) # Match the addon's timeout
|
||||||
|
|
||||||
# Receive the response
|
# Receive the response using the improved receive_full_response method
|
||||||
# response_data = self.sock.recv(65536) # Increase buffer size for larger responses
|
|
||||||
response_data = self.receive_full_response(self.sock)
|
response_data = self.receive_full_response(self.sock)
|
||||||
logger.info(f"Received {len(response_data)} bytes of data")
|
logger.info(f"Received {len(response_data)} bytes of data")
|
||||||
|
|
||||||
@ -116,42 +135,55 @@ class BlenderConnection:
|
|||||||
return response.get("result", {})
|
return response.get("result", {})
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
logger.error("Socket timeout while waiting for response from Blender")
|
logger.error("Socket timeout while waiting for response from Blender")
|
||||||
# Try to reconnect
|
# Don't try to reconnect here - let the get_blender_connection handle reconnection
|
||||||
self.disconnect()
|
# Just invalidate the current socket so it will be recreated next time
|
||||||
if self.connect():
|
self.sock = None
|
||||||
logger.info("Reconnected to Blender after timeout")
|
raise Exception("Timeout waiting for Blender response - try simplifying your request")
|
||||||
raise Exception("Timeout waiting for Blender response")
|
except (ConnectionError, BrokenPipeError, ConnectionResetError) as e:
|
||||||
|
logger.error(f"Socket connection error: {str(e)}")
|
||||||
|
self.sock = None
|
||||||
|
raise Exception(f"Connection to Blender lost: {str(e)}")
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
logger.error(f"Invalid JSON response from Blender: {str(e)}")
|
logger.error(f"Invalid JSON response from Blender: {str(e)}")
|
||||||
# Try to log what was received
|
# Try to log what was received
|
||||||
if response_data:
|
if 'response_data' in locals() and response_data:
|
||||||
logger.error(f"Raw response (first 200 bytes): {response_data[:200]}")
|
logger.error(f"Raw response (first 200 bytes): {response_data[:200]}")
|
||||||
raise Exception(f"Invalid response from Blender: {str(e)}")
|
raise Exception(f"Invalid response from Blender: {str(e)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error communicating with Blender: {str(e)}")
|
logger.error(f"Error communicating with Blender: {str(e)}")
|
||||||
# Try to reconnect
|
# Don't try to reconnect here - let the get_blender_connection handle reconnection
|
||||||
self.disconnect()
|
self.sock = None
|
||||||
if self.connect():
|
|
||||||
logger.info("Reconnected to Blender")
|
|
||||||
raise Exception(f"Communication error with Blender: {str(e)}")
|
raise Exception(f"Communication error with Blender: {str(e)}")
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
|
async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
|
||||||
"""Manage server startup and shutdown lifecycle"""
|
"""Manage server startup and shutdown lifecycle"""
|
||||||
blender = BlenderConnection(host="localhost", port=9876)
|
# We don't need to create a connection here since we're using the global connection
|
||||||
|
# for resources and tools
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Connect to Blender on startup
|
# Just log that we're starting up
|
||||||
connected = blender.connect()
|
logger.info("BlenderMCP server starting up")
|
||||||
if not connected:
|
|
||||||
logger.warning("Could not connect to Blender on startup. Make sure the Blender addon is running.")
|
|
||||||
|
|
||||||
# Return the Blender connection in the context
|
# Try to connect to Blender on startup to verify it's available
|
||||||
yield {"blender": blender}
|
try:
|
||||||
|
# This will initialize the global connection if needed
|
||||||
|
blender = get_blender_connection()
|
||||||
|
logger.info("Successfully connected to Blender on startup")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not connect to Blender on startup: {str(e)}")
|
||||||
|
logger.warning("Make sure the Blender addon is running before using Blender resources or tools")
|
||||||
|
|
||||||
|
# Return an empty context - we're using the global connection
|
||||||
|
yield {}
|
||||||
finally:
|
finally:
|
||||||
# Disconnect from Blender on shutdown
|
# Clean up the global connection on shutdown
|
||||||
blender.disconnect()
|
global _blender_connection
|
||||||
logger.info("Disconnected from Blender")
|
if _blender_connection:
|
||||||
|
logger.info("Disconnecting from Blender on shutdown")
|
||||||
|
_blender_connection.disconnect()
|
||||||
|
_blender_connection = None
|
||||||
|
logger.info("BlenderMCP server shut down")
|
||||||
|
|
||||||
# Create the MCP server with lifespan support
|
# Create the MCP server with lifespan support
|
||||||
mcp = FastMCP(
|
mcp = FastMCP(
|
||||||
@ -162,68 +194,88 @@ mcp = FastMCP(
|
|||||||
|
|
||||||
# Resource endpoints
|
# Resource endpoints
|
||||||
|
|
||||||
# Global connection for resources (workaround since resources can't access context)
|
# Global connection for resources (since resources can't access context)
|
||||||
_blender_connection = None
|
_blender_connection = None
|
||||||
|
|
||||||
def get_blender_connection():
|
def get_blender_connection():
|
||||||
|
"""Get or create a persistent Blender connection"""
|
||||||
global _blender_connection
|
global _blender_connection
|
||||||
|
|
||||||
|
# If we have an existing connection, check if it's still valid
|
||||||
|
if _blender_connection is not None:
|
||||||
|
# Test if the connection is still alive with a simple ping
|
||||||
|
try:
|
||||||
|
# Just try to send a small message to check if the socket is still connected
|
||||||
|
_blender_connection.sock.sendall(b'')
|
||||||
|
return _blender_connection
|
||||||
|
except Exception as e:
|
||||||
|
# Connection is dead, close it and create a new one
|
||||||
|
logger.warning(f"Existing connection is no longer valid: {str(e)}")
|
||||||
|
try:
|
||||||
|
_blender_connection.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
_blender_connection = None
|
||||||
|
|
||||||
|
# Create a new connection if needed
|
||||||
if _blender_connection is None:
|
if _blender_connection is None:
|
||||||
_blender_connection = BlenderConnection(host="localhost", port=9876)
|
_blender_connection = BlenderConnection(host="localhost", port=9876)
|
||||||
_blender_connection.connect()
|
if not _blender_connection.connect():
|
||||||
|
logger.error("Failed to connect to Blender")
|
||||||
|
_blender_connection = None
|
||||||
|
raise Exception("Could not connect to Blender. Make sure the Blender addon is running.")
|
||||||
|
logger.info("Created new persistent connection to Blender")
|
||||||
|
|
||||||
return _blender_connection
|
return _blender_connection
|
||||||
|
|
||||||
@mcp.resource("blender://ping")
|
@mcp.resource("blender://ping")
|
||||||
def ping_blender() -> str:
|
def ping_blender() -> str:
|
||||||
"""Simple ping to test Blender connectivity"""
|
"""Ping the Blender server to check connectivity"""
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
blender = get_blender_connection()
|
||||||
result = blender.send_command("ping")
|
result = blender.send_command("ping")
|
||||||
return f"Ping successful: {json.dumps(result)}"
|
return json.dumps({"status": "success", "result": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Ping failed: {str(e)}"
|
logger.error(f"Error pinging Blender: {str(e)}")
|
||||||
|
return json.dumps({"status": "error", "message": str(e)})
|
||||||
|
|
||||||
@mcp.resource("blender://simple")
|
@mcp.resource("blender://simple")
|
||||||
def get_simple_info() -> str:
|
def get_simple_info() -> str:
|
||||||
"""Get simplified information from Blender"""
|
"""Get basic information about the Blender instance"""
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
blender = get_blender_connection()
|
||||||
result = blender.send_command("get_simple_info")
|
result = blender.send_command("get_simple_info")
|
||||||
return json.dumps(result, indent=2)
|
return json.dumps({"status": "success", "result": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting simple info: {str(e)}"
|
logger.error(f"Error getting simple info from Blender: {str(e)}")
|
||||||
|
return json.dumps({"status": "error", "message": str(e)})
|
||||||
|
|
||||||
@mcp.resource("blender://scene")
|
@mcp.resource("blender://scene")
|
||||||
def get_scene_info() -> str:
|
def get_scene_info() -> str:
|
||||||
"""
|
"""Get detailed information about the current Blender scene"""
|
||||||
Get information about the current Blender scene, including all objects,
|
|
||||||
materials, camera settings, and render configuration.
|
|
||||||
"""
|
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
scene_info = blender.send_command("get_scene_info")
|
blender = get_blender_connection()
|
||||||
return json.dumps(scene_info, indent=2)
|
result = blender.send_command("get_scene_info")
|
||||||
|
return json.dumps({"status": "success", "result": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting scene info: {str(e)}"
|
logger.error(f"Error getting scene info from Blender: {str(e)}")
|
||||||
|
return json.dumps({"status": "error", "message": str(e)})
|
||||||
|
|
||||||
@mcp.resource("blender://object/{object_name}")
|
@mcp.resource("blender://object/{object_name}")
|
||||||
def get_object_info(object_name: str) -> str:
|
def get_object_info(object_name: str) -> str:
|
||||||
"""
|
"""
|
||||||
Get detailed information about a specific object in the Blender scene.
|
Get detailed information about a specific object in the Blender scene.
|
||||||
|
|
||||||
Parameters:
|
Args:
|
||||||
- object_name: The name of the object to get information about
|
object_name: The name of the object to get information about
|
||||||
"""
|
"""
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
object_info = blender.send_command("get_object_info", {"name": object_name})
|
blender = get_blender_connection()
|
||||||
return json.dumps(object_info, indent=2)
|
result = blender.send_command("get_object_info", {"name": object_name})
|
||||||
|
return json.dumps({"status": "success", "result": result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error getting object info: {str(e)}"
|
logger.error(f"Error getting object info from Blender: {str(e)}")
|
||||||
|
return json.dumps({"status": "error", "message": str(e)})
|
||||||
|
|
||||||
# Tool endpoints
|
# Tool endpoints
|
||||||
|
|
||||||
@ -246,16 +298,15 @@ def create_object(
|
|||||||
- rotation: Optional [x, y, z] rotation in radians
|
- rotation: Optional [x, y, z] rotation in radians
|
||||||
- scale: Optional [x, y, z] scale factors
|
- scale: Optional [x, y, z] scale factors
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
# Set default values for missing parameters
|
|
||||||
loc = location or [0, 0, 0]
|
|
||||||
rot = rotation or [0, 0, 0]
|
|
||||||
sc = scale or [1, 1, 1]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Get the global connection
|
||||||
|
blender = get_blender_connection()
|
||||||
|
|
||||||
|
# Set default values for missing parameters
|
||||||
|
loc = location or [0, 0, 0]
|
||||||
|
rot = rotation or [0, 0, 0]
|
||||||
|
sc = scale or [1, 1, 1]
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"type": type,
|
"type": type,
|
||||||
"location": loc,
|
"location": loc,
|
||||||
@ -269,6 +320,7 @@ def create_object(
|
|||||||
result = blender.send_command("create_object", params)
|
result = blender.send_command("create_object", params)
|
||||||
return f"Created {type} object: {result['name']}"
|
return f"Created {type} object: {result['name']}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating object: {str(e)}")
|
||||||
return f"Error creating object: {str(e)}"
|
return f"Error creating object: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
@ -290,11 +342,10 @@ def modify_object(
|
|||||||
- scale: Optional [x, y, z] scale factors
|
- scale: Optional [x, y, z] scale factors
|
||||||
- visible: Optional boolean to set visibility
|
- visible: Optional boolean to set visibility
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Get the global connection
|
||||||
|
blender = get_blender_connection()
|
||||||
|
|
||||||
params = {"name": name}
|
params = {"name": name}
|
||||||
|
|
||||||
if location is not None:
|
if location is not None:
|
||||||
@ -309,6 +360,7 @@ def modify_object(
|
|||||||
result = blender.send_command("modify_object", params)
|
result = blender.send_command("modify_object", params)
|
||||||
return f"Modified object: {result['name']}"
|
return f"Modified object: {result['name']}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error modifying object: {str(e)}")
|
||||||
return f"Error modifying object: {str(e)}"
|
return f"Error modifying object: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
@ -319,14 +371,14 @@ def delete_object(ctx: Context, name: str) -> str:
|
|||||||
Parameters:
|
Parameters:
|
||||||
- name: Name of the object to delete
|
- name: Name of the object to delete
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Get the global connection
|
||||||
|
blender = get_blender_connection()
|
||||||
|
|
||||||
result = blender.send_command("delete_object", {"name": name})
|
result = blender.send_command("delete_object", {"name": name})
|
||||||
return f"Deleted object: {result['deleted']}"
|
return f"Deleted object: {name}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error deleting object: {str(e)}")
|
||||||
return f"Error deleting object: {str(e)}"
|
return f"Error deleting object: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
@ -340,32 +392,25 @@ def set_material(
|
|||||||
Set or create a material for an object.
|
Set or create a material for an object.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- object_name: Name of the object to assign the material to
|
- object_name: Name of the object to apply the material to
|
||||||
- material_name: Optional name of the material to use/create
|
- material_name: Optional name of the material to use or create
|
||||||
- color: Optional [r, g, b] or [r, g, b, a] color values (0.0-1.0)
|
- color: Optional [R, G, B] color values (0.0-1.0)
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = {
|
# Get the global connection
|
||||||
"object_name": object_name
|
blender = get_blender_connection()
|
||||||
}
|
|
||||||
|
params = {"object_name": object_name}
|
||||||
|
|
||||||
if material_name:
|
if material_name:
|
||||||
params["material_name"] = material_name
|
params["material_name"] = material_name
|
||||||
|
|
||||||
if color:
|
if color:
|
||||||
params["color"] = color
|
params["color"] = color
|
||||||
|
|
||||||
result = blender.send_command("set_material", params)
|
result = blender.send_command("set_material", params)
|
||||||
|
return f"Applied material to {object_name}: {result.get('material_name', 'unknown')}"
|
||||||
if "material" in result:
|
|
||||||
return f"Set material '{result['material']}' on object '{result['object']}'"
|
|
||||||
else:
|
|
||||||
return f"Error setting material: {result.get('error', 'Unknown error')}"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error setting material: {str(e)}")
|
||||||
return f"Error setting material: {str(e)}"
|
return f"Error setting material: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
@ -376,182 +421,148 @@ def render_scene(
|
|||||||
resolution_y: int = None
|
resolution_y: int = None
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Render the current Blender scene.
|
Render the current scene and return the image.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- output_path: Optional path to save the rendered image
|
- output_path: Optional path to save the rendered image
|
||||||
- resolution_x: Optional horizontal resolution in pixels
|
- resolution_x: Optional horizontal resolution
|
||||||
- resolution_y: Optional vertical resolution in pixels
|
- resolution_y: Optional vertical resolution
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = {}
|
# Get the global connection
|
||||||
|
blender = get_blender_connection()
|
||||||
|
|
||||||
|
params = {}
|
||||||
if output_path:
|
if output_path:
|
||||||
params["output_path"] = output_path
|
params["output_path"] = output_path
|
||||||
|
if resolution_x:
|
||||||
if resolution_x is not None:
|
|
||||||
params["resolution_x"] = resolution_x
|
params["resolution_x"] = resolution_x
|
||||||
|
if resolution_y:
|
||||||
if resolution_y is not None:
|
|
||||||
params["resolution_y"] = resolution_y
|
params["resolution_y"] = resolution_y
|
||||||
|
|
||||||
result = blender.send_command("render_scene", params)
|
result = blender.send_command("render_scene", params)
|
||||||
|
|
||||||
if result.get("rendered"):
|
if "image_path" in result:
|
||||||
if output_path:
|
# If we have an image path, we could potentially load and return the image
|
||||||
return f"Scene rendered and saved to {result['output_path']} at resolution {result['resolution'][0]}x{result['resolution'][1]}"
|
return f"Scene rendered to {result['image_path']}"
|
||||||
else:
|
|
||||||
return f"Scene rendered at resolution {result['resolution'][0]}x{result['resolution'][1]}"
|
|
||||||
else:
|
else:
|
||||||
return "Error rendering scene"
|
return "Scene rendered successfully"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error rendering scene: {str(e)}")
|
||||||
return f"Error rendering scene: {str(e)}"
|
return f"Error rendering scene: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def execute_blender_code(ctx: Context, code: str) -> str:
|
def execute_blender_code(ctx: Context, code: str) -> str:
|
||||||
"""
|
"""
|
||||||
Execute arbitrary Blender Python code.
|
Execute arbitrary Python code in Blender.
|
||||||
|
|
||||||
WARNING: This tool allows executing any Python code in Blender's environment.
|
|
||||||
Use with caution as it can modify or delete data.
|
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- code: The Python code to execute in Blender's context
|
- code: The Python code to execute
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = blender.send_command("execute_code", {"code": code})
|
# Get the global connection
|
||||||
|
blender = get_blender_connection()
|
||||||
|
|
||||||
if result.get("executed"):
|
result = blender.send_command("execute_code", {"code": code})
|
||||||
return "Code executed successfully"
|
return f"Code executed successfully: {result.get('result', '')}"
|
||||||
else:
|
|
||||||
return "Error executing code"
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error executing code: {str(e)}")
|
||||||
return f"Error executing code: {str(e)}"
|
return f"Error executing code: {str(e)}"
|
||||||
|
|
||||||
@mcp.tool()
|
@mcp.tool()
|
||||||
def create_3d_scene(ctx: Context, description: str) -> str:
|
def create_3d_scene(ctx: Context, description: str) -> str:
|
||||||
"""
|
"""
|
||||||
Create a 3D scene based on a natural language description.
|
Create a 3D scene based on a text description.
|
||||||
This helper function interprets a description and creates the appropriate objects.
|
|
||||||
|
This is a higher-level tool that will interpret the description and create
|
||||||
|
appropriate objects, materials, and lighting.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- description: A natural language description of the 3D scene to create
|
- description: Text description of the scene to create
|
||||||
"""
|
"""
|
||||||
blender = ctx.request_context.lifespan_context.get("blender")
|
|
||||||
if not blender:
|
|
||||||
return "Not connected to Blender"
|
|
||||||
|
|
||||||
# First, get the current scene to see what's there
|
|
||||||
try:
|
try:
|
||||||
scene_info = blender.send_command("get_scene_info")
|
# Get the global connection
|
||||||
except Exception as e:
|
blender = get_blender_connection()
|
||||||
return f"Error accessing Blender scene: {str(e)}"
|
|
||||||
|
|
||||||
# Parse the description and create a simple scene
|
# Parse the description and create a scene
|
||||||
# This is a basic implementation - a more sophisticated version would use
|
# This is a simplified implementation - in a real tool, you would use more
|
||||||
# natural language understanding to interpret the description more accurately
|
# sophisticated parsing and scene generation logic
|
||||||
|
|
||||||
response = "Creating scene based on your description:\n\n"
|
# For now, we'll just create a simple scene with a few objects
|
||||||
|
|
||||||
try:
|
# Clear existing objects (optional)
|
||||||
# Split description into parts
|
try:
|
||||||
parts = description.lower().split()
|
blender.send_command("execute_code", {
|
||||||
|
"code": """
|
||||||
|
import bpy
|
||||||
|
# Delete all objects except camera and light
|
||||||
|
for obj in bpy.data.objects:
|
||||||
|
if obj.type not in ['CAMERA', 'LIGHT']:
|
||||||
|
bpy.data.objects.remove(obj)
|
||||||
|
"""
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error clearing scene: {str(e)}")
|
||||||
|
|
||||||
# Look for basic scene elements
|
# Create a simple scene based on the description
|
||||||
if any(word in parts for word in ["ground", "floor", "plane"]):
|
objects_created = []
|
||||||
# Create a ground plane
|
|
||||||
ground = blender.send_command("create_object", {
|
# Add a ground plane
|
||||||
|
try:
|
||||||
|
result = blender.send_command("create_object", {
|
||||||
"type": "PLANE",
|
"type": "PLANE",
|
||||||
"name": "Ground",
|
"name": "Ground",
|
||||||
"location": [0, 0, 0],
|
"location": [0, 0, 0],
|
||||||
"scale": [5, 5, 1]
|
"scale": [5, 5, 1]
|
||||||
})
|
})
|
||||||
response += f"✓ Created ground plane '{ground['name']}'\n"
|
objects_created.append(result["name"])
|
||||||
|
|
||||||
# Set material to gray
|
# Set a material for the ground
|
||||||
blender.send_command("set_material", {
|
blender.send_command("set_material", {
|
||||||
"object_name": ground["name"],
|
"object_name": "Ground",
|
||||||
|
"material_name": "GroundMaterial",
|
||||||
"color": [0.8, 0.8, 0.8]
|
"color": [0.8, 0.8, 0.8]
|
||||||
})
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error creating ground: {str(e)}")
|
||||||
|
|
||||||
# Look for cubes
|
# Simple keyword-based object creation
|
||||||
if any(word in parts for word in ["cube", "box"]):
|
if "cube" in description.lower():
|
||||||
cube = blender.send_command("create_object", {
|
try:
|
||||||
"type": "CUBE",
|
result = blender.send_command("create_object", {
|
||||||
"name": "Cube",
|
"type": "CUBE",
|
||||||
"location": [0, 0, 1],
|
"name": "Cube",
|
||||||
"scale": [1, 1, 1]
|
"location": [0, 0, 1]
|
||||||
})
|
})
|
||||||
response += f"✓ Created cube '{cube['name']}'\n"
|
objects_created.append(result["name"])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error creating cube: {str(e)}")
|
||||||
|
|
||||||
# Set material to blue
|
if "sphere" in description.lower():
|
||||||
blender.send_command("set_material", {
|
try:
|
||||||
"object_name": cube["name"],
|
result = blender.send_command("create_object", {
|
||||||
"color": [0.2, 0.4, 0.8]
|
"type": "SPHERE",
|
||||||
})
|
"name": "Sphere",
|
||||||
|
"location": [2, 0, 1]
|
||||||
|
})
|
||||||
|
objects_created.append(result["name"])
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error creating sphere: {str(e)}")
|
||||||
|
|
||||||
# Look for spheres
|
if "cylinder" in description.lower():
|
||||||
if any(word in parts for word in ["sphere", "ball"]):
|
try:
|
||||||
sphere = blender.send_command("create_object", {
|
result = blender.send_command("create_object", {
|
||||||
"type": "SPHERE",
|
"type": "CYLINDER",
|
||||||
"name": "Sphere",
|
"name": "Cylinder",
|
||||||
"location": [2, 2, 1],
|
"location": [-2, 0, 1]
|
||||||
"scale": [1, 1, 1]
|
})
|
||||||
})
|
objects_created.append(result["name"])
|
||||||
response += f"✓ Created sphere '{sphere['name']}'\n"
|
except Exception as e:
|
||||||
|
logger.warning(f"Error creating cylinder: {str(e)}")
|
||||||
|
|
||||||
# Set material to red
|
return f"Created scene with objects: {', '.join(objects_created)}"
|
||||||
blender.send_command("set_material", {
|
|
||||||
"object_name": sphere["name"],
|
|
||||||
"color": [0.8, 0.2, 0.2]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Look for cylinders
|
|
||||||
if any(word in parts for word in ["cylinder", "pipe", "tube"]):
|
|
||||||
cylinder = blender.send_command("create_object", {
|
|
||||||
"type": "CYLINDER",
|
|
||||||
"name": "Cylinder",
|
|
||||||
"location": [-2, -2, 1],
|
|
||||||
"scale": [1, 1, 1]
|
|
||||||
})
|
|
||||||
response += f"✓ Created cylinder '{cylinder['name']}'\n"
|
|
||||||
|
|
||||||
# Set material to green
|
|
||||||
blender.send_command("set_material", {
|
|
||||||
"object_name": cylinder["name"],
|
|
||||||
"color": [0.2, 0.8, 0.2]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Add a camera if not already in the scene
|
|
||||||
if not any(obj.get("type") == "CAMERA" for obj in scene_info["objects"]):
|
|
||||||
camera = blender.send_command("create_object", {
|
|
||||||
"type": "CAMERA",
|
|
||||||
"name": "Camera",
|
|
||||||
"location": [7, -7, 5],
|
|
||||||
"rotation": [0.9, 0, 2.6] # Pointing at the origin
|
|
||||||
})
|
|
||||||
response += f"✓ Added camera '{camera['name']}'\n"
|
|
||||||
|
|
||||||
# Add a light if not already in the scene
|
|
||||||
if not any(obj.get("type") == "LIGHT" for obj in scene_info["objects"]):
|
|
||||||
light = blender.send_command("create_object", {
|
|
||||||
"type": "LIGHT",
|
|
||||||
"name": "Light",
|
|
||||||
"location": [4, 1, 6]
|
|
||||||
})
|
|
||||||
response += f"✓ Added light '{light['name']}'\n"
|
|
||||||
|
|
||||||
response += "\nScene created! You can continue to modify it with specific commands."
|
|
||||||
return response
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating scene: {str(e)}")
|
||||||
return f"Error creating scene: {str(e)}"
|
return f"Error creating scene: {str(e)}"
|
||||||
|
|
||||||
# Prompts to help users interact with Blender
|
# Prompts to help users interact with Blender
|
||||||
@ -583,8 +594,6 @@ def add_material() -> str:
|
|||||||
I have a cube in my scene. Can you create a blue metallic material and apply it to the cube?
|
I have a cube in my scene. Can you create a blue metallic material and apply it to the cube?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
61
test_ping.py
Normal file
61
test_ping.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
def test_ping():
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
print("Connecting to Blender...")
|
||||||
|
sock.connect(('localhost', 9876))
|
||||||
|
print("Connected!")
|
||||||
|
|
||||||
|
# Ping command
|
||||||
|
command = {
|
||||||
|
"type": "ping",
|
||||||
|
"params": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Sending command: {json.dumps(command)}")
|
||||||
|
sock.sendall(json.dumps(command).encode('utf-8'))
|
||||||
|
|
||||||
|
print(f"Setting socket timeout: 15 seconds")
|
||||||
|
sock.settimeout(15)
|
||||||
|
|
||||||
|
print("Waiting for response...")
|
||||||
|
try:
|
||||||
|
# Receive data in chunks
|
||||||
|
chunks = []
|
||||||
|
while True:
|
||||||
|
chunk = sock.recv(8192)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
# Try to parse the JSON to see if we have a complete response
|
||||||
|
try:
|
||||||
|
data = b''.join(chunks)
|
||||||
|
json.loads(data.decode('utf-8'))
|
||||||
|
# If we get here, we have a complete response
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Incomplete JSON, continue receiving
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = b''.join(chunks)
|
||||||
|
print(f"Received {len(data)} bytes")
|
||||||
|
|
||||||
|
if data:
|
||||||
|
response = json.loads(data.decode('utf-8'))
|
||||||
|
print(f"Response: {response}")
|
||||||
|
else:
|
||||||
|
print("Received empty response")
|
||||||
|
except socket.timeout:
|
||||||
|
print("Socket timeout while waiting for response")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {type(e).__name__}: {str(e)}")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_ping()
|
||||||
68
test_scene_info.py
Normal file
68
test_scene_info.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
def test_scene_info():
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
print("Connecting to Blender...")
|
||||||
|
sock.connect(('localhost', 9876))
|
||||||
|
print("Connected!")
|
||||||
|
|
||||||
|
# Scene info command
|
||||||
|
command = {
|
||||||
|
"type": "get_scene_info",
|
||||||
|
"params": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Sending command: {json.dumps(command)}")
|
||||||
|
sock.sendall(json.dumps(command).encode('utf-8'))
|
||||||
|
|
||||||
|
print(f"Setting socket timeout: 15 seconds")
|
||||||
|
sock.settimeout(15)
|
||||||
|
|
||||||
|
print("Waiting for response...")
|
||||||
|
try:
|
||||||
|
# Receive data in chunks
|
||||||
|
chunks = []
|
||||||
|
while True:
|
||||||
|
chunk = sock.recv(8192)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
# Try to parse the JSON to see if we have a complete response
|
||||||
|
try:
|
||||||
|
data = b''.join(chunks)
|
||||||
|
json.loads(data.decode('utf-8'))
|
||||||
|
# If we get here, we have a complete response
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Incomplete JSON, continue receiving
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = b''.join(chunks)
|
||||||
|
print(f"Received {len(data)} bytes")
|
||||||
|
|
||||||
|
if data:
|
||||||
|
response = json.loads(data.decode('utf-8'))
|
||||||
|
print(f"Response status: {response.get('status')}")
|
||||||
|
if response.get('status') == 'success':
|
||||||
|
result = response.get('result', {})
|
||||||
|
print(f"Scene name: {result.get('name')}")
|
||||||
|
print(f"Object count: {result.get('object_count')}")
|
||||||
|
print(f"Objects returned: {len(result.get('objects', []))}")
|
||||||
|
else:
|
||||||
|
print(f"Error: {response.get('message')}")
|
||||||
|
else:
|
||||||
|
print("Received empty response")
|
||||||
|
except socket.timeout:
|
||||||
|
print("Socket timeout while waiting for response")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {type(e).__name__}: {str(e)}")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_scene_info()
|
||||||
61
test_simple_info.py
Normal file
61
test_simple_info.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
def test_simple_info():
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
print("Connecting to Blender...")
|
||||||
|
sock.connect(('localhost', 9876))
|
||||||
|
print("Connected!")
|
||||||
|
|
||||||
|
# Simple info command
|
||||||
|
command = {
|
||||||
|
"type": "get_simple_info",
|
||||||
|
"params": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Sending command: {json.dumps(command)}")
|
||||||
|
sock.sendall(json.dumps(command).encode('utf-8'))
|
||||||
|
|
||||||
|
print(f"Setting socket timeout: 15 seconds")
|
||||||
|
sock.settimeout(15)
|
||||||
|
|
||||||
|
print("Waiting for response...")
|
||||||
|
try:
|
||||||
|
# Receive data in chunks
|
||||||
|
chunks = []
|
||||||
|
while True:
|
||||||
|
chunk = sock.recv(8192)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
chunks.append(chunk)
|
||||||
|
|
||||||
|
# Try to parse the JSON to see if we have a complete response
|
||||||
|
try:
|
||||||
|
data = b''.join(chunks)
|
||||||
|
json.loads(data.decode('utf-8'))
|
||||||
|
# If we get here, we have a complete response
|
||||||
|
break
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
# Incomplete JSON, continue receiving
|
||||||
|
continue
|
||||||
|
|
||||||
|
data = b''.join(chunks)
|
||||||
|
print(f"Received {len(data)} bytes")
|
||||||
|
|
||||||
|
if data:
|
||||||
|
response = json.loads(data.decode('utf-8'))
|
||||||
|
print(f"Response: {response}")
|
||||||
|
else:
|
||||||
|
print("Received empty response")
|
||||||
|
except socket.timeout:
|
||||||
|
print("Socket timeout while waiting for response")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {type(e).__name__}: {str(e)}")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_simple_info()
|
||||||
Loading…
Reference in New Issue
Block a user