# 配置参考(分享版) > 分享版的全部机器相关设置都在 `runtime/config.mjs`:**环境变量 → 配置文件 → 自动探测 → 默认值**。 > 作者自用版把这些写死在源码里;分享版**换机器不用改任何代码**。 --- ## 1. 优先级链(高 → 低) ```text ┌ 1. 环境变量 DSH_BLENDER_*(见 §3) ├ 2. 配置文件 $DSH_BLENDER_CONFIG │ → <包根>/dsh-blender.config.json │ → ~/.dsh/dsh-blender.config.json ├ 3. 自动探测 工作目录(Windows %LOCALAPPDATA%\dsh-blender-rt;macOS ~/.dsh-blender-rt;取不到则 os.tmpdir()) │ blender.exe(macOS /Applications/Blender*.app;Windows Program Files / Steam;WSL /mnt/<盘>/…;再 PATH) └ 4. 内置默认 addon 127.0.0.1:9876 · 后端 127.0.0.1:9877 · 租约 TTL 600000 ms · holder=plugin-pid- ``` **怎么确认当前生效的是什么**: ```bash curl -sS http://127.0.0.1:9877/who | python3 -m json.tool | sed -n '/"config"/,/}/p' # 或者让模型调:blender_viewport(op="doctor") → 返回里的 config 段 ``` `config` 段里会写明 `configFile`(用了哪个配置文件,没用到就是 `null`)、`workDir.from`、`blenderFrom`(配置 / 自动探测 / path / not-found)。 --- ## 2. 配置文件 复制样例并改名即可生效: ```bash cp dsh-blender.config.example.json dsh-blender.config.json ``` | 键 | 类型 | 默认 | 说明 | |---|---|---|---| | `workDir` | string | 自动 | 帧 PNG / 无头脚本 / 临时产物目录。**Windows 形式**(`D:\\work\\dsh-blender-rt`);WSL 下会映射成 `/mnt/d/...`。必须"两端都能读写" | | `blenderExe` | string | 自动 | blender 可执行文件。WSL 里给 `/mnt/d/.../blender.exe`(Windows 进程);纯 Windows 给 `D:\\...\\blender.exe` | | `addonHost` | string | `127.0.0.1` | addon 监听地址(一般不用改) | | `addonPort` | number | `9876` | addon 端口(addon 面板里可改;这里要一致) | | `addonProtocol` | string | `auto` | Blender 侧 addon 的线上协议:`auto`(探测一次并缓存)/ `flat` / `category-action`。旧值 `ahujasid` 仍被接受为**已弃用别名**(归一化为 `flat`,不报错)。见 §6 末「两种 addon 协议」 | | `httpPort` | number | `9877` | 本插件后端端口(两台 Blender 并存时改一台) | | `holder` | string | `plugin-pid-` | 写通道租约身份(多会话时便于分辨谁在写) | | `leaseTtlMs` | number | `600000` | 租约有效期;持有者每次都续期,超时自动失效 | | `blenderUserConfig` | string | `null` | 用户 Blender 配置目录(GPU 偏好所在)。**无头进程默认读不到它** → 想继承就设它并配合 `factory_startup=false`(env:`DSH_BLENDER_USER_CONFIG`) | | `blenderUserScripts` | string | `null` | 用户 Blender 脚本目录(addon 所在),同上(env:`DSH_BLENDER_USER_SCRIPTS`) | | `workerPort` | number | `9879` | 热无头 worker 的本地端口(与 addon 9876 / 后端 9877 区分;env:`DSH_BLENDER_WORKER_PORT`) | --- ## 3. 环境变量 | 变量 | 等价于 | 备注 | |---|---|---| | `DSH_BLENDER_CONFIG` | 指定配置文件路径 | 优先级最高,适合"一套环境一份配置" | | `DSH_BLENDER_WORKDIR` | `workDir` | 给 Windows 形式路径;WSL 下自动映射 | | `DSH_BLENDER_EXE` | `blenderExe` | 无头工具的 blender | | `DSH_BLENDER_ADDON_HOST` / `DSH_BLENDER_ADDON_PORT` | `addonHost` / `addonPort` | | | `DSH_BLENDER_ADDON_PROTOCOL` | `addonProtocol` | `auto` / `flat` / `category-action`(旧值 `ahujasid` 仍被接受为已弃用别名 → `flat`) | | `DSH_BLENDER_HTTP_PORT` | `httpPort` | 后端端口(插件与后端共用同一套来源) | | `DSH_BLENDER_HOLDER` | `holder` | 多会话时建议显式给(如 `session-A`) | | `DSH_BLENDER_LEASE_TTL_MS` | `leaseTtlMs` | | | `DSH_BLENDER_SERVER` | — | 直接指定后端脚本路径(一般不用) | | `DSH_HEADLESS_WAIT_MS` | — | **v0.9.3**:headless 的客户端**等待窗口**(默认 100000 = 100 s)。到点还没跑完 → 回 `{kind:"promoted", jobId:"run-…"}`(结果不丢,用 `blender_rt_job(op="collect")` 收)。见 §10.1 | | `DSH_HEADLESS_AUTO_JOB_MS` | — | **v0.9.3**:**自动转作业层阈值** —— 声明/预期时长超过它的 headless 调用不等窗口,直接后台化。见 §10.1 | --- ## 4. blender.exe 怎么被找到 探测顺序(`config.mjs:detectBlenderExe`): 1. `DSH_BLENDER_EXE` 环境变量; 2. 配置文件 `blenderExe`; 3. **自动扫描**: - Windows:`%ProgramFiles%\\Blender Foundation\\Blender*\\blender.exe`(按目录名排序取第一个存在的); - WSL:`/mnt/{c,d,e,f}/Program Files/Blender Foundation/Blender*/blender.exe`,以及 `SteamLibrary/steamapps/common/Blender/blender.exe`、`Steam/steamapps/common/Blender/blender.exe`; - **macOS**:`/Applications/Blender*.app/Contents/MacOS/Blender` 与 `~/Applications/Blender*.app/Contents/MacOS/Blender`(任意版本名,如 `Blender.app`、`Blender 4.2.app`),以及挂载卷 `/Volumes/*/Applications/…`; 4. `PATH`(Windows `where blender`;Linux `which blender`); 5. 都没有 → `config.blenderExe = null`,`blender_rt_headless` 会报 `blender-exe-missing` 并提示三种配置方式。 **三种配置方式(任选)**: ```bash # ① 环境变量 export DSH_BLENDER_EXE=/mnt/d/SteamLibrary/steamapps/common/Blender/blender.exe # WSL # Windows: set DSH_BLENDER_EXE=D:\Program Files\Blender Foundation\Blender 5.2\blender.exe # ② 包根配置 echo '{"blenderExe":"/mnt/d/.../blender.exe"}' > dsh-blender.config.json # ③ 用户级配置(多个工程共用) mkdir -p ~/.dsh && echo '{"blenderExe":"/mnt/d/.../blender.exe"}' > ~/.dsh/dsh-blender.config.json ``` --- ## 5. 工作目录(为什么要"成对") Blender(Windows 进程)负责**写**帧 PNG 与无头脚本;宿主(WSL 里的 Node)负责**读**它们。所以需要同一个物理目录的两个写法: | | 例子 | |---|---| | Blender 侧(Windows 路径) | `C:\Users\\<你>\\AppData\\Local\\dsh-blender-rt` | | 宿主侧(WSL 路径) | `/mnt/c/Users/<你>/AppData/Local/dsh-blender-rt` | - 默认自动取 Windows 的 `%LOCALAPPDATA%\\dsh-blender-rt`(在 WSL 里通过 `cmd.exe /c echo %LOCALAPPDATA%` 问一次,结果会缓存); - 取不到(没有 WSL 互操作 / **原生 Linux**,后者 v1.0.4 起支持)→ 退回 `os.tmpdir()/dsh-blender-rt`(此时要求 Blender 与宿主同机;原生 Linux 上**不做跨 OS 映射**,宿主路径原样交给 Blender,`workDirWin` 与 `workDirWsl` 同构); - **WSL 场景不要**配成 WSL 内部路径(如 `/home/me/x`):Windows Blender 只能通过 UNC 访问,而 **UNC 写入会静默失败**;原生 Linux 上 `/home/me/x` 反而是正确写法; - 别配到只读挂载(WSL 里 `/mnt/c` 通常只读,除非你在 Windows 侧开了对应权限)。 --- ## 5.5 GPU 语义(无头渲染最贵的坑) **问题**:无头进程(`blender -b`)**默认读不到用户偏好** → `prefs.compute_device_type = NONE`、设备列表为空 → 即使工程文件里写着 `scene.cycles.device = "GPU"`,**Cycles 也会静默跑 CPU**。 本机实测(1821 对象工程,480×270/32spp):**CPU 2.78 s vs GPU 预热 0.18 s = 15.4×**。 **三层解法**(按推荐顺序): 1. **默认的 `gpu:"auto"` 前导**(零配置):按 `OPTIX→CUDA→HIP→ONEAPI→METAL` 顺序设置 `compute_device_type` + 勾选 GPU 设备 + `scene.cycles.device="GPU"`,并回传 `gpu` 字段(`before/after/configured/fell_back_to_cpu`); 2. **`use_user_config: true`**:透传 `BLENDER_USER_CONFIG` / `BLENDER_USER_SCRIPTS`(配合 `factory_startup=false`,让无头进程继承你那套偏好与 addon); 3. **`gpu: "true"`**:要求必须有 GPU,没有就 `ok=false`(不想再被静默降级坑一次时用)。 > 一句话:**别信文件里的 `device=GPU`,要看回执里的 `gpu.after.gpu_enabled`。** ## 5.6 热无头会话与长任务 | 场景 | 用什么 | 说明 | |---|---|---| | 一次性重活(渲染/批量几何/校验) | `blender_rt_headless` | 冷启动 0.9–1.2 s,干净、可复现 | | **反复迭代**(几十次重建/导入) | **`blender_rt_worker`** | 常驻 `blender -b`;`start` → `exec`(复用持久内核 `K`)→ `status` → `stop`;串行执行 | | 单次 > 5 分钟 | 热会话,或把结果写文件 | 客户端 `fetch` 的响应头超时是 **300 s**(实测 `UND_ERR_HEADERS_TIMEOUT`);`/headless` 与 `/worker` 已改**流式**(响应头立刻返回 + 15 s 心跳)→ 现在可以跑过 5 分钟 | **超时/断连的语义**:客户端断开**不等于任务失败** —— 服务端子进程会继续跑完,产物仍在 `outdir`,全量日志路径在 `logs` 字段里。遇到 `fetch failed` 先翻目录。 ## 6. 端口与多实例 | 场景 | 做法 | |---|---| | 单 Blender 单会话 | 默认即可(9876 + 9877) | | **两台 Blender 并行** | 第二台:addon 面板改端口(比如 9896);插件侧 `httpPort=9898, addonPort=9896`(配置文件或环境变量) | | 临时排错 | `node runtime/server.mjs --port 9888`(命令行 `--port` 优先于配置) | ### 两种 addon 协议(`addonProtocol`) Blender 侧存在两种**互不兼容**的 addon 实现,本插件都支持: | | 扁平协议 `flat`(`DSH Blender Bridge`,旧名 `MCP for Blender`;v1.6/protocol 5 与 v1.7/protocol 11,协议形状相同) | harveyxiacn 增强版(`blender_mcp_addon`) | |---|---|---| | 请求 | `{"type":"get_scene_info","params":{}}`(**无分帧**) | `{"id":..,"type":"command","category":"scene","action":"get_info","params":{}}` + `\n` | | 应答 | `{"status":"success","result":{…}}` | `{"id":..,"success":true,"data":{…}}` + `\n` | | 命令词汇 | 扁平名(`ping` / `execute_code` / …) | `category.action`(`system.get_info` / `utility.execute_python` / …) | `auto`(默认)在通道首次使用时**探测一次并缓存**:先发不带换行的扁平 ping(只有扁平 addon 会回), 没回再发带换行的 category/action ping,并按回包封套形状纠错(避免 addon 偶尔忙一下就把协议判错)。 配置成具体值则不探测。探测结果会出现在 `/doctor`(`protocol` / `protocol_label` / `protocol_from`) 与 `/who` 的 `config.addonProtocol` 里。 > **`ahujasid` 是已弃用别名(仍被接受)**:v2.0.0 起协议判别 id 由 `ahujasid` 改称 **`flat`**(`/doctor` 的 `protocol` 会回 `flat`)。 > 旧配置文件 / 环境变量里写 `ahujasid` 仍然可用 —— `resolveProtocol` 会把它**归一化为 `flat`**,不报错。 **症状对照**:装了另一种 addon 时,`/doctor` 原先只会报 `addon-thread-stuck`(端口通、ping 不回), 与"addon 客户端线程卡住"无法区分;现在会直接给出 `detected_protocol` 和"把 `addonProtocol` 改成 X"的修法。 `category-action` 侧没有 PolyHaven / Hyper3D / Sketchfab / Poly Pizza / Hunyuan3D 这几条资产集成通道, 对应命令会**明确报错**(不假装成功);`get_viewport_screenshot` 与 `get_world_state_snapshot` 由插件侧生成 python 借 `utility.execute_python` 执行,不依赖对面的同名 handler。 自检(不需要 Blender、不需要 DSH):`node tests/protocol_selftest.mjs`。 --- ## 7. 租约(并发写保护) | 项 | 说明 | |---|---| | 谁受保护 | 写路由:`/act /cmd /loop /perf(写 op) /opt(join) /headless`;**只读 op 豁免**(`perf status/help`、`loop status/board/help`、`opt analyze`) | | 无租约时 | 完全无阻力(单 agent 日常) | | 别人持有时 | 返回 `409 {ok:false, error:"leased", holder, expiresInMs, hint}` | | 抢占 | `blender_viewport {op:"lease", force:true}` 或在 HTTP body 里 `force:true` | | 续期 | 插件每 15 s 心跳;**只在本来就持有时**才续(空闲不抢) | | 释放 | `blender_viewport {op:"release"}` | | 查看 | `blender_viewport {op:"who"}` 或 `curl /who` | --- ## 8. 两种部署形态 ### A. DSH 在 WSL,Blender 在 Windows(推荐) - 需要:WSL 互操作(`cmd.exe` / `blender.exe` 能直接执行); - 无头工具通过 binfmt 直接起 `blender.exe`(stdout/stderr 直连回传); - 路径映射自动:`/mnt/d/x` ↔ `D:\x`。 ### B. DSH 与 Blender 同在 Windows - `config.mjs` 检测到 `process.platform === 'win32'` 时路径映射自动退化为"原样返回"; - `blenderExe` 给 Windows 路径即可; - 无头工具直接 `spawn` 该 exe。 --- ## 9. 与作者自用版对照 | 配置项 | 自用版(写死) | 分享版 | |---|---|---| | workDir | `D:\DSH\blender\tmp` ↔ `/mnt/d/DSH/blender/tmp` | 自动 / 可配 | | blender.exe | Steam 路径 | 自动扫描 / 可配 | | 端口 | 9876 / 9877 写死 | 可配 | | 降噪器 | 固定 OPTIX | 按 `compute_device_type` 自动(OptiX ↔ OIDN) | **只改配置、不改代码**即可从其一变成另一个。 ## 8. 引擎相关索引(EEVEE 默认)与踩坑 配置项本身不区分引擎,但**默认引擎是 EEVEE + 光追**(v0.8.0 起),引擎相关的硬限制与对策单独成文: | 想查什么 | 去哪看 | |---|---| | 引擎事实(纯 GPU / 图形后端 / 不依赖设备偏好 / 无头不静默降级)与实测数字(1.35 s vs 3.13 s、冷首帧 16 s) | docs/EEVEE-工作要点.md 第 1 节 | | EEVEE 硬限制:Shader to RGB 只含直接光、暗部纯黑、环境灯 300~500 W | 同上 第 2 节 | | NPR 材质管线分工(主节点 vs 光源拓展 BSDF、Lightgroup ID = -1、漫射拓展光源压暗 214→44) | 同上 第 3 节 | | 渐变组三坑 + 「先挪相机验证再回用户机位」诊断套路 | 同上 第 4 节 | | 与本插件工具链的配合(rt_do / rt_worker / rt_headless / qc_compare / txn) | 同上 第 5 节 | | 工具参数 engine= 的取值(eevee / cycles / keep) | docs/操作教程.md 附录「渲染引擎」 | > 一句话:**引擎相关的问题先看 EEVEE-工作要点.md**;只有需要真实 GI / 焦散 / 最高保真时才用 engine="cycles"。 --- ## 10. 无头路径空间与等待窗口(v0.9.3) ### 10.1 两个新环境变量 | 变量 | 默认 | 作用 | |---|---|---| | `DSH_HEADLESS_WAIT_MS` | **100000**(100 s) | 客户端**等待窗口**:`blender_rt_headless` 同步等多久。到点还没跑完 → 回 `{kind:"promoted", jobId:"run-…"}`,**结果不丢**(v0.9.2 之前这里是丢结果的),用 `blender_rt_job(op="collect", id=…)` 收 | | `DSH_HEADLESS_AUTO_JOB_MS` | **0(关闭)** | **自动转作业层阈值**:`timeout_ms + 60 s` 的预计预算 ≥ 它时,headless 调用**不等窗口**,直接按 `as_job=true` 语义后台化(当场回 jobId)。设 `120000` 即「预计 >2 min 的活一律后台化」 | 读取时机:两个变量都在**插件模块加载时读一次**(不是每次调用)→ **改完重启 DSH 最稳妥**。 **显式参数优先于环境变量**:给了 `as_job` / `wait_s` 就按给的来(`wait_s` 本身也被等待窗口夹住,取 `min(wait_s, 窗口)`)。 > 与 v0.9.2 的区别:v0.9.2 的 headless 会一直等到服务端返回(或外层 deadline 把它掐掉,**结果丢失**); > v0.9.3 把「等多久」变成客户端自己的预算,到点回 `promoted` 信封 —— 服务端子进程照跑,结果可回收。 ```bash # WSL(DSH 在 WSL) export DSH_HEADLESS_WAIT_MS=30000 # 只等 30 s,再长就直接拿 jobId 走人 export DSH_HEADLESS_AUTO_JOB_MS=120000 # 预期 >2 min 的活直接后台化 # Windows(DSH 与 Blender 同在 Windows) set DSH_HEADLESS_WAIT_MS=30000 set DSH_HEADLESS_AUTO_JOB_MS=120000 ``` **确认生效值**:`blender_viewport(op="doctor")` 的 `config` 段(或 `curl -sS http://127.0.0.1:9877/who`)反映后端侧生效配置; 两个 `DSH_HEADLESS_*` 是**客户端**变量,用 `node -e "console.log(process.env.DSH_HEADLESS_WAIT_MS)"` 在同一个环境里看即可。 **验收(复制即跑)**: ```python # ① 等待窗口语义:sleep 200 的脚本,窗口到点应回 promoted 信封,而不是 deadline 报错 r = blender_rt_headless(script="import time; time.sleep(200); print('HEADLESS {}')", timeout_ms=900000) blender_rt_job(op="collect", id=r.jobId) # 信封里的 jobId(形如 run-…) # ② 显式参数压过环境变量:wait_s 给了就按 wait_s 等 blender_rt_headless(script="import time; time.sleep(5); print('HEADLESS {}')", as_job=True, wait_s=15) ``` > 与 §5.6「客户端超时 ≠ 任务失败」的关系:v0.9.3 起这条从"要你手动去翻目录"升级成**有句柄可收** > —— 回执里直接给 `jobId`,不用再靠 `ls` 看 PNG 时间戳猜。 ### 10.2 `outdir` / `out_json` 现在接受 WSL 路径(D4) | 参数 | v0.9.2 行为 | v0.9.3 行为 | |---|---|---| | `blender_rt_headless(outdir=…)` | 只吃 Windows 路径;给 `/home/…` 会被 Windows Blender 当成盘符相对路径 → **静默写到 `C:\home\…`** | **接受 WSL 路径**(`/home/…`、`/mnt/…`),内部用已有的 `K.win_path` 映射 | | `blender_rt_headless(out_json=…)` | 同上;大结果自动溢出也落 Windows 工作目录 | 同上;回执给**真实写入路径** | | 脚本把 `/home/…` 交给 Windows 侧 API | 静默改写(`os.path.exists()` 返回 false,日志里反斜杠还被吞) | 检测到就**显式报错**,不再静默漂移 | ```python blender_rt_headless(outdir="/home/sixtyseven67/DSH/.tmp/hull_aft", # ✅ v0.9.3 直接给 WSL 路径 script="import os, json; open(os.path.join(K.out_dir, 'probe.txt'), 'w').write('ok'); " "print('HEADLESS ' + json.dumps({'out': K.out_dir}))") ``` ```bash # 跑完在 WSL 侧核对(回执里也会给 Windows 形态 + WSL 可见形态两份真实路径) ls -la /home/sixtyseven67/DSH/.tmp/hull_aft/probe.txt ``` **红线(不变)**: - `/mnt/c|d|e|f` 是**只读挂载** —— 写不进去; - 产物落在只读挂载 → `read_image` 打不开,每轮要多一次 `cp` 才能看图; - **不要**往 UNC(`\\wsl.localhost\…`)写:可能"返回成功但没有文件"; - 临时文件落**工作区**(bash 沙箱的 `/tmp` 与 fs 工具不共享,见 [操作教程.md](操作教程.md) §7.5)。 ### 10.3 与 §5「工作目录必须成对」的关系 两件事,不冲突: | | 谁的要求 | 怎么写 | |---|---|---| | **§5 插件 `workDir`** | 插件自身(帧 PNG / 无头脚本的落点;Blender 侧要能直接写) | 仍建议配 **Windows 形态**(`D:\…`),插件自动映射出 WSL 形态 | | **§10.2 调用级 `outdir` / `out_json`** | 每次调用的产物目录 | **可以直接给 WSL 路径**,映射由插件做 |