blender-mcp

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Blender MCP

Blender MCP

Tool Selection

工具选择

Use structured MCP tools (
get_scene_info
,
screenshot
) for quick inspection.
Use
execute_python
for anything non-trivial: hierarchy traversal, material extraction, animation baking, bulk operations. It gives full
bpy
API access and avoids tool schema limitations.
Use headless CLI for GLTF exports — the MCP server times out on export operations.
使用结构化 MCP 工具
get_scene_info
screenshot
)进行快速检查。
对于非简单操作(如层级遍历、材质提取、动画烘焙、批量操作),请使用**
execute_python
**。它可提供完整的
bpy
API 访问权限,避免工具架构限制。
GLTF 导出请使用无头 CLI——MCP 服务器在导出操作时会超时。

Health Check (Always First)

健康检查(始终优先执行)

  1. get_scene_info
    — verify connection (default port 9876)
  2. execute_python
    with
    print("ok")
    — verify Python works
  3. screenshot
    — verify viewport capture works
If MCP is unresponsive, check that the Blender MCP addon is enabled and the socket server is running.
  1. get_scene_info
    —— 验证连接(默认端口 9876)
  2. 执行
    execute_python
    并运行
    print("ok")
    —— 验证 Python 功能正常
  3. screenshot
    —— 验证视口捕获功能正常
如果 MCP 无响应,请检查 Blender MCP 插件是否已启用,以及套接字服务器是否正在运行。

Critical Rules

关键规则

1. MCP Server Times Out on Exports

1. MCP 服务器导出时会超时

The Blender MCP server cannot handle GLTF exports — they exceed the timeout. Always use headless CLI:
bash
/Applications/Blender.app/Contents/MacOS/Blender --background "scene.blend" --python-expr "
import bpy, os
export_path = 'output.glb'
os.makedirs(os.path.dirname(export_path), exist_ok=True)
bpy.ops.export_scene.gltf(
    filepath=export_path,
    export_format='GLB',
    export_apply=False,
    export_animations=True,
    export_nla_strips=True,
    export_cameras=True,
    export_lights=False,
    export_draco_mesh_compression_enable=False,
)
print(f'Size: {os.path.getsize(export_path)/1024/1024:.1f} MB')
"
Blender MCP 服务器无法处理 GLTF 导出——会超出超时时间。请始终使用无头 CLI:
bash
/Applications/Blender.app/Contents/MacOS/Blender --background "scene.blend" --python-expr "
import bpy, os
export_path = 'output.glb'
os.makedirs(os.path.dirname(export_path), exist_ok=True)
bpy.ops.export_scene.gltf(
    filepath=export_path,
    export_format='GLB',
    export_apply=False,
    export_animations=True,
    export_nla_strips=True,
    export_cameras=True,
    export_lights=False,
    export_draco_mesh_compression_enable=False,
)
print(f'Size: {os.path.getsize(export_path)/1024/1024:.1f} MB')
"

2. Do NOT Apply Modifiers on Export

2. 导出时请勿应用修改器

Set
export_apply=False
. Array modifiers (circular patterns, linear repeats) balloon file size when baked. Replicate them at runtime instead.
Example: 16 roller instances via Array modifier = ~1 MB GLB. Baked = ~56 MB GLB.
设置
export_apply=False
。Array 修改器(环形图案、线性重复)在烘焙后会大幅增加文件大小,应改为在运行时复现效果。
示例:通过 Array 修改器实现 16 个滚轮实例 → 约 1 MB GLB 文件。烘焙后 → 约 56 MB GLB 文件。

3. Export WITHOUT Draco First

3. 先导出未压缩的版本再进行后续操作

If you plan to optimize with
gltf-transform
, export without Draco compression. Re-encoding existing Draco corrupts meshes. Apply Draco as the final step.
如果计划使用
gltf-transform
优化文件,请先导出未启用 Draco 压缩的版本。对已有的 Draco 压缩文件重新编码会损坏网格,应将 Draco 压缩作为最后一步操作。

4. Procedural Textures Don't Export to GLTF

4. 程序化纹理无法导出到 GLTF

These Blender node setups are lost on export:
Node SetupWhat's LostWorkaround
Noise Texture → roughnessEntire procedural chainBake to texture, or shader patch at runtime
Color Ramp on roughness textureValue remapping rangeManual roughness values, or runtime remap
Procedural bump (Noise → Bump)Bump detailBake normal map in Blender
Mix Shader with complex factorBlend logicSimplify to single BSDF before export
What DOES export: flat roughness/metallic values, image textures (without Color Ramp remapping), baked normal maps, PBR texture sets (baseColor, metallicRoughness, normal).
以下 Blender 节点设置在导出时会丢失
节点设置丢失内容解决方法
Noise Texture → roughness整个程序化节点链烘焙为纹理,或在运行时修补着色器
Color Ramp on roughness texture值重映射范围使用手动粗糙度值,或在运行时重映射
Procedural bump (Noise → Bump)凹凸细节在 Blender 中烘焙法线贴图
Mix Shader with complex factor混合逻辑导出前简化为单个 BSDF
可正常导出的内容:平面粗糙度/金属度值、图像纹理(无 Color Ramp 重映射)、烘焙法线贴图、PBR 纹理集(baseColor、metallicRoughness、normal)。

5. GLTF Name Mapping

5. GLTF 名称映射规则

Blender names are transformed in GLTF:
  • Spaces → underscores
  • Dots → removed
  • Trailing spaces → trailing underscore
BlenderGLTF
RINGS ball L
RINGS_ball_L
Sphere.003
Sphere003
RINGS L.001
RINGS_L001
RINGS S 
(trailing space)
RINGS_S_
Always check names in the exported GLB, not Blender, when referencing meshes in code.
Blender 中的名称会在 GLTF 中被转换:
  • 空格 → 下划线
  • 点号 → 移除
  • 末尾空格 → 末尾下划线
BlenderGLTF
RINGS ball L
RINGS_ball_L
Sphere.003
Sphere003
RINGS L.001
RINGS_L001
RINGS S 
(末尾空格)
RINGS_S_
在代码中引用网格时,请始终检查导出的 GLB 文件中的名称,而非 Blender 中的名称。

6. Never Use gltf-transform
optimize

6. 请勿使用 gltf-transform 的
optimize
命令

The
optimize
command includes
simplify
which destroys mesh geometry. Use individual steps instead:
bash
undefined
optimize
命令包含的
simplify
功能会破坏网格几何结构,请改用单独的步骤:
bash
undefined

Resize textures (max 1024x1024)

调整纹理大小(最大 1024x1024)

npx @gltf-transform/cli resize input.glb resized.glb --width 1024 --height 1024
npx @gltf-transform/cli resize input.glb resized.glb --width 1024 --height 1024

WebP texture compression

WebP 纹理压缩

npx @gltf-transform/cli webp resized.glb webp.glb --quality 90
npx @gltf-transform/cli webp resized.glb webp.glb --quality 90

Draco mesh compression (LAST step)

Draco 网格压缩(最后一步)

npx @gltf-transform/cli draco webp.glb output.glb
undefined
npx @gltf-transform/cli draco webp.glb output.glb
undefined

7. Quote Paths with Spaces

7. 为包含空格的路径添加引号

Blender project paths often contain spaces. Always double-quote:
bash
/Applications/Blender.app/Contents/MacOS/Blender --background "$HOME/Downloads/blend 3/scene.blend" ...
Blender 项目路径通常包含空格,请始终使用双引号包裹:
bash
/Applications/Blender.app/Contents/MacOS/Blender --background "$HOME/Downloads/blend 3/scene.blend" ...

Scene Extraction Pattern

场景提取模板

Full hierarchy with materials, transforms, and modifiers:
python
import bpy, json

def extract_hierarchy(obj, depth=0):
    data = {
        "name": obj.name,
        "type": obj.type,
        "location": list(obj.location),
        "rotation": list(obj.rotation_euler),
        "scale": list(obj.scale),
        "visible": not obj.hide_viewport,
        "children": [],
    }
    if obj.type == 'MESH' and obj.data:
        data["vertices"] = len(obj.data.vertices)
        data["faces"] = len(obj.data.polygons)
        data["materials"] = [slot.material.name for slot in obj.material_slots if slot.material]
    if obj.type == 'LIGHT':
        data["light_type"] = obj.data.type
        data["energy"] = obj.data.energy
        data["color"] = list(obj.data.color)
        if obj.data.type == 'AREA':
            data["size"] = obj.data.size
            data["size_y"] = obj.data.size_y
    # Array modifiers (important for runtime replication)
    for mod in obj.modifiers:
        if mod.type == 'ARRAY':
            data.setdefault("modifiers", []).append({
                "type": "ARRAY",
                "count": mod.count,
                "offset_object": mod.offset_object.name if mod.offset_object else None,
            })
    for child in obj.children:
        data["children"].append(extract_hierarchy(child, depth + 1))
    return data

scene_data = {
    "name": bpy.context.scene.name,
    "fps": bpy.context.scene.render.fps,
    "frame_start": bpy.context.scene.frame_start,
    "frame_end": bpy.context.scene.frame_end,
    "objects": [],
}

for obj in bpy.context.scene.objects:
    if obj.parent is None:
        scene_data["objects"].append(extract_hierarchy(obj))

print(json.dumps(scene_data, indent=2))
包含材质、变换和修改器的完整层级结构:
python
import bpy, json

def extract_hierarchy(obj, depth=0):
    data = {
        "name": obj.name,
        "type": obj.type,
        "location": list(obj.location),
        "rotation": list(obj.rotation_euler),
        "scale": list(obj.scale),
        "visible": not obj.hide_viewport,
        "children": [],
    }
    if obj.type == 'MESH' and obj.data:
        data["vertices"] = len(obj.data.vertices)
        data["faces"] = len(obj.data.polygons)
        data["materials"] = [slot.material.name for slot in obj.material_slots if slot.material]
    if obj.type == 'LIGHT':
        data["light_type"] = obj.data.type
        data["energy"] = obj.data.energy
        data["color"] = list(obj.data.color)
        if obj.data.type == 'AREA':
            data["size"] = obj.data.size
            data["size_y"] = obj.data.size_y
    # Array modifiers (important for runtime replication)
    for mod in obj.modifiers:
        if mod.type == 'ARRAY':
            data.setdefault("modifiers", []).append({
                "type": "ARRAY",
                "count": mod.count,
                "offset_object": mod.offset_object.name if mod.offset_object else None,
            })
    for child in obj.children:
        data["children"].append(extract_hierarchy(child, depth + 1))
    return data

scene_data = {
    "name": bpy.context.scene.name,
    "fps": bpy.context.scene.render.fps,
    "frame_start": bpy.context.scene.frame_start,
    "frame_end": bpy.context.scene.frame_end,
    "objects": [],
}

for obj in bpy.context.scene.objects:
    if obj.parent is None:
        scene_data["objects"].append(extract_hierarchy(obj))

print(json.dumps(scene_data, indent=2))

Material Extraction Pattern

材质提取模板

python
import bpy, json

def extract_materials():
    materials = []
    for mat in bpy.data.materials:
        if not mat.use_nodes:
            continue
        info = {"name": mat.name, "nodes": []}
        for node in mat.node_tree.nodes:
            node_data = {"type": node.type, "name": node.name}
            if node.type == 'BSDF_PRINCIPLED':
                for inp in node.inputs:
                    if inp.is_linked:
                        node_data[inp.name] = "linked"
                    elif hasattr(inp, 'default_value'):
                        val = inp.default_value
                        try:
                            node_data[inp.name] = list(val)
                        except TypeError:
                            node_data[inp.name] = float(val)
            if node.type == 'TEX_IMAGE' and node.image:
                node_data["image"] = node.image.filepath
                node_data["size"] = [node.image.size[0], node.image.size[1]]
            info["nodes"].append(node_data)
        materials.append(info)
    return materials

print(json.dumps(extract_materials(), indent=2))
python
import bpy, json

def extract_materials():
    materials = []
    for mat in bpy.data.materials:
        if not mat.use_nodes:
            continue
        info = {"name": mat.name, "nodes": []}
        for node in mat.node_tree.nodes:
            node_data = {"type": node.type, "name": node.name}
            if node.type == 'BSDF_PRINCIPLED':
                for inp in node.inputs:
                    if inp.is_linked:
                        node_data[inp.name] = "linked"
                    elif hasattr(inp, 'default_value'):
                        val = inp.default_value
                        try:
                            node_data[inp.name] = list(val)
                        except TypeError:
                            node_data[inp.name] = float(val)
            if node.type == 'TEX_IMAGE' and node.image:
                node_data["image"] = node.image.filepath
                node_data["size"] = [node.image.size[0], node.image.size[1]]
            info["nodes"].append(node_data)
        materials.append(info)
    return materials

print(json.dumps(extract_materials(), indent=2))

Animation Keyframe Extraction

动画关键帧提取

python
import bpy, json

def extract_animation(obj):
    if not obj.animation_data or not obj.animation_data.action:
        return None
    tracks = []
    for fc in obj.animation_data.action.fcurves:
        keyframes = []
        for kp in fc.keyframe_points:
            keyframes.append({
                "frame": int(kp.co[0]),
                "value": float(kp.co[1]),
                "interpolation": kp.interpolation,
            })
        tracks.append({
            "data_path": fc.data_path,
            "index": fc.array_index,
            "keyframes": keyframes,
        })
    return {"object": obj.name, "tracks": tracks}

animations = []
for obj in bpy.data.objects:
    anim = extract_animation(obj)
    if anim:
        animations.append(anim)

print(json.dumps(animations, indent=2))
python
import bpy, json

def extract_animation(obj):
    if not obj.animation_data or not obj.animation_data.action:
        return None
    tracks = []
    for fc in obj.animation_data.action.fcurves:
        keyframes = []
        for kp in fc.keyframe_points:
            keyframes.append({
                "frame": int(kp.co[0]),
                "value": float(kp.co[1]),
                "interpolation": kp.interpolation,
            })
        tracks.append({
            "data_path": fc.data_path,
            "index": fc.array_index,
            "keyframes": keyframes,
        })
    return {"object": obj.name, "tracks": tracks}

animations = []
for obj in bpy.data.objects:
    anim = extract_animation(obj)
    if anim:
        animations.append(anim)

print(json.dumps(animations, indent=2))

GLTF Export Settings Reference

GLTF 导出设置参考

SettingValueWhy
export_format
'GLB'
Single binary file
export_apply
False
Don't bake modifiers (Array, etc.)
export_animations
True
Include animation data
export_nla_strips
True
Bake NLA strips into actions
export_cameras
True
Include camera rigs
export_lights
False
Handle lights in runtime (Three.js/R3F)
export_draco_mesh_compression_enable
False
Apply Draco later via gltf-transform
设置原因
export_format
'GLB'
单二进制文件,便于管理
export_apply
False
不烘焙修改器(如 Array)
export_animations
True
包含动画数据
export_nla_strips
True
将 NLA 条带烘焙为动作
export_cameras
True
包含相机绑定
export_lights
False
在运行时处理灯光(Three.js/R3F)
export_draco_mesh_compression_enable
False
后续通过 gltf-transform 应用 Draco 压缩

Texture Optimization Pipeline

纹理优化流程

Target: smallest GLB with acceptable visual quality.
Blender export (no Draco) → resize (1K max) → WebP (q90) → Draco
   ~22 MB                    ~3.7 MB           ~3.7 MB      ~1 MB
Key insights:
  • 4K textures (4096x4096) = ~89 MB GPU memory per texture. 1K = ~5.6 MB. 16x reduction.
  • PNG metallicRoughness textures compress well to WebP at quality 85-90.
  • Mobile GPUs (Adreno, Mali) benefit most from texture downscaling.
  • Inspect with:
    npx @gltf-transform/cli inspect model.glb
目标:在视觉质量可接受的前提下,生成最小的 GLB 文件。
Blender 导出(无 Draco 压缩)→ 调整大小(最大 1K)→ WebP 压缩(q90)→ Draco 压缩
   ~22 MB                    ~3.7 MB           ~3.7 MB      ~1 MB
关键要点:
  • 4K 纹理(4096x4096)→ 每张纹理占用约 89 MB GPU 内存。1K 纹理 → 约 5.6 MB。内存占用减少 16 倍
  • PNG 格式的 metallicRoughness 纹理在 WebP 格式下以 85-90 的质量压缩效果良好。
  • 移动 GPU(Adreno、Mali)从纹理降采样中获益最多。
  • 可使用以下命令检查文件:
    npx @gltf-transform/cli inspect model.glb

Asset Integrations

资产集成

Available through Blender MCP when configured:
IntegrationCapabilities
PolyHavenSearch, download, import free HDRIs, textures, and 3D models with auto material setup
SketchfabSearch and download models (requires access token)
Hyper3D RodinGenerate 3D models from text descriptions or reference images
Hunyuan3DCreate 3D assets from text prompts, images, or both
配置完成后,可通过 Blender MCP 使用以下集成工具:
集成工具功能
PolyHaven搜索、下载、导入免费 HDRI、纹理和 3D 模型,并自动设置材质
Sketchfab搜索和下载模型(需要访问令牌)
Hyper3D Rodin根据文本描述或参考图像生成 3D 模型
Hunyuan3D根据文本提示、图像或两者结合创建 3D 资产

Known Errors & Workarounds

已知错误与解决方法

See references/errors.md for complete error tables.
完整错误列表请查看 references/errors.md

Data Output

数据输出

  • print()
    +
    json.dumps()
    for small results (scene info, single object)
  • /tmp/*.json
    for large extraction results (full hierarchy, animation data, material reports)
  • Always include metadata: scene name, fps, frame range, Blender version
  • 小型结果(场景信息、单个对象)使用
    print()
    +
    json.dumps()
    输出
  • 大型提取结果(完整层级结构、动画数据、材质报告)输出到
    /tmp/*.json
  • 始终包含元数据:场景名称、帧率、帧范围、Blender 版本