at least it isnt crashing now? somewhat works!

This commit is contained in:
ahujasid 2025-03-08 00:55:52 +05:30
parent 2efcd9c7f6
commit 31bce0dfbd
3 changed files with 165 additions and 200 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -22,104 +22,124 @@ class BlenderMCPServer:
self.running = False
self.socket = None
self.client = None
self.server_thread = None
self.command_queue = []
self.buffer = b'' # Add buffer for incomplete data
def start(self):
self.running = True
self.server_thread = threading.Thread(target=self._run_server)
self.server_thread.daemon = True
self.server_thread.start()
print(f"BlenderMCP server started on {self.host}:{self.port}")
def stop(self):
self.running = False
if self.socket:
self.socket.close()
if self.client:
self.client.close()
print("BlenderMCP server stopped")
def _run_server(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
self.socket.bind((self.host, self.port))
self.socket.listen(1)
self.socket.settimeout(1.0) # Add a timeout for accept
self.socket.setblocking(False)
# Register the timer
bpy.app.timers.register(self._process_server, persistent=True)
print(f"BlenderMCP server started on {self.host}:{self.port}")
except Exception as e:
print(f"Failed to start server: {str(e)}")
self.stop()
while self.running:
def stop(self):
self.running = False
if hasattr(bpy.app.timers, "unregister"):
if bpy.app.timers.is_registered(self._process_server):
bpy.app.timers.unregister(self._process_server)
if self.socket:
self.socket.close()
if self.client:
self.client.close()
self.socket = None
self.client = None
print("BlenderMCP server stopped")
def _process_server(self):
"""Timer callback to process server operations"""
if not self.running:
return None # Unregister timer
try:
# Accept new connections
if not self.client and self.socket:
try:
self.client, address = self.socket.accept()
self.client.setblocking(False)
print(f"Connected to client: {address}")
while self.running:
try:
# Set a timeout for receiving data
self.client.settimeout(15.0)
data = self.client.recv(8192) # Increased buffer size
if not data:
print("Empty data received, client may have disconnected")
break
try:
print(f"Received data: {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)
# 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:
print(f"Invalid JSON received: {data.decode('utf-8')}")
self.client.sendall(json.dumps({
"status": "error",
"message": "Invalid JSON format"
}).encode('utf-8'))
except Exception as e:
print(f"Error executing command: {str(e)}")
import traceback
traceback.print_exc()
self.client.sendall(json.dumps({
"status": "error",
"message": str(e)
}).encode('utf-8'))
except socket.timeout:
print("Socket timeout while waiting for data")
continue
except Exception as e:
print(f"Error receiving data: {str(e)}")
break
if self.client:
self.client.close()
self.client = None
except socket.timeout:
# This is normal - just continue the loop
continue
except BlockingIOError:
pass # No connection waiting
except Exception as e:
print(f"Connection error: {str(e)}")
print(f"Error accepting connection: {str(e)}")
# Process existing connection
if self.client:
try:
# Try to receive data
try:
data = self.client.recv(8192)
if data:
self.buffer += data
# Try to process complete messages
try:
# Attempt to parse the buffer as JSON
command = json.loads(self.buffer.decode('utf-8'))
# If successful, clear the buffer and process command
self.buffer = b''
response = self.execute_command(command)
response_json = json.dumps(response)
self.client.sendall(response_json.encode('utf-8'))
except json.JSONDecodeError:
# Incomplete data, keep in buffer
pass
else:
# Connection closed by client
print("Client disconnected")
self.client.close()
self.client = None
self.buffer = b''
except BlockingIOError:
pass # No data available
except Exception as e:
print(f"Error receiving data: {str(e)}")
self.client.close()
self.client = None
self.buffer = b''
except Exception as e:
print(f"Error with client: {str(e)}")
if self.client:
self.client.close()
self.client = None
time.sleep(1) # Prevent busy waiting
self.buffer = b''
except Exception as e:
print(f"Server error: {str(e)}")
finally:
if self.socket:
self.socket.close()
return 0.1 # Continue timer with 0.1 second interval
def execute_command(self, command):
"""Execute a Blender command received from the MCP server"""
"""Execute a command in the main Blender thread"""
try:
cmd_type = command.get("type")
params = command.get("params", {})
# Ensure we're in the right context
if cmd_type in ["create_object", "modify_object", "delete_object"]:
override = bpy.context.copy()
override['area'] = [area for area in bpy.context.screen.areas if area.type == 'VIEW_3D'][0]
with bpy.context.temp_override(**override):
return self._execute_command_internal(command)
else:
return self._execute_command_internal(command)
except Exception as e:
print(f"Error executing command: {str(e)}")
import traceback
traceback.print_exc()
return {"status": "error", "message": str(e)}
def _execute_command_internal(self, command):
"""Internal command execution with proper context"""
cmd_type = command.get("type")
params = command.get("params", {})

View File

@ -279,6 +279,66 @@ def get_object_info(object_name: str) -> str:
# Tool endpoints
@mcp.tool()
def create_primitive(
ctx: Context,
type: str = "CUBE",
location: List[float] = None,
color: List[float] = None
) -> str:
"""
Create a basic primitive object in Blender.
Parameters:
- type: Object type (CUBE, SPHERE, CYLINDER, PLANE)
- location: Optional [x, y, z] location coordinates
- color: Optional [R, G, B] color values (0.0-1.0)
"""
try:
blender = get_blender_connection()
loc = location or [0, 0, 0]
# First create the object
params = {
"type": type,
"location": loc
}
result = blender.send_command("create_object", params)
# If color specified, set the material
if color:
blender.send_command("set_material", {
"object_name": result["name"],
"color": color
})
return f"Created {type} at location {loc}"
except Exception as e:
return f"Error creating primitive: {str(e)}"
@mcp.tool()
def set_object_property(
ctx: Context,
name: str,
property: str,
value: Any
) -> str:
"""
Set a single property of an object.
Parameters:
- name: Name of the object
- property: Property to set (location, rotation, scale, color, visible)
- value: New value for the property
"""
try:
blender = get_blender_connection()
params = {"name": name, property: value}
result = blender.send_command("modify_object", params)
return f"Set {property} of {name} to {value}"
except Exception as e:
return f"Error setting property: {str(e)}"
@mcp.tool()
def create_object(
ctx: Context,
@ -469,130 +529,15 @@ def execute_blender_code(ctx: Context, code: str) -> str:
logger.error(f"Error executing code: {str(e)}")
return f"Error executing code: {str(e)}"
@mcp.tool()
def create_3d_scene(ctx: Context, description: str) -> str:
"""
Create a 3D scene based on a text description.
This is a higher-level tool that will interpret the description and create
appropriate objects, materials, and lighting.
Parameters:
- description: Text description of the scene to create
"""
try:
# Get the global connection
blender = get_blender_connection()
# Parse the description and create a scene
# This is a simplified implementation - in a real tool, you would use more
# sophisticated parsing and scene generation logic
# For now, we'll just create a simple scene with a few objects
# Clear existing objects (optional)
try:
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)}")
# Create a simple scene based on the description
objects_created = []
# Add a ground plane
try:
result = blender.send_command("create_object", {
"type": "PLANE",
"name": "Ground",
"location": [0, 0, 0],
"scale": [5, 5, 1]
})
objects_created.append(result["name"])
# Set a material for the ground
blender.send_command("set_material", {
"object_name": "Ground",
"material_name": "GroundMaterial",
"color": [0.8, 0.8, 0.8]
})
except Exception as e:
logger.warning(f"Error creating ground: {str(e)}")
# Simple keyword-based object creation
if "cube" in description.lower():
try:
result = blender.send_command("create_object", {
"type": "CUBE",
"name": "Cube",
"location": [0, 0, 1]
})
objects_created.append(result["name"])
except Exception as e:
logger.warning(f"Error creating cube: {str(e)}")
if "sphere" in description.lower():
try:
result = blender.send_command("create_object", {
"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)}")
if "cylinder" in description.lower():
try:
result = blender.send_command("create_object", {
"type": "CYLINDER",
"name": "Cylinder",
"location": [-2, 0, 1]
})
objects_created.append(result["name"])
except Exception as e:
logger.warning(f"Error creating cylinder: {str(e)}")
return f"Created scene with objects: {', '.join(objects_created)}"
except Exception as e:
logger.error(f"Error creating scene: {str(e)}")
return f"Error creating scene: {str(e)}"
# Prompts to help users interact with Blender
@mcp.prompt()
def create_basic_object() -> str:
"""Create a single object with basic properties"""
return """Create a blue cube at position [0, 1, 0]"""
@mcp.prompt()
def create_simple_scene() -> str:
"""Create a simple Blender scene with basic objects"""
return """
I'd like to create a simple scene in Blender. Please create:
1. A ground plane
2. A cube above the ground
3. A sphere to the side
4. Make sure there's a camera and light
5. Set different colors for the objects
"""
@mcp.prompt()
def animate_object() -> str:
"""Create keyframe animation for an object"""
return """
I want to animate a cube moving from point A to point B over 30 frames.
Can you help me create this animation?
"""
@mcp.prompt()
def add_material() -> str:
"""Add a material to an object"""
return """
I have a cube in my scene. Can you create a blue metallic material and apply it to the cube?
"""
def modify_basic_object() -> str:
"""Modify a single property of an object"""
return """Make the cube red"""
# Main execution