Compare commits
No commits in common. "a6ca7509d1bc47dfab546cc46bc966cbe79d54a0" and "de65ad64eeca6437ef030cfdaa04dc5ac049a61c" have entirely different histories.
a6ca7509d1
...
de65ad64ee
22
README.md
22
README.md
@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
BlenderMCP connects Blender to Claude AI through the Model Context Protocol (MCP), allowing Claude to directly interact with and control Blender. This integration enables prompt assisted 3D modeling, scene creation, and manipulation.
|
BlenderMCP connects Blender to Claude AI through the Model Context Protocol (MCP), allowing Claude to directly interact with and control Blender. This integration enables prompt assisted 3D modeling, scene creation, and manipulation.
|
||||||
|
|
||||||
**We have no official website. Any website you see online is unofficial and has no affiliation with this project. Use them at your own risk.**
|
|
||||||
|
|
||||||
[Full tutorial](https://www.youtube.com/watch?v=lCyQ717DuzQ)
|
[Full tutorial](https://www.youtube.com/watch?v=lCyQ717DuzQ)
|
||||||
|
|
||||||
### Join the Community
|
### Join the Community
|
||||||
@ -32,8 +30,6 @@ Give feedback, get inspired, and build on top of the MCP: [Discord](https://disc
|
|||||||
|
|
||||||
[CodeRabbit](https://www.coderabbit.ai/)
|
[CodeRabbit](https://www.coderabbit.ai/)
|
||||||
|
|
||||||
[Satish Goda](https://github.com/satishgoda)
|
|
||||||
|
|
||||||
**All supporters:**
|
**All supporters:**
|
||||||
|
|
||||||
[Support this project](https://github.com/sponsors/ahujasid)
|
[Support this project](https://github.com/sponsors/ahujasid)
|
||||||
@ -91,18 +87,6 @@ Otherwise installation instructions are on their website: [Install uv](https://d
|
|||||||
|
|
||||||
**⚠️ Do not proceed before installing UV**
|
**⚠️ Do not proceed before installing UV**
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
|
|
||||||
The following environment variables can be used to configure the Blender connection:
|
|
||||||
|
|
||||||
- `BLENDER_HOST`: Host address for Blender socket server (default: "localhost")
|
|
||||||
- `BLENDER_PORT`: Port number for Blender socket server (default: 9876)
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```bash
|
|
||||||
export BLENDER_HOST='host.docker.internal'
|
|
||||||
export BLENDER_PORT=9876
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude for Desktop Integration
|
### Claude for Desktop Integration
|
||||||
|
|
||||||
@ -167,12 +151,6 @@ For Windows users, go to Settings > MCP > Add Server, add a new server with the
|
|||||||
|
|
||||||
**⚠️ Only run one instance of the MCP server (either on Cursor or Claude Desktop), not both**
|
**⚠️ Only run one instance of the MCP server (either on Cursor or Claude Desktop), not both**
|
||||||
|
|
||||||
### Visual Studio Code Integration
|
|
||||||
|
|
||||||
_Prerequisites_: Make sure you have [Visual Studio Code](https://code.visualstudio.com/) installed before proceeding.
|
|
||||||
|
|
||||||
[](vscode:mcp/install?%7B%22name%22%3A%22blender-mcp%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22blender-mcp%22%5D%7D)
|
|
||||||
|
|
||||||
### Installing the Blender Addon
|
### Installing the Blender Addon
|
||||||
|
|
||||||
1. Download the `addon.py` file from this repo
|
1. Download the `addon.py` file from this repo
|
||||||
|
|||||||
18
addon.py
18
addon.py
@ -28,10 +28,6 @@ bl_info = {
|
|||||||
|
|
||||||
RODIN_FREE_TRIAL_KEY = "k9TcfFoEhNd9cCPP2guHAHHHkctZHIRhZDywZ1euGUXwihbYLpOjQhofby80NJez"
|
RODIN_FREE_TRIAL_KEY = "k9TcfFoEhNd9cCPP2guHAHHHkctZHIRhZDywZ1euGUXwihbYLpOjQhofby80NJez"
|
||||||
|
|
||||||
# Add User-Agent as required by Poly Haven API
|
|
||||||
REQ_HEADERS = requests.utils.default_headers()
|
|
||||||
REQ_HEADERS.update({"User-Agent": "blender-mcp"})
|
|
||||||
|
|
||||||
class BlenderMCPServer:
|
class BlenderMCPServer:
|
||||||
def __init__(self, host='localhost', port=9876):
|
def __init__(self, host='localhost', port=9876):
|
||||||
self.host = host
|
self.host = host
|
||||||
@ -427,7 +423,7 @@ class BlenderMCPServer:
|
|||||||
if asset_type not in ["hdris", "textures", "models", "all"]:
|
if asset_type not in ["hdris", "textures", "models", "all"]:
|
||||||
return {"error": f"Invalid asset type: {asset_type}. Must be one of: hdris, textures, models, all"}
|
return {"error": f"Invalid asset type: {asset_type}. Must be one of: hdris, textures, models, all"}
|
||||||
|
|
||||||
response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}", headers=REQ_HEADERS)
|
response = requests.get(f"https://api.polyhaven.com/categories/{asset_type}")
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return {"categories": response.json()}
|
return {"categories": response.json()}
|
||||||
else:
|
else:
|
||||||
@ -449,7 +445,7 @@ class BlenderMCPServer:
|
|||||||
if categories:
|
if categories:
|
||||||
params["categories"] = categories
|
params["categories"] = categories
|
||||||
|
|
||||||
response = requests.get(url, params=params, headers=REQ_HEADERS)
|
response = requests.get(url, params=params)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Limit the response size to avoid overwhelming Blender
|
# Limit the response size to avoid overwhelming Blender
|
||||||
assets = response.json()
|
assets = response.json()
|
||||||
@ -469,7 +465,7 @@ class BlenderMCPServer:
|
|||||||
def download_polyhaven_asset(self, asset_id, asset_type, resolution="1k", file_format=None):
|
def download_polyhaven_asset(self, asset_id, asset_type, resolution="1k", file_format=None):
|
||||||
try:
|
try:
|
||||||
# First get the files information
|
# First get the files information
|
||||||
files_response = requests.get(f"https://api.polyhaven.com/files/{asset_id}", headers=REQ_HEADERS)
|
files_response = requests.get(f"https://api.polyhaven.com/files/{asset_id}")
|
||||||
if files_response.status_code != 200:
|
if files_response.status_code != 200:
|
||||||
return {"error": f"Failed to get asset files: {files_response.status_code}"}
|
return {"error": f"Failed to get asset files: {files_response.status_code}"}
|
||||||
|
|
||||||
@ -489,7 +485,7 @@ class BlenderMCPServer:
|
|||||||
# since Blender can't properly load HDR data directly from memory
|
# since Blender can't properly load HDR data directly from memory
|
||||||
with tempfile.NamedTemporaryFile(suffix=f".{file_format}", delete=False) as tmp_file:
|
with tempfile.NamedTemporaryFile(suffix=f".{file_format}", delete=False) as tmp_file:
|
||||||
# Download the file
|
# Download the file
|
||||||
response = requests.get(file_url, headers=REQ_HEADERS)
|
response = requests.get(file_url)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
return {"error": f"Failed to download HDRI: {response.status_code}"}
|
return {"error": f"Failed to download HDRI: {response.status_code}"}
|
||||||
|
|
||||||
@ -585,7 +581,7 @@ class BlenderMCPServer:
|
|||||||
# Use NamedTemporaryFile like we do for HDRIs
|
# Use NamedTemporaryFile like we do for HDRIs
|
||||||
with tempfile.NamedTemporaryFile(suffix=f".{file_format}", delete=False) as tmp_file:
|
with tempfile.NamedTemporaryFile(suffix=f".{file_format}", delete=False) as tmp_file:
|
||||||
# Download the file
|
# Download the file
|
||||||
response = requests.get(file_url, headers=REQ_HEADERS)
|
response = requests.get(file_url)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
tmp_file.write(response.content)
|
tmp_file.write(response.content)
|
||||||
tmp_path = tmp_file.name
|
tmp_path = tmp_file.name
|
||||||
@ -722,7 +718,7 @@ class BlenderMCPServer:
|
|||||||
main_file_name = file_url.split("/")[-1]
|
main_file_name = file_url.split("/")[-1]
|
||||||
main_file_path = os.path.join(temp_dir, main_file_name)
|
main_file_path = os.path.join(temp_dir, main_file_name)
|
||||||
|
|
||||||
response = requests.get(file_url, headers=REQ_HEADERS)
|
response = requests.get(file_url)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
return {"error": f"Failed to download model: {response.status_code}"}
|
return {"error": f"Failed to download model: {response.status_code}"}
|
||||||
|
|
||||||
@ -740,7 +736,7 @@ class BlenderMCPServer:
|
|||||||
os.makedirs(os.path.dirname(include_file_path), exist_ok=True)
|
os.makedirs(os.path.dirname(include_file_path), exist_ok=True)
|
||||||
|
|
||||||
# Download the included file
|
# Download the included file
|
||||||
include_response = requests.get(include_url, headers=REQ_HEADERS)
|
include_response = requests.get(include_url)
|
||||||
if include_response.status_code == 200:
|
if include_response.status_code == 200:
|
||||||
with open(include_file_path, "wb") as f:
|
with open(include_file_path, "wb") as f:
|
||||||
f.write(include_response.content)
|
f.write(include_response.content)
|
||||||
|
|||||||
@ -18,10 +18,6 @@ logging.basicConfig(level=logging.INFO,
|
|||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
logger = logging.getLogger("BlenderMCPServer")
|
logger = logging.getLogger("BlenderMCPServer")
|
||||||
|
|
||||||
# Default configuration
|
|
||||||
DEFAULT_HOST = "localhost"
|
|
||||||
DEFAULT_PORT = 9876
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BlenderConnection:
|
class BlenderConnection:
|
||||||
host: str
|
host: str
|
||||||
@ -229,9 +225,7 @@ def get_blender_connection():
|
|||||||
|
|
||||||
# Create a new connection if needed
|
# Create a new connection if needed
|
||||||
if _blender_connection is None:
|
if _blender_connection is None:
|
||||||
host = os.getenv("BLENDER_HOST", DEFAULT_HOST)
|
_blender_connection = BlenderConnection(host="localhost", port=9876)
|
||||||
port = int(os.getenv("BLENDER_PORT", DEFAULT_PORT))
|
|
||||||
_blender_connection = BlenderConnection(host=host, port=port)
|
|
||||||
if not _blender_connection.connect():
|
if not _blender_connection.connect():
|
||||||
logger.error("Failed to connect to Blender")
|
logger.error("Failed to connect to Blender")
|
||||||
_blender_connection = None
|
_blender_connection = None
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user