code refactoring: removed create, delete, modify functions
This commit is contained in:
parent
9b3b327853
commit
e0e8095892
267
addon.py
267
addon.py
@ -1,3 +1,5 @@
|
|||||||
|
# Code created by Siddharth Ahuja: www.github.com/ahujasid © 2025
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
import json
|
import json
|
||||||
@ -14,7 +16,7 @@ from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Blender MCP",
|
"name": "Blender MCP",
|
||||||
"author": "BlenderMCP",
|
"author": "BlenderMCP",
|
||||||
"version": (0, 2),
|
"version": (1, 2),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "View3D > Sidebar > BlenderMCP",
|
"location": "View3D > Sidebar > BlenderMCP",
|
||||||
"description": "Connect Blender to Claude via MCP",
|
"description": "Connect Blender to Claude via MCP",
|
||||||
@ -172,18 +174,8 @@ class BlenderMCPServer:
|
|||||||
|
|
||||||
def execute_command(self, command):
|
def execute_command(self, command):
|
||||||
"""Execute a command in the main Blender thread"""
|
"""Execute a command in the main Blender thread"""
|
||||||
try:
|
try:
|
||||||
cmd_type = command.get("type")
|
return self._execute_command_internal(command)
|
||||||
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:
|
except Exception as e:
|
||||||
print(f"Error executing command: {str(e)}")
|
print(f"Error executing command: {str(e)}")
|
||||||
@ -202,12 +194,8 @@ class BlenderMCPServer:
|
|||||||
# Base handlers that are always available
|
# Base handlers that are always available
|
||||||
handlers = {
|
handlers = {
|
||||||
"get_scene_info": self.get_scene_info,
|
"get_scene_info": self.get_scene_info,
|
||||||
"create_object": self.create_object,
|
|
||||||
"modify_object": self.modify_object,
|
|
||||||
"delete_object": self.delete_object,
|
|
||||||
"get_object_info": self.get_object_info,
|
"get_object_info": self.get_object_info,
|
||||||
"execute_code": self.execute_code,
|
"execute_code": self.execute_code,
|
||||||
"set_material": self.set_material,
|
|
||||||
"get_polyhaven_status": self.get_polyhaven_status,
|
"get_polyhaven_status": self.get_polyhaven_status,
|
||||||
"get_hyper3d_status": self.get_hyper3d_status,
|
"get_hyper3d_status": self.get_hyper3d_status,
|
||||||
}
|
}
|
||||||
@ -246,13 +234,6 @@ class BlenderMCPServer:
|
|||||||
return {"status": "error", "message": f"Unknown command type: {cmd_type}"}
|
return {"status": "error", "message": f"Unknown command type: {cmd_type}"}
|
||||||
|
|
||||||
|
|
||||||
def get_simple_info(self):
|
|
||||||
"""Get basic Blender information"""
|
|
||||||
return {
|
|
||||||
"blender_version": ".".join(str(v) for v in bpy.app.version),
|
|
||||||
"scene_name": bpy.context.scene.name,
|
|
||||||
"object_count": len(bpy.context.scene.objects)
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_scene_info(self):
|
def get_scene_info(self):
|
||||||
"""Get information about the current Blender scene"""
|
"""Get information about the current Blender scene"""
|
||||||
@ -308,140 +289,7 @@ class BlenderMCPServer:
|
|||||||
[*min_corner], [*max_corner]
|
[*min_corner], [*max_corner]
|
||||||
]
|
]
|
||||||
|
|
||||||
def create_object(self, type="CUBE", name=None, location=(0, 0, 0), rotation=(0, 0, 0), scale=(1, 1, 1),
|
|
||||||
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):
|
|
||||||
"""Create a new object in the scene"""
|
|
||||||
try:
|
|
||||||
# Deselect all objects first
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
|
||||||
|
|
||||||
# Create the object based on type
|
|
||||||
if type == "CUBE":
|
|
||||||
bpy.ops.mesh.primitive_cube_add(location=location, rotation=rotation, scale=scale)
|
|
||||||
elif type == "SPHERE":
|
|
||||||
bpy.ops.mesh.primitive_uv_sphere_add(location=location, rotation=rotation, scale=scale)
|
|
||||||
elif type == "CYLINDER":
|
|
||||||
bpy.ops.mesh.primitive_cylinder_add(location=location, rotation=rotation, scale=scale)
|
|
||||||
elif type == "PLANE":
|
|
||||||
bpy.ops.mesh.primitive_plane_add(location=location, rotation=rotation, scale=scale)
|
|
||||||
elif type == "CONE":
|
|
||||||
bpy.ops.mesh.primitive_cone_add(location=location, rotation=rotation, scale=scale)
|
|
||||||
elif type == "TORUS":
|
|
||||||
bpy.ops.mesh.primitive_torus_add(
|
|
||||||
align=align,
|
|
||||||
location=location,
|
|
||||||
rotation=rotation,
|
|
||||||
major_segments=major_segments,
|
|
||||||
minor_segments=minor_segments,
|
|
||||||
mode=mode,
|
|
||||||
major_radius=major_radius,
|
|
||||||
minor_radius=minor_radius,
|
|
||||||
abso_major_rad=abso_major_rad,
|
|
||||||
abso_minor_rad=abso_minor_rad,
|
|
||||||
generate_uvs=generate_uvs
|
|
||||||
)
|
|
||||||
elif type == "EMPTY":
|
|
||||||
bpy.ops.object.empty_add(location=location, rotation=rotation, scale=scale)
|
|
||||||
elif type == "CAMERA":
|
|
||||||
bpy.ops.object.camera_add(location=location, rotation=rotation)
|
|
||||||
elif type == "LIGHT":
|
|
||||||
bpy.ops.object.light_add(type='POINT', location=location, rotation=rotation, scale=scale)
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unsupported object type: {type}")
|
|
||||||
|
|
||||||
# Force update the view layer
|
|
||||||
bpy.context.view_layer.update()
|
|
||||||
|
|
||||||
# Get the active object (which should be our newly created object)
|
|
||||||
obj = bpy.context.view_layer.objects.active
|
|
||||||
|
|
||||||
# If we don't have an active object, something went wrong
|
|
||||||
if obj is None:
|
|
||||||
raise RuntimeError("Failed to create object - no active object")
|
|
||||||
|
|
||||||
# Make sure it's selected
|
|
||||||
obj.select_set(True)
|
|
||||||
|
|
||||||
# Rename if name is provided
|
|
||||||
if name:
|
|
||||||
obj.name = name
|
|
||||||
if obj.data:
|
|
||||||
obj.data.name = name
|
|
||||||
|
|
||||||
# Patch for PLANE: scale don't work with bpy.ops.mesh.primitive_plane_add()
|
|
||||||
if type in {"PLANE"}:
|
|
||||||
obj.scale = scale
|
|
||||||
|
|
||||||
# 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):
|
|
||||||
"""Modify an existing object in the scene"""
|
|
||||||
# Find the object by name
|
|
||||||
obj = bpy.data.objects.get(name)
|
|
||||||
if not obj:
|
|
||||||
raise ValueError(f"Object not found: {name}")
|
|
||||||
|
|
||||||
# Modify properties as requested
|
|
||||||
if location is not None:
|
|
||||||
obj.location = location
|
|
||||||
|
|
||||||
if rotation is not None:
|
|
||||||
obj.rotation_euler = rotation
|
|
||||||
|
|
||||||
if scale is not None:
|
|
||||||
obj.scale = scale
|
|
||||||
|
|
||||||
if visible is not None:
|
|
||||||
obj.hide_viewport = not visible
|
|
||||||
obj.hide_render = not visible
|
|
||||||
|
|
||||||
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],
|
|
||||||
"visible": obj.visible_get(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj.type == "MESH":
|
|
||||||
bounding_box = self._get_aabb(obj)
|
|
||||||
result["world_bounding_box"] = bounding_box
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def delete_object(self, name):
|
|
||||||
"""Delete an object from the scene"""
|
|
||||||
obj = bpy.data.objects.get(name)
|
|
||||||
if not obj:
|
|
||||||
raise ValueError(f"Object not found: {name}")
|
|
||||||
|
|
||||||
# Store the name to return
|
|
||||||
obj_name = obj.name
|
|
||||||
|
|
||||||
# Select and delete the object
|
|
||||||
if obj:
|
|
||||||
bpy.data.objects.remove(obj, do_unlink=True)
|
|
||||||
|
|
||||||
return {"deleted": obj_name}
|
|
||||||
|
|
||||||
def get_object_info(self, name):
|
def get_object_info(self, name):
|
||||||
"""Get detailed information about a specific object"""
|
"""Get detailed information about a specific object"""
|
||||||
@ -491,108 +339,7 @@ class BlenderMCPServer:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Code execution error: {str(e)}")
|
raise Exception(f"Code execution error: {str(e)}")
|
||||||
|
|
||||||
def set_material(self, object_name, material_name=None, create_if_missing=True, color=None):
|
|
||||||
"""Set or create a material for an object"""
|
|
||||||
try:
|
|
||||||
# Get the object
|
|
||||||
obj = bpy.data.objects.get(object_name)
|
|
||||||
if not obj:
|
|
||||||
raise ValueError(f"Object not found: {object_name}")
|
|
||||||
|
|
||||||
# Make sure object can accept materials
|
|
||||||
if not hasattr(obj, 'data') or not hasattr(obj.data, 'materials'):
|
|
||||||
raise ValueError(f"Object {object_name} cannot accept materials")
|
|
||||||
|
|
||||||
# Create or get material
|
|
||||||
if material_name:
|
|
||||||
mat = bpy.data.materials.get(material_name)
|
|
||||||
if not mat and create_if_missing:
|
|
||||||
mat = bpy.data.materials.new(name=material_name)
|
|
||||||
print(f"Created new material: {material_name}")
|
|
||||||
else:
|
|
||||||
# Generate unique material name if none provided
|
|
||||||
mat_name = f"{object_name}_material"
|
|
||||||
mat = bpy.data.materials.get(mat_name)
|
|
||||||
if not mat:
|
|
||||||
mat = bpy.data.materials.new(name=mat_name)
|
|
||||||
material_name = mat_name
|
|
||||||
print(f"Using material: {mat_name}")
|
|
||||||
|
|
||||||
# Set up material nodes if needed
|
|
||||||
if mat:
|
|
||||||
if not mat.use_nodes:
|
|
||||||
mat.use_nodes = True
|
|
||||||
|
|
||||||
# Get or create Principled BSDF
|
|
||||||
principled = mat.node_tree.nodes.get('Principled BSDF')
|
|
||||||
if not principled:
|
|
||||||
principled = mat.node_tree.nodes.new('ShaderNodeBsdfPrincipled')
|
|
||||||
# Get or create Material Output
|
|
||||||
output = mat.node_tree.nodes.get('Material Output')
|
|
||||||
if not output:
|
|
||||||
output = mat.node_tree.nodes.new('ShaderNodeOutputMaterial')
|
|
||||||
# Link if not already linked
|
|
||||||
if not principled.outputs[0].links:
|
|
||||||
mat.node_tree.links.new(principled.outputs[0], output.inputs[0])
|
|
||||||
|
|
||||||
# Set color if provided
|
|
||||||
if color and len(color) >= 3:
|
|
||||||
principled.inputs['Base Color'].default_value = (
|
|
||||||
color[0],
|
|
||||||
color[1],
|
|
||||||
color[2],
|
|
||||||
1.0 if len(color) < 4 else color[3]
|
|
||||||
)
|
|
||||||
print(f"Set material color to {color}")
|
|
||||||
|
|
||||||
# Assign material to object if not already assigned
|
|
||||||
if mat:
|
|
||||||
if not obj.data.materials:
|
|
||||||
obj.data.materials.append(mat)
|
|
||||||
else:
|
|
||||||
# Only modify first material slot
|
|
||||||
obj.data.materials[0] = mat
|
|
||||||
|
|
||||||
print(f"Assigned material {mat.name} to object {object_name}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "success",
|
|
||||||
"object": object_name,
|
|
||||||
"material": mat.name,
|
|
||||||
"color": color if color else None
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Failed to create or find material: {material_name}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error in set_material: {str(e)}")
|
|
||||||
traceback.print_exc()
|
|
||||||
return {
|
|
||||||
"status": "error",
|
|
||||||
"message": str(e),
|
|
||||||
"object": object_name,
|
|
||||||
"material": material_name if 'material_name' in locals() else None
|
|
||||||
}
|
|
||||||
|
|
||||||
def render_scene(self, output_path=None, resolution_x=None, resolution_y=None):
|
|
||||||
"""Render the current scene"""
|
|
||||||
if resolution_x is not None:
|
|
||||||
bpy.context.scene.render.resolution_x = resolution_x
|
|
||||||
|
|
||||||
if resolution_y is not None:
|
|
||||||
bpy.context.scene.render.resolution_y = resolution_y
|
|
||||||
|
|
||||||
if output_path:
|
|
||||||
bpy.context.scene.render.filepath = output_path
|
|
||||||
|
|
||||||
# Render the scene
|
|
||||||
bpy.ops.render.render(write_still=bool(output_path))
|
|
||||||
|
|
||||||
return {
|
|
||||||
"rendered": True,
|
|
||||||
"output_path": output_path if output_path else "[not saved]",
|
|
||||||
"resolution": [bpy.context.scene.render.resolution_x, bpy.context.scene.render.resolution_y],
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_polyhaven_categories(self, asset_type):
|
def get_polyhaven_categories(self, asset_type):
|
||||||
"""Get categories for a specific asset type from Polyhaven"""
|
"""Get categories for a specific asset type from Polyhaven"""
|
||||||
@ -1630,9 +1377,9 @@ class BLENDERMCP_PT_Panel(bpy.types.Panel):
|
|||||||
layout.operator("blendermcp.set_hyper3d_free_trial_api_key", text="Set Free Trial API Key")
|
layout.operator("blendermcp.set_hyper3d_free_trial_api_key", text="Set Free Trial API Key")
|
||||||
|
|
||||||
if not scene.blendermcp_server_running:
|
if not scene.blendermcp_server_running:
|
||||||
layout.operator("blendermcp.start_server", text="Start MCP Server")
|
layout.operator("blendermcp.start_server", text="Connect to MCP server")
|
||||||
else:
|
else:
|
||||||
layout.operator("blendermcp.stop_server", text="Stop MCP Server")
|
layout.operator("blendermcp.stop_server", text="Disconnect from MCP server")
|
||||||
layout.label(text=f"Running on port {scene.blendermcp_port}")
|
layout.label(text=f"Running on port {scene.blendermcp_port}")
|
||||||
|
|
||||||
# Operator to set Hyper3D API Key
|
# Operator to set Hyper3D API Key
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "blender-mcp"
|
name = "blender-mcp"
|
||||||
version = "1.1.1"
|
version = "1.1.3"
|
||||||
description = "Blender integration through the Model Context Protocol"
|
description = "Blender integration through the Model Context Protocol"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
@ -268,186 +268,10 @@ def get_object_info(ctx: Context, object_name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
def create_object(
|
|
||||||
ctx: Context,
|
|
||||||
type: str = "CUBE",
|
|
||||||
name: str = None,
|
|
||||||
location: List[float] = None,
|
|
||||||
rotation: List[float] = None,
|
|
||||||
scale: List[float] = None,
|
|
||||||
# Torus-specific parameters
|
|
||||||
align: str = "WORLD",
|
|
||||||
major_segments: int = 48,
|
|
||||||
minor_segments: int = 12,
|
|
||||||
mode: str = "MAJOR_MINOR",
|
|
||||||
major_radius: float = 1.0,
|
|
||||||
minor_radius: float = 0.25,
|
|
||||||
abso_major_rad: float = 1.25,
|
|
||||||
abso_minor_rad: float = 0.75,
|
|
||||||
generate_uvs: bool = True
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Create a new object in the Blender scene.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- type: Object type (CUBE, SPHERE, CYLINDER, PLANE, CONE, TORUS, EMPTY, CAMERA, LIGHT)
|
|
||||||
- name: Optional name for the object
|
|
||||||
- location: Optional [x, y, z] location coordinates
|
|
||||||
- rotation: Optional [x, y, z] rotation in radians
|
|
||||||
- scale: Optional [x, y, z] scale factors (not used for TORUS)
|
|
||||||
|
|
||||||
Torus-specific parameters (only used when type == "TORUS"):
|
|
||||||
- align: How to align the torus ('WORLD', 'VIEW', or 'CURSOR')
|
|
||||||
- major_segments: Number of segments for the main ring
|
|
||||||
- minor_segments: Number of segments for the cross-section
|
|
||||||
- mode: Dimension mode ('MAJOR_MINOR' or 'EXT_INT')
|
|
||||||
- major_radius: Radius from the origin to the center of the cross sections
|
|
||||||
- minor_radius: Radius of the torus' cross section
|
|
||||||
- abso_major_rad: Total exterior radius of the torus
|
|
||||||
- abso_minor_rad: Total interior radius of the torus
|
|
||||||
- generate_uvs: Whether to generate a default UV map
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A message indicating the created object name.
|
|
||||||
"""
|
|
||||||
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 = {
|
|
||||||
"type": type,
|
|
||||||
"location": loc,
|
|
||||||
"rotation": rot,
|
|
||||||
}
|
|
||||||
|
|
||||||
if name:
|
|
||||||
params["name"] = name
|
|
||||||
|
|
||||||
if type == "TORUS":
|
|
||||||
# For torus, the scale is not used.
|
|
||||||
params.update({
|
|
||||||
"align": align,
|
|
||||||
"major_segments": major_segments,
|
|
||||||
"minor_segments": minor_segments,
|
|
||||||
"mode": mode,
|
|
||||||
"major_radius": major_radius,
|
|
||||||
"minor_radius": minor_radius,
|
|
||||||
"abso_major_rad": abso_major_rad,
|
|
||||||
"abso_minor_rad": abso_minor_rad,
|
|
||||||
"generate_uvs": generate_uvs
|
|
||||||
})
|
|
||||||
result = blender.send_command("create_object", params)
|
|
||||||
return f"Created {type} object: {result['name']}"
|
|
||||||
else:
|
|
||||||
# For non-torus objects, include scale
|
|
||||||
params["scale"] = sc
|
|
||||||
result = blender.send_command("create_object", params)
|
|
||||||
return f"Created {type} object: {result['name']}"
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error creating object: {str(e)}")
|
|
||||||
return f"Error creating object: {str(e)}"
|
|
||||||
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
def modify_object(
|
|
||||||
ctx: Context,
|
|
||||||
name: str,
|
|
||||||
location: List[float] = None,
|
|
||||||
rotation: List[float] = None,
|
|
||||||
scale: List[float] = None,
|
|
||||||
visible: bool = None
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Modify an existing object in the Blender scene.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- name: Name of the object to modify
|
|
||||||
- location: Optional [x, y, z] location coordinates
|
|
||||||
- rotation: Optional [x, y, z] rotation in radians
|
|
||||||
- scale: Optional [x, y, z] scale factors
|
|
||||||
- visible: Optional boolean to set visibility
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Get the global connection
|
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
params = {"name": name}
|
|
||||||
|
|
||||||
if location is not None:
|
|
||||||
params["location"] = location
|
|
||||||
if rotation is not None:
|
|
||||||
params["rotation"] = rotation
|
|
||||||
if scale is not None:
|
|
||||||
params["scale"] = scale
|
|
||||||
if visible is not None:
|
|
||||||
params["visible"] = visible
|
|
||||||
|
|
||||||
result = blender.send_command("modify_object", params)
|
|
||||||
return f"Modified object: {result['name']}"
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error modifying object: {str(e)}")
|
|
||||||
return f"Error modifying object: {str(e)}"
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
def delete_object(ctx: Context, name: str) -> str:
|
|
||||||
"""
|
|
||||||
Delete an object from the Blender scene.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- name: Name of the object to delete
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Get the global connection
|
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
result = blender.send_command("delete_object", {"name": name})
|
|
||||||
return f"Deleted object: {name}"
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error deleting object: {str(e)}")
|
|
||||||
return f"Error deleting object: {str(e)}"
|
|
||||||
|
|
||||||
@mcp.tool()
|
|
||||||
def set_material(
|
|
||||||
ctx: Context,
|
|
||||||
object_name: str,
|
|
||||||
material_name: str = None,
|
|
||||||
color: List[float] = None
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
Set or create a material for an object.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- object_name: Name of the object to apply the material to
|
|
||||||
- material_name: Optional name of the material to use or create
|
|
||||||
- color: Optional [R, G, B] color values (0.0-1.0)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# Get the global connection
|
|
||||||
blender = get_blender_connection()
|
|
||||||
|
|
||||||
params = {"object_name": object_name}
|
|
||||||
|
|
||||||
if material_name:
|
|
||||||
params["material_name"] = material_name
|
|
||||||
if color:
|
|
||||||
params["color"] = color
|
|
||||||
|
|
||||||
result = blender.send_command("set_material", params)
|
|
||||||
return f"Applied material to {object_name}: {result.get('material_name', 'unknown')}"
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error setting material: {str(e)}")
|
|
||||||
return f"Error setting material: {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 Python code in Blender.
|
Execute arbitrary Python code in Blender. Make sure to do it step-by-step by breaking it into smaller chunks.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
- code: The Python code to execute
|
- code: The Python code to execute
|
||||||
@ -885,7 +709,7 @@ def asset_creation_strategy() -> str:
|
|||||||
Hyper3D Rodin is good at generating 3D models for single item.
|
Hyper3D Rodin is good at generating 3D models for single item.
|
||||||
So don't try to:
|
So don't try to:
|
||||||
1. Generate the whole scene with one shot
|
1. Generate the whole scene with one shot
|
||||||
2. Generate ground using Rodin
|
2. Generate ground using Hyper3D
|
||||||
3. Generate parts of the items separately and put them together afterwards
|
3. Generate parts of the items separately and put them together afterwards
|
||||||
|
|
||||||
Use get_hyper3d_status() to verify its status
|
Use get_hyper3d_status() to verify its status
|
||||||
@ -907,21 +731,12 @@ def asset_creation_strategy() -> str:
|
|||||||
|
|
||||||
You can reuse assets previous generated by running python code to duplicate the object, without creating another generation task.
|
You can reuse assets previous generated by running python code to duplicate the object, without creating another generation task.
|
||||||
|
|
||||||
2. If all integrations are disabled or when falling back to basic tools:
|
3. Always check the world_bounding_box for each item so that:
|
||||||
- create_object() for basic primitives (CUBE, SPHERE, CYLINDER, etc.)
|
|
||||||
- set_material() for basic colors and materials
|
|
||||||
|
|
||||||
3. When including an object into scene, ALWAYS make sure that the name of the object is meanful.
|
|
||||||
|
|
||||||
4. Always check the world_bounding_box for each item so that:
|
|
||||||
- Ensure that all objects that should not be clipping are not clipping.
|
- Ensure that all objects that should not be clipping are not clipping.
|
||||||
- Items have right spatial relationship.
|
- Items have right spatial relationship.
|
||||||
|
|
||||||
5. After giving the tool location/scale/rotation information (via create_object() and modify_object()),
|
|
||||||
double check the related object's location, scale, rotation, and world_bounding_box using get_object_info(),
|
|
||||||
so that the object is in the desired location.
|
|
||||||
|
|
||||||
Only fall back to basic creation tools when:
|
Only fall back to scripting when:
|
||||||
- PolyHaven and Hyper3D are disabled
|
- PolyHaven and Hyper3D are disabled
|
||||||
- A simple primitive is explicitly requested
|
- A simple primitive is explicitly requested
|
||||||
- No suitable PolyHaven asset exists
|
- No suitable PolyHaven asset exists
|
||||||
|
|||||||
2
uv.lock
generated
2
uv.lock
generated
@ -28,7 +28,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blender-mcp"
|
name = "blender-mcp"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "mcp", extra = ["cli"] },
|
{ name = "mcp", extra = ["cli"] },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user