Merge branch 'ahujasid:main' into rodin-intergration
This commit is contained in:
commit
648ed244fb
33
README.md
33
README.md
@ -5,6 +5,7 @@ BlenderMCP connects Blender to Claude AI through the Model Context Protocol (MCP
|
|||||||
## Release notes (1.1.0)
|
## Release notes (1.1.0)
|
||||||
|
|
||||||
- Added support for Poly Haven assets through their API
|
- Added support for Poly Haven assets through their API
|
||||||
|
- Added support to prompt 3D models using Hyper3D Rodin
|
||||||
- For newcomers, you can go straight to Installation. For existing users, see the points below
|
- For newcomers, you can go straight to Installation. For existing users, see the points below
|
||||||
- Download the latest addon.py file and replace the older one, then add it to Blender
|
- Download the latest addon.py file and replace the older one, then add it to Blender
|
||||||
- Delete the MCP server from Claude and add it back again, and you should be good to go!
|
- Delete the MCP server from Claude and add it back again, and you should be good to go!
|
||||||
@ -33,13 +34,17 @@ The system consists of two main components:
|
|||||||
- Python 3.10 or newer
|
- Python 3.10 or newer
|
||||||
- uv package manager:
|
- uv package manager:
|
||||||
|
|
||||||
If you're on Mac, please install uv as
|
**If you're on Mac, please install uv as**
|
||||||
```bash
|
```bash
|
||||||
brew install uv
|
brew install uv
|
||||||
```
|
```
|
||||||
On Windows
|
**On Windows**
|
||||||
```bash
|
```bash
|
||||||
pip install uv
|
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||||
|
```
|
||||||
|
and then
|
||||||
|
```bash
|
||||||
|
set Path=C:\Users\nntra\.local\bin;%Path%
|
||||||
```
|
```
|
||||||
|
|
||||||
Otherwise installation instructions are on their website: [Install uv](https://docs.astral.sh/uv/getting-started/installation/)
|
Otherwise installation instructions are on their website: [Install uv](https://docs.astral.sh/uv/getting-started/installation/)
|
||||||
@ -104,22 +109,15 @@ Once the config file has been set on Claude, and the addon is running on Blender
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
#### Tools
|
#### Capabilities
|
||||||
|
|
||||||
- `get_scene_info` - Gets scene information
|
- Get scene and object information
|
||||||
- `get_object_info` - Gets detailed information for a specific object in the scene
|
- Create, delete and modify shapes
|
||||||
- `create_primitive` - Create basic primitive objects with optional color
|
- Apply or create materials for objects
|
||||||
- `set_object_property` - Set a single property of an object
|
- Execute any Python code in Blender
|
||||||
- `create_object` - Create a new object with detailed parameters
|
- Download the right models, assets and HDRIs through [Poly Haven](https://polyhaven.com/)
|
||||||
- `modify_object` - Modify an existing object's properties
|
- AI generated 3D models through [Hyper3D Rodin](https://hyper3d.ai/)
|
||||||
- `delete_object` - Remove an object from the scene
|
|
||||||
- `set_material` - Apply or create materials for objects
|
|
||||||
- `execute_blender_code` - Run any Python code in Blender
|
|
||||||
- `get_polyhaven_categories` - Get a list of categories for PolyHaven assets (HDRIs, textures, models)
|
|
||||||
- `search_polyhaven_assets` - Search for assets on PolyHaven with optional category filtering
|
|
||||||
- `download_polyhaven_asset` - Download and import a PolyHaven asset into Blender
|
|
||||||
|
|
||||||
To see everything in Poly Haven, [see here](https://polyhaven.com/)
|
|
||||||
|
|
||||||
### Example Commands
|
### Example Commands
|
||||||
|
|
||||||
@ -128,6 +126,7 @@ Here are some examples of what you can ask Claude to do:
|
|||||||
- "Create a low poly scene in a dungeon, with a dragon guarding a pot of gold" [Demo](https://www.youtube.com/watch?v=DqgKuLYUv00)
|
- "Create a low poly scene in a dungeon, with a dragon guarding a pot of gold" [Demo](https://www.youtube.com/watch?v=DqgKuLYUv00)
|
||||||
- "Create a beach vibe using HDRIs, textures, and models like rocks and vegetation from Poly Haven" [Demo](https://www.youtube.com/watch?v=I29rn92gkC4)
|
- "Create a beach vibe using HDRIs, textures, and models like rocks and vegetation from Poly Haven" [Demo](https://www.youtube.com/watch?v=I29rn92gkC4)
|
||||||
- Give a reference image, and create a Blender scene out of it [Demo](https://www.youtube.com/watch?v=FDRb03XPiRo)
|
- Give a reference image, and create a Blender scene out of it [Demo](https://www.youtube.com/watch?v=FDRb03XPiRo)
|
||||||
|
- "Generate a 3D model of a garden gnome through Hyper3D"
|
||||||
- "Get information about the current scene, and make a threejs sketch from it" [Demo](https://www.youtube.com/watch?v=jxbNI5L7AH8)
|
- "Get information about the current scene, and make a threejs sketch from it" [Demo](https://www.youtube.com/watch?v=jxbNI5L7AH8)
|
||||||
- "Make this car red and metallic"
|
- "Make this car red and metallic"
|
||||||
- "Create a sphere and place it above the cube"
|
- "Create a sphere and place it above the cube"
|
||||||
|
|||||||
132
addon.py
132
addon.py
@ -268,63 +268,81 @@ class BlenderMCPServer:
|
|||||||
align="WORLD", major_segments=48, minor_segments=12, mode="MAJOR_MINOR",
|
align="WORLD", major_segments=48, minor_segments=12, mode="MAJOR_MINOR",
|
||||||
major_radius=1.0, minor_radius=0.25, abso_major_rad=1.25, abso_minor_rad=0.75, generate_uvs=True):
|
major_radius=1.0, minor_radius=0.25, abso_major_rad=1.25, abso_minor_rad=0.75, generate_uvs=True):
|
||||||
"""Create a new object in the scene"""
|
"""Create a new object in the scene"""
|
||||||
# Deselect all objects
|
try:
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
# Deselect all objects first
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
if type == "CUBE":
|
|
||||||
bpy.ops.mesh.primitive_cube_add(location=location, rotation=rotation, scale=scale)
|
# Create the object based on type
|
||||||
elif type == "SPHERE":
|
if type == "CUBE":
|
||||||
bpy.ops.mesh.primitive_uv_sphere_add(location=location, rotation=rotation, scale=scale)
|
bpy.ops.mesh.primitive_cube_add(location=location, rotation=rotation, scale=scale)
|
||||||
elif type == "CYLINDER":
|
elif type == "SPHERE":
|
||||||
bpy.ops.mesh.primitive_cylinder_add(location=location, rotation=rotation, scale=scale)
|
bpy.ops.mesh.primitive_uv_sphere_add(location=location, rotation=rotation, scale=scale)
|
||||||
elif type == "PLANE":
|
elif type == "CYLINDER":
|
||||||
bpy.ops.mesh.primitive_plane_add(location=location, rotation=rotation, scale=scale)
|
bpy.ops.mesh.primitive_cylinder_add(location=location, rotation=rotation, scale=scale)
|
||||||
elif type == "CONE":
|
elif type == "PLANE":
|
||||||
bpy.ops.mesh.primitive_cone_add(location=location, rotation=rotation, scale=scale)
|
bpy.ops.mesh.primitive_plane_add(location=location, rotation=rotation, scale=scale)
|
||||||
elif type == "TORUS":
|
elif type == "CONE":
|
||||||
bpy.ops.mesh.primitive_torus_add(
|
bpy.ops.mesh.primitive_cone_add(location=location, rotation=rotation, scale=scale)
|
||||||
align=align,
|
elif type == "TORUS":
|
||||||
location=location,
|
bpy.ops.mesh.primitive_torus_add(
|
||||||
rotation=rotation,
|
align=align,
|
||||||
major_segments=major_segments,
|
location=location,
|
||||||
minor_segments=minor_segments,
|
rotation=rotation,
|
||||||
mode=mode,
|
major_segments=major_segments,
|
||||||
major_radius=major_radius,
|
minor_segments=minor_segments,
|
||||||
minor_radius=minor_radius,
|
mode=mode,
|
||||||
abso_major_rad=abso_major_rad,
|
major_radius=major_radius,
|
||||||
abso_minor_rad=abso_minor_rad,
|
minor_radius=minor_radius,
|
||||||
generate_uvs=generate_uvs
|
abso_major_rad=abso_major_rad,
|
||||||
)
|
abso_minor_rad=abso_minor_rad,
|
||||||
elif type == "EMPTY":
|
generate_uvs=generate_uvs
|
||||||
bpy.ops.object.empty_add(location=location, rotation=rotation, scale=scale)
|
)
|
||||||
elif type == "CAMERA":
|
elif type == "EMPTY":
|
||||||
bpy.ops.object.camera_add(location=location, rotation=rotation)
|
bpy.ops.object.empty_add(location=location, rotation=rotation, scale=scale)
|
||||||
elif type == "LIGHT":
|
elif type == "CAMERA":
|
||||||
bpy.ops.object.light_add(type='POINT', location=location, rotation=rotation, scale=scale)
|
bpy.ops.object.camera_add(location=location, rotation=rotation)
|
||||||
else:
|
elif type == "LIGHT":
|
||||||
raise ValueError(f"Unsupported object type: {type}")
|
bpy.ops.object.light_add(type='POINT', location=location, rotation=rotation, scale=scale)
|
||||||
|
else:
|
||||||
# Get the created object
|
raise ValueError(f"Unsupported object type: {type}")
|
||||||
bpy.context.view_layer.update()
|
|
||||||
obj = bpy.context.view_layer.objects.active
|
# Force update the view layer
|
||||||
|
bpy.context.view_layer.update()
|
||||||
# Rename the object if a name is provided
|
|
||||||
if name:
|
# Get the active object (which should be our newly created object)
|
||||||
obj.name = name
|
obj = bpy.context.view_layer.objects.active
|
||||||
|
|
||||||
result = {
|
# If we don't have an active object, something went wrong
|
||||||
"name": obj.name,
|
if obj is None:
|
||||||
"type": obj.type,
|
raise RuntimeError("Failed to create object - no active object")
|
||||||
"location": [obj.location.x, obj.location.y, obj.location.z],
|
|
||||||
"rotation": [obj.rotation_euler.x, obj.rotation_euler.y, obj.rotation_euler.z],
|
# Make sure it's selected
|
||||||
"scale": [obj.scale.x, obj.scale.y, obj.scale.z],
|
obj.select_set(True)
|
||||||
}
|
|
||||||
|
# Rename if name is provided
|
||||||
if obj.type == "MESH":
|
if name:
|
||||||
bounding_box = self._get_aabb(obj)
|
obj.name = name
|
||||||
result["world_bounding_box"] = bounding_box
|
if obj.data:
|
||||||
|
obj.data.name = name
|
||||||
return result
|
|
||||||
|
# Return the object info
|
||||||
|
result = {
|
||||||
|
"name": obj.name,
|
||||||
|
"type": obj.type,
|
||||||
|
"location": [obj.location.x, obj.location.y, obj.location.z],
|
||||||
|
"rotation": [obj.rotation_euler.x, obj.rotation_euler.y, obj.rotation_euler.z],
|
||||||
|
"scale": [obj.scale.x, obj.scale.y, obj.scale.z],
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj.type == "MESH":
|
||||||
|
bounding_box = self._get_aabb(obj)
|
||||||
|
result["world_bounding_box"] = bounding_box
|
||||||
|
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in create_object: {str(e)}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return {"error": str(e)}
|
||||||
|
|
||||||
def modify_object(self, name, location=None, rotation=None, scale=None, visible=None):
|
def modify_object(self, name, location=None, rotation=None, scale=None, visible=None):
|
||||||
"""Modify an existing object in the scene"""
|
"""Modify an existing object in the scene"""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user