Skip to content

feat(store): add BlenderBridge plugin (Blender MCP bridge) - #6

Open
infinite-vector wants to merge 1 commit into
lioensky:mainfrom
infinite-vector:feat/blender-bridge-plugin
Open

feat(store): add BlenderBridge plugin (Blender MCP bridge)#6
infinite-vector wants to merge 1 commit into
lioensky:mainfrom
infinite-vector:feat/blender-bridge-plugin

Conversation

@infinite-vector

@infinite-vector infinite-vector commented Aug 27, 2026

Copy link
Copy Markdown

BlenderBridge — VCP ⇄ Blender 三端桥接让 Agent 直接读写正在运行的 Blender 场景。实时操作用户眼前那个 Blender 实例:读场景树、建模、改材质、调几何节点、画蜡笔、渲染、截图、查 Python API 文档。

作者:竹林夕弦(GitHub: Lucifa-sakula)类型:synchronous / stdio,与分布式节点插件的约束一致

  1. 架构与端口
    VCP (stdio)

    BlenderBridge.js ← 本插件,MCP Client 适配层
    ⇕ Streamable HTTP :6090
    blender-mcp ← Blender 官方 MCP Server(独立 Python 进程)
    ⇕ TCP socket :9876(null 分帧 JSON)
    Blender Add-on ← 运行在 Blender 进程内

对接的是 Blender 官方的 MCP 实现(projects.blender.org/lab/blender_mcp)。两跳都必须活着,任一跳断开工具调用即失败——插件对三种断开方式各有明确的错误提示,不返回裸错误码。


协议
默认端口
配置方式

① VCP ⇄ blender-mcp
HTTP
6090
插件 config.env 的 BLENDER_MCP_URL

② blender-mcp ⇄ Add-on
TCP
9876
Add-on 偏好面板 / 环境变量 BLENDER_MCP_PORT

端口默认值改为 6090 而非上游的 8000:8000 是极易被抢占的公共默认端口,6090 紧邻 VCP 自家的 6005/6006,语义归拢便于运维识别。

  1. 前置条件与安装
    上游 blender_mcp 本体已随插件附带在 blander-mcp/ 目录下(目录名沿用上游 fork 仓库名 yoyo201626/blander-mcp),不需要额外 clone。
    cd Plugin/BlenderBridge

① 打包 Blender Add-on(纯标准库,本机不需要装 Blender)

python3 build_addon.py

② 装 Python 环境(uv 自动按 .python-version 拉 3.13.x)

cd blander-mcp && uv sync && cd ..

③ 启动 MCP 服务器

cd blander-mcp/src && BLENDER_MCP_HOST=localhost BLENDER_MCP_PORT=9876
../.venv/bin/blender-mcp --transport http --host 127.0.0.1 --port 6090

然后在 Blender 里 Preferences → Add-ons → Install from Disk 选第 ① 步产出的 zip 并启用。
最易踩的坑:Preferences → System → Allow Online Access 必须勾上。Add-on 启动时强制检查 bpy.app.online_access,未开启会直接拒启并报Online access must be enabled in the system preferences——即使只连localhost 也照拦。这一条不在插件可控范围内,装完不通先查它。
需要先装 uv。依赖 mcp[cli] / docutils / pyyaml 全由 uv 管理,不污染系统 Python。Add-on 本身零 pip 依赖(只用 bpy + 标准库)。

  1. 使用范式:渐进式发现
    上游共 59 个工具。Schema 一次性注入会冲垮上下文,因此拆成四级:
    list_domains → discover_tools → get_tool_schema → call_tool
    (看领域) (看某领域工具) (查参数) (执行)

六个子命令:

子命令
参数
说明

status

连接状态、工具总数、领域分布

list_domains

所有领域及各自工具数

discover_tools
domain
该领域的工具名与简介

get_tool_schema
tool
单个工具的完整参数 Schema

call_tool
tool, arguments
调用任意 Blender MCP 工具

create_model
code
程序化建模逃生舱(bpy Python)

插件文档里留了一条实测教训:mesh_primitive_add 的参数是 primitive_type 而非 primitive,只有查 Schema 才知道。三个 handler 的报错都指回上一级,形成自我导航。
领域划分(59 工具)

领域
数量
内容

scene
1
场景状态总览

object
10
物体详情、修改器、材质、驱动器、F 曲线、关键帧

mesh
1
图元创建

material
1
材质列表

geonodes
4
几何节点查询、赋值、预设、关键帧

greasepencil
10
蜡笔图层、材质、笔画、形状

animation
3
骨骼动作、动作列表、相机追踪

asset
2
资产导入、库链接

blendfile
10
数据块统计、缺失文件、链接库、路径信息、用途推测

render
4
帧渲染、动画渲染、缩略图、视口输出

screenshot
3
窗口/区域截图、窗口布局 JSON

navigation
4
切换工作区标签、聚焦物体

docs
3
Python API 文档、用户手册检索

exec
2
任意 Python 执行(含 CLI 后台版)

other
1
连通性探针

  1. 四处值得一提的设计
    前缀归类,适配层零维护。 领域是 classifyDomain()按工具名前缀规则实时推导,不匹配任何前缀的落 other。上游 blender-mcp新增工具时自动落位,插件侧不需要跟着改——这把"适配层与被适配方的版本耦合"这个陷阱直接消掉了。manifest 里的领域一览标注了它是撰写时快照,并指明以运行时 list_domains 返回为准。
    上游零补丁的端口策略。 作者审出上游一处不对称:第二跳 TCP 直读环境变量原生可配,而第一跳 HTTP 不读环境变量、只认命令行 --port(blmcp/ init.py 里 argparse 的 default 是 8000,无 os.environ 兜底;上游预留的 src/.env 只被 Makefile 的 -include 加载,直接 spawn 不走 make)。于是策略是 HTTP 走 CLI 参数、TCP 走环境变量注入,上游仓库不打任何补丁,git pull 永不冲突。这是在别人的约束里找自由,而不是要求别人改。
    图像走 VCP 多模态通道。 MCP 返回的 image 内容项转为标准 content 数组{type:'image_url', image_url:{url:'data:...;base64,...'}},Agent 能真正看见截图与渲染结果。插件不自己解码落盘——路径管理与清理策略属于宿主,桥接层重复实现等于把宿主能力复制一份还要独立维护。base64 合计超 2MB 时会在文本段自动建议改用 render_thumbnail_to_path / render_viewport_to_path(上游自带落盘语义、返回路径而非内联图像)。
    纯文本零回归。 仅当返回含 images 时才切多模态数组,其余情况仍走原有字符串通路。59 个工具里 52 个的返回形态完全没变。

  2. 可选扩展:GMR(Generative Motion Rig)
    gmr/ 子目录,15 个命令,为复刻 Disney Research × ETH Zurich 的「A Generative Motion Rig for Artist-Driven Motion Authoring」(SIGGRAPH Talks '26, DOI 10.1145/3799818.3812088) 打地基。
    与桥接本体完全解耦:本体只有一处 try-require;删除整个 gmr/ 目录,6 个原生子命令照常工作,15 个 GMR 命令自动隐藏。不想用的用户零负担。
    三条不同频率的回路是这个模块最重要的设计决定:
    慢回路(秒级) VCP → BlenderBridge → blender-mcp:6090 → Add-on快回路(~ 100ms) Blender GMR Add-on → sidecar:6091 【不经过 VCP】管理回路(秒级) VCP → BlenderBridge → gmr → sidecar:6091
    快回路必须绕开 blender-mcp——上游只支持单并发(BLENDER_BUSY),而拖拽期间每秒产生几十次事件。所以 sidecar 与 blender-mcp 是并列关系,不是串联。这个拓扑是从上游的既有约束直接导出的,不是先画架构再发现跑不动。
    约束的单一真相源是 .blend 文件里的自定义属性(Empty 上挂 gmr_type /gmr_joint / gmr_frame / gmr_seed,Scene 上挂 gmr_window / gmr_base_action),不在内存也不在 sidecar。于是动画师拖 Empty 和 Agent 经 exec 修改,只是同一份文档的两个编辑器——没有第二份状态,所以没有同步问题。
    模型接入是配置驱动的:训练与推理的六个关键入口由 gmr/model_config.json描述(model.factory / model.loader / dataset.loader / training.trainStep /checkpoint.saver / sampler.sample),不需要改任何 Python 源码,git pull不冲突。python3 gmr/python/model_config.py --resolve 可独立校验配置并逐项报告 OK / 未配置 / FAIL,不必启动训练就能排错。
    未配置时可用:训练走 dry-run,推理走三次贝塞尔插值兜底,未装 PyTorch也能验证整条链路(Node 侧状态机、日志读取、SIGTERM 取消、done 文件解析、自动登记模型)。这对应论文第一阶段"用传统插值顶替 ML-Betweener"的做法。

  3. 安全须知
    Add-on 侧的 weak_sandbox.py 不是真沙箱。 上游注释原话:"this isn't really a sandbox, more guidance that some things should not bedone... This is more of a slap on the wrist"。它只拦 sys.exit() 与四个毁灭级算子(wm.quit_blender / read_factory_settings / read_factory_userpref/ read_userpref)。
    也就是说 execute_blender_code 与 create_model 在 Blender 进程内近乎全权限执行 Python——可读写文件系统、可调完整 bpy API、可发起网络请求。插件 README 与「模型自制说明.md」都在显著位置写了这一点,并给出三条建议:优先用 59 个结构化工具、破坏性操作前向用户确权、不在不受信任的 .blend文件或提示词下开放本插件。
    GMR 侧针对 pickle 反序列化做了三层防御:import_model 拒绝 URL 只接受本地路径;SAFE_FORMATS 只列 safetensors / onnx / npz,其它格式返回明确警告且 GMR_REQUIRE_SAFE_FORMAT=true 可硬性拦截;原地登记(copy=false)的模型拒绝删除文件,防误删用户原始资产。训练与推理都用 spawn 数组参数、不用 shell:true。

  4. 关于分布式节点
    本仓库有两个身份,这个插件正落在交集上,也可以当作分布式节点场景的一个具体范例。
    manifest 是 synchronous + stdio,符合 README 第 4 节对节点插件的约束,无需改造即可加载。而且它属于少数天然更适合跑在节点上的插件:

Blender 与 GPU 绑定,通常在有独显的工作站;VCP 主服务器往往部署在另一台常驻低功耗机器上。
本插件两跳链路全是 loopback 通信。装进跑 Blender 那台机器的分布式节点里,BLENDER_MCP_URL 保持默认的 http://127.0.0.1:6090/ 就已经是正确值——localhost 指向节点本机,恰好是 Blender 所在处。零配置。
反之若装在主服务器而 Blender 在别处,两跳都要改成跨网地址,并把blender-mcp 与 Add-on 双双暴露到局域网。多两个监听面、两处配置漂移点。
GMR 扩展把这一点放大:训练需要 CUDA,推理 sidecar 需要常驻显存。正对应本仓库 README 第 1 节列的第一个场景——把 GPU 密集型任务卸载到专用高性能机器。

一句话:把桥接器放在被桥接对象身边,而不是放在调度者身边。

  1. 仓库体积(需要维护者判断)
    本 PR 引入约 4560 个文件、15.8 MB:

内容
文件数
体积

blander-mcp/src/mcp/blmcp/data/api
2173
9.7 MB

blander-mcp/src/mcp/blmcp/data/manual
2217
5.4 MB

其余(addon、tools、tests、插件本体、GMR)
~ 170
0.7 MB

两个 data 目录是 Blender 官方 RST 文档语料,供 docs 域三个工具检索,占总量 96%。生成的 ZIP 约 3-4 MB(RST 压缩率高)。
doc/VCP官方插件商店落地方案.md 第 12.4 节建议这种体积改用 GitHubRelease Asset。此处先按作者「所有需要的东西放在一起」的意图用 raw zip,如倾向 Release Asset、或倾向让用户自行 clone 上游本体,可调整后重提。
另:blander-mcp/.claude/ 与 .agents/(15 文件 45.9 KB)是上游 fork作者的开发环境配置,随 vendored 一并保留未做剥离——删除会让副本与上游不一致。如需剥离请告知。

  1. 已知边界
    作者划定一期只做地基——能装、能跑、能生成 Add-on zip、GMR 命令可调用。以下归后续版本,作者会在二期处理:

真实模型训练与推理未实机验证(配置加载器的降级路径与校验逻辑已验证,无模型时的贝塞尔兜底已验证)
Blender 侧 GMR add-on 尚未编写:NMC 视口绘制、Empty handle 拖拽监听、生成层/传统层双层结构与 Rig 切换、IK 锁脚、Undo 栈整合
并发仲裁锁:用户 modal 拖拽时 Agent 从慢回路发来的 exec 会撞车,需要模式锁让 Agent 调用返回 busy 而非静默改坏状态。这是加了 GMR 才诞生的新问题,纯桥接时代不存在
stdio 模型下每次子命令都要重新 initialize + tools/list,完整走一遍四级发现是 8 次 HTTP 往返,其中 3 次拉的是同一份 59 工具定义,未做缓存。当前延迟对秒级的慢回路无影响
README 的启动命令是 Unix 路径(../.venv/bin/blender-mcp),Windows下应为 ...venv\Scripts\blender-mcp.exe,尚未补充说明

作者在 gmr/README.md 的「诚实标注」段主动列出了未实机验证项,包括Blender 5.2 的 Action Slots 数据模型是否可用、gpu 模块在 5.x 的接口名、sidecar 侧的 CUDA 环境、CondMDI 与论文 IBMM 的控制粒度是否等价。
已验证环境:Blender 5.2.0 LTS(Add-on 要求 ≥ 5.1.0)、blender-mcp 1.28.0、MCP 协议 2025-06-18、Python 3.13.13。

  1. 代为提交与署名
    插件作者竹林夕弦已在群内完成冒烟测试、公开征集 PR,并授权代为发布与修改。commit 已用 Co-authored-by trailer 记入作者署名。
    经作者授权的四处小修改(功能逻辑未改动):

.gitignore 删除 blander-mcp/ 排除规则——该规则会让上游本体不进版本库,导致 README 安装步骤、Add-on zip 分发、build_addon.py 现场生成三条路同时失败。作者原注释担心的「嵌套 .git 会被记成 gitlink」确实存在,处理方式改为提交时不携带 blander-mcp/.git。
README.md 第 5 节 cd blender_mcp/src → cd blander-mcp/src(早期 clone 方案的路径残留,同文档速查段已是正确路径)。
gmr/python/sidecar.py 的 out["note"] = note + ... 补空值保护——note 在真实模型分支为 None,超过 200 帧时会拼接失败。
两处文档与实现对齐:/load_model 的 warning 仍写「load_model_impl尚未实现」而该函数已重构为配置驱动;bezier_fallback docstring 声称三分位控制点得到 C1 连续,但那样三次贝塞尔精确退化为线性插值B(t) = a + (b-a)·t,代码正确、声明需修正。

Co-authored-by: Lucifa-sakula <Lucifa-sakula@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant