godot-imagegen
本地 AI 出图 → Godot 可用美术资产 的 DSH 插件。
它把本机已装好的两套秋叶出图后端接进 DeepSeek Harness:模型通过 10 个 image_* 工具在对话里直接出图、改写并运行 ComfyUI 工作流、直接驱动右侧面板,
你通过右侧边栏的 「美术工坊」 面板点选提示词、看图、抠图、放大;产物自动落进 Godot 项目的
assets/generated/<品类>/,并给出 res:// 路径,一键注册进 godot-asset-planner 的资产库。
作者:LINinLIN-0079 · 许可证:MIT(见 LICENSE) · 更新记录:CHANGELOG.md
面板里的 ComfyUI 画布不是截图,而是 真正的 ComfyUI 前端(内嵌 iframe)。装上一个随插件分发的 轻量 web 扩展后,对话里说「把 item-icon 放到画布上」就真的会切过去,画布上正在编辑什么也会回传给代理。
1. 能力范围
| 层 | 内容 |
|---|---|
| 模型工具(10) | image_backend_status image_generate image_edit image_variants image_upscale image_cutout image_interrogate image_gallery image_workflow image_workshop |
REST(/gig/*) |
面板与宿主的同源通道:状态、图库、原图字节、生成/编辑/放大/抠图/反推任务、任务轮询与取消、后端启停、设置与预设、工作流库读写、面板指令与画布中继轮询、画布桥接安装/卸载 |
| 工作流库 | 插件本地($DSH_HOME/godot-imagegen/workflows/,从不写入你的 ComfyUI):6 套游戏美术模板 + 任意新建/改写;元数据写在文档自身的 extra 里 |
| 画布桥接 | 随插件分发的 ComfyUI web 扩展(源码在仓库 comfyui-extension/),安装时把 DSH origin + 一次性令牌 烤进副本;装/卸都是一键,写入前必先备份 |
| 对话驱动 | 代理可通过 image_workflow / image_workshop 改写工作流、把工作流直接推到 ComfyUI 画布、把提示词与参数填进面板、在面板里定位图片——你只需在对话里说 |
| 浏览器面板 | better-sidebar tab「美术工坊」(order 63)。按可用宽度布局:宽 ≥700px 三栏(图库 / ComfyUI 画布 / 参数),窄了上下堆叠。画布分区默认折叠,载入失败时自动展开并标注。含提示词预设、任务进度(含后端实时预览图)、图库灯箱(放大/抠图/反推/发送到对话/注册资产/删除)。浮窗与全屏只在支持浮窗的侧边栏上出现——dsh-better-sidebar 从 v0.19.0 起已移除该功能(见第 2 节) |
| DSH 技能(可选配对) | 本机技能目录里的 game-art —— 游戏美术提示词手册、分品类尺寸与透明规范、端到端工作流、21 条样例提示词、色键抠图脚本。不属于本仓库,插件不依赖它,只是提示词写法与它对齐 |
| 持久化 | 独立 KV 单元 godot_imagegen(图库记录 + 用户预设 + 设置) |
每个产物都会带上:绝对路径、res:// 路径(落在 Godot 项目内时)、尺寸、seed、模型、后端、抠图/放大方法。
2. 面板怎么用
侧边栏放不下节点画布,所以面板按可用宽度决定布局:宽 ≥700px 时三栏(图库 / ComfyUI 画布 / 参数),窄了上下堆叠。
画布在侧边栏里就能用,不需要浮窗。 dsh-better-sidebar 从 v0.19.0 起删掉了浮窗功能
(floatWindows 被移除,右列交给 DSH 原生侧边栏),本机装的是 0.24.1,于是:
- 面板直接显示完整工作台(含内嵌画布)。插件的
readPlacement在侧边栏报不出floats数组时 返回known: false,而它刻意不在未知时收窄——"showing less by accident is worse than showing more",宁可多显示; - 因为不再有浮窗,面板头部不会再出现「如何浮窗?」;那个按钮只在支持浮窗的侧边栏上出现。
判断依据是能力而不是版本号:侧边栏一旦重新报出
floats,浮窗与「⛶ 全屏」按钮会自己回来; - 画布分区默认折叠,点标题展开。载入被画布拒绝时它会自动展开,标题上同时标注
⚠ 画布拒绝了最近 N 次载入——ComfyUI 自己的报错是弹在这个分区里面的,折叠着就完全看不见。
历史:0.18.0 上浮窗要靠右键 tab →「移动到自由窗口」,而「把 tab 拖到主会话区」那个手势因为中心列 锚点是旧版槽位名(
#root [data-slot="conversation"],当前 DSH 渲染的是main.conversation)而整个 不触发——与本插件无关,也不是拖拽动作本身坏了。这两条如今只对旧版侧边栏有意义。
面板里还能:选工作流 → 在画布上编辑或直接「运行工作流」→ 产物照常落进 Godot 项目并进图库。 「发送到对话」把当前提示词与参数(或选中的图)送回输入框。
表单只管 SD WebUI:ComfyUI 的一切都交给画布,面板不替它重画一套参数表单(那只会与 ComfyUI 自己的界面不一致)。所以参数区永远是 SD WebUI 的字段,ComfyUI 走「工作流」这一栏。
3. 画布桥接:对话直接改 ComfyUI 画布
为什么需要它,以及为什么它走 postMessage 而不是 HTTP
画布是 iframe,里面是另一个源(127.0.0.1:8188)的页面。浏览器不允许面板把手伸进 iframe 去点节点,
所以唯一的通路是:往 ComfyUI 里放一个属于它自己的 web 扩展。
最直觉的做法——让扩展在 iframe 里 fetch 回 DSH 的 HTTP 路由——在这台机器上不成立,原因值得记下来,
免得以后有人又"优化"回去:
DSH Desktop 会给 Electron 渲染进程的流量附加一枚每代独有的能力头 (
x-dsh-desktop-renderer),其 Web 服务器会 403 掉任何没带这枚头的请求; 而这枚头只在details.frame.origin === 载体 origin时附加 (见requestComesFromRendererOrigin)。画布 iframe 的 frame origin 是 ComfyUI 的源, 所以头不会被附加,它发给 DSH 的每一个请求都会在路由之前就被拒(403)。 这是刻意的:能力不得离开本地载体。CORS 头救不了它——请求根本没走到路由; WebSocket 升级受同一条规则约束。 (dsh-desktop.mode: advanced+openBrowser: false下,普通浏览器访问本身就是关闭的, 任何没有渲染器头的请求都会被拒。)
postMessage 才是能用的那条路:父窗口就是 DSH 页面本身,它天生持有那枚能力头。 于是面板充当中继——把指令递下去,把画布的状态捎上来:
对话 / 代理 ──image_workflow_open(to_canvas)──► 宿主画布队列
美术工坊面板 ──POST /gig/canvas/load──────────►│
▼
POST /gig/ui/poll ◄── 面板(DSH 源,同源,带渲染器头)
{ canvasMessages: [ {__gig, type:'canvas.load', seq, graph} ] }
│
└──iframe.contentWindow.postMessage(msg, 'http://127.0.0.1:8188')──► 扩展
│
POST /gig/canvas/inbound ◄── 面板转交 ◄── postMessage ◄┘
两端的门都是硬的:
| 方向 | 校验 |
|---|---|
| 面板 → 扩展 | event.origin === __DSH_ORIGIN__ 且 data.__gig === __TOKEN__ |
| 扩展 → 面板 | event.source === iframe.contentWindow(并预筛信封形状) |
| 面板 → 宿主 | 面板把信封原样转交;宿主自己验 __gig 令牌 |
令牌是每次安装烤进副本的,重新同步时复用;所以另一个 ComfyUI 安装里的旧副本既不能被本宿主驱动, 它的上报也不会被本宿主采信。面板从不解读令牌,它只是个搬运工。
代价(已知且明确):通道经过面板,所以面板必须开着。面板关着时画布指令会留在队列里(10 分钟), 下次打开再投递;用系统浏览器「外部打开」的 ComfyUI 也不在这个通道里(那边没有父窗口)。
安装 / 卸载(一键,且可逆)
面板「画布桥接(对话 ↔ ComfyUI)」卡片:
- 安装到 ComfyUI —— 把
comfyui-extension/的内容复制到<comfyRoot>/ComfyUI/custom_nodes/godot-imagegen-bridge/,并把当前页面 origin 与一枚新令牌烤进副本。 - 卸载扩展 —— 只删除清单里登记过的文件,并且只回收它自己创建的目录;不是它写的东西一律不碰。
- 安装后 必须重启 ComfyUI(
WEB_DIRECTORY是启动时读的),卡片会一直提醒这一点。
写入前一定先备份到 <dataRoot>/backups/<时间戳>/comfyui/,并附一份 BEFORE.json:
里面写清这次是不是"本来就没有东西"(existed: false)还是"备份了 N 个既有文件"。备份不会被卸载删掉——
它是审计线索,不是临时文件。
清单在 <dataRoot>/installed-files.json:令牌、origin、目标目录、每一个文件、每一个"我创建的目录"。
同步时机是有讲究的:ComfyUI 只在启动时读一次 WEB_DIRECTORY,所以
「启动后端」会先把已安装的桥接刷新到当前源码,再拉起进程——反过来做,这次启动加载的
仍然是旧副本,只能等下一次重启。而且只有内容真的不一致时才写:否则每次启动都会去动你的
ComfyUI 安装目录、还多留一份备份,毫无意义。
心跳:扩展只在两种时刻说话——启动时打个招呼,以及被问到的时候。所以宿主在每次面板轮询
里都塞一条 canvas.ping(带令牌、不带 seq,因此不进指令队列、不需要确认),面板把它转进 iframe,
扩展回一份画布摘要,面板再捎回宿主。没有这条心跳,"画布已连接"会在连上后十几秒变成"没连接"——
报告过期了,而画布其实好好的。
两个时钟是故意分开的:面板在线判定用 15 秒,画布用 60 秒。因为轮询是面板和画布共用的同一次 往返,而窗口最小化/被遮挡时 Chromium 会把定时器节流到大约每分钟一次——用 15 秒判画布,就会出现 "人切了个窗口,画布就显示掉线"。卡片上还会写明"已经 N 秒没应声",并提示多半是「画布」小节没打开 (面板窄时它默认折叠),而不是让你去重启一个本来正常的后端。
还有一个实测发现的坑:ComfyUI 启动时 import 这个包,Python 会在包里写下
__pycache__/__init__.cpython-3xx.pyc。它也算残留,所以卸载会一并删掉——但只删文件名词干
与"本次安装过的文件"对得上的 .pyc,别人放进去的 .pyc 会保留并如实报告。
对话里怎么用
image_workshop action="bridge-status" # 装没装?画布应声没?画布上有几个节点?
image_workshop action="install-bridge" # 装(会先备份;需要重启 ComfyUI)
image_workshop action="uninstall-bridge" # 卸(只删自己登记过的,零残留)
image_workshop action="read-canvas" # 画布正在编辑什么、缺哪些节点类型
image_workflow action="open" id="item-icon" # 载入面板 + 推到画布
image_workflow action="open" id="item-icon" to_canvas=false # 只给面板,不动画布
image_workflow action="patch" id="item-icon" edits=[…] to_canvas=true # 改完立刻反映到画布
两个队列是分开的:/gig/ui/poll 给面板,画布指令搭同一次往返;面板没开不会让画布收到,
画布没连上也不会让面板看不到。画布载入失败(loadGraphData 抛错)会被记下来,
bridge-status 和 GET /gig/extension 都能看到,而不是永远默默重试。
桥接没装时,open 不会偷偷把图排进画布队列——它会把 canvas:false 和原因一起返回。
否则那张图会在几分钟后你打开画布时凭空冒出来,而你早已忘了自己说过什么。
4. 后端:本机实测的能力矩阵
插件自动探测、可手动指定,auto 会优先用 defaultBackend 并自动回落。
表单里「模型」留空 ≠ 不指定模型:A1111 会用它当前已加载的那个 checkpoint。 本机实测同一个 512²/12 步请求,落在 SDXL 上 231 秒、落在 SD1.5 上约 5 秒—— 差 40 倍。面板的「模型」下拉因此会把当前加载的那个名字直接写出来 (
— 后端当前:waiIllustriousSDXL_v150.safetensors —),免得这个选择是隐形的。 草稿阶段建议显式选anything-v5。
| 能力 | SD WebUI(秋叶 v4.10,:7860) |
ComfyUI(秋叶 v2,:8188) |
|---|---|---|
| 出图 / 图生图 | ✅ waiIllustriousSDXL_v150(SDXL/Illustrious)、anything-v5(SD1.5) |
✅ 同 3 个 checkpoint(经 extra_model_paths 共享 SD WebUI 目录) |
| ControlNet | ✅ 15 个模型(openpose / lineart_anime / depth / canny / normalbae / seg / scribble / softedge / tile …) | ✅ 权重可见(同 15 个)——但本插件的 ComfyUI 通道尚未实现 ControlNet |
| 放大 | ✅ 16 个可选(R-ESRGAN 4x+ Anime6B、4x-AnimeSharp、BSRGAN、ESRGAN_4x、SwinIR_4x …) | ✅ 权重可见(6 个 .pth)——同上,未实现 |
| 反推提示词 | ✅ WD14 tagger 扩展(16 个模型,默认 wd14-convnextv2-v2;内置 /sdapi/v1/interrogators 在本机是 404) |
⚠️ 节点在、ONNX 权重缺失 |
| 人脸修复 | ✅ GFPGAN + CodeFormer | — |
| 生成式透明 | ❌ | ⚠️ layerdiffuse 节点已装但 models/layerdiffuse 权重未下载 |
| 面板一键启动 | ✅ python launch.py --api … |
✅ python -S main.py … |
关键事实:秋叶 ComfyUI 的
extra_model_paths.yaml把a111.base_path指向了 SD WebUI 安装目录, 所以 ComfyUI 能看到同一批 checkpoint / ControlNet / 放大权重(image_backend_status会实测报告数量)。 但本插件的 ComfyUI 通道目前只实现文生图与图生图——ControlNet、放大、反推请走 SD WebUI, 工具返回值里也会这样提示。
本机实测(
test-imagegen-live.mjs/test-imagegen-live-comfy.mjs,RTX 5060 Laptop / 8GB 显存):
任务 耗时 SD1.5 512²/14 步 · SD WebUI 冷启动 ≈24s(含 7GB 换模型),模型已就位 ≈7s SD1.5 512²/12 步 · ComfyUI ≈8.5s;图生图 ≈2.4s rembg(u2net) 抠图 ≈6s R-ESRGAN 4x+ Anime6B 放大 ×2 ≈1.2s ControlNet(lineart_anime) 512²/12 步 ≈4s SDXL 768² ≈60–77 秒/步 —— 18 步实测超过 15 分钟(GPU 99%、显存 7.7/8.1GB 打满) 所以默认
requestTimeoutMs定为 30 分钟。草稿阶段优先 ComfyUI + SD1.5(最快)或 SD WebUI + SD1.5, 尺寸 ≤640×640,定稿后再image_upscale。image_generate在检测到 SDXL + ≥768² 时会把耗时提示放进返回值。
所以默认后端是 SD WebUI;ComfyUI 作为 SD1.5 / 自定义工作流的第二后端。
后处理(抠图 / 放大 / 无缝平铺)走秋叶自带的 Python,它已经装了 Pillow、numpy、OpenCV、scipy、rembg、onnxruntime,所以插件不额外安装任何依赖。
5. 依赖与鸣谢
5.1 依赖
宿主(必需)
| 依赖 | 形态 | 说明 |
|---|---|---|
| DeepSeek Harness | 宿主 | 插件运行的环境(dsh.bundle + dsh.client 一个包两半) |
tools 服务 |
宿主服务 | export const inject = ['tools', 'storage'] 之一:image_* 工具的注册面 |
storage 服务(@deepseek-ai/storage-json) |
宿主服务 | 图库记录 / 用户预设 / 设置的 KV 单元,默认名 godot_imagegen(unitName 可改) |
@deepseek-ai/cordis ^4.0.1 |
peerDependency | 只是包关系:宿主半边不 import 它,而是在本地声明所需的结构类型(见 godot-imagegen.ts 的 Context),因此不产生运行时耦合 |
宿主能力(可选,有则增强)
| 能力 | 缺了会怎样 |
|---|---|
subprocess 服务 |
优先用它启动后端与跑后处理;取不到时回落到 node:child_process,功能不变 |
webServer(dsh-host-webserver) |
/gig 路由经 ctx.inject(['webServer']) 条件注册;没有它时面板与宿主之间的同源 HTTP 通道不存在 |
dsh-better-sidebar |
「美术工坊」面板的挂载点。没有它时 image_* 工具与 /gig 路由照常工作,只是看不到侧边栏面板。客户端注册走内层 ctx.inject(['betterSidebar']),所以缺宿主时不会把客户端 fiber 卡成 pending、也不会被启动健康门判成渲染器启动失败 |
外部运行时(不是 npm 依赖;插件不安装、不升级、不卸载它们)
| 依赖 | 默认地址 | 用途 |
|---|---|---|
| SD WebUI(秋叶整合包) | 127.0.0.1:7860 |
主后端:文生图、图生图、局部重绘、batch 变体、放大、反推 |
| ComfyUI(秋叶整合包) | 127.0.0.1:8188 |
第二后端:工作流库与内嵌画布 |
| 上面两套自带的 Python | — | 后处理(抠图 / 放大 / 无缝平铺)走它已装的 Pillow、numpy、OpenCV、scipy、rembg、onnxruntime。所以本插件自身不安装任何 Python 包 |
首次使用某项功能时才下载
- rembg 的 u2net 权重(约 176 MB)——
image_cutout - WD14 tagger 的 ONNX 模型——
image_interrogate(可能数百 MB、首次耗时数分钟;未装 WD14 时先退内置 deepdanbooru) - ControlNet 模型——只有用到
control_type时才需要 - 出图 checkpoint(如
anything-v5、waiIllustriousSDXL_v150)与放大权重(RealESRGAN / SwinIR / AnimeSharp)由你自己的后端提供,插件不附带也不下载
随插件分发
- ComfyUI 画布桥接 web 扩展(源码在
comfyui-extension/)。安装时把它拷进<comfyRoot>/ComfyUI/custom_nodes/godot-imagegen-bridge/——这是插件唯一的写入点,装/卸都带清单与时间戳备份,见第 10 节。
可选配对(不属于本仓库)
game-art技能:游戏美术提示词手册与分品类尺寸/透明规范。插件不依赖它,只是提示词写法与它对齐。
5.2 鸣谢
这个插件能成立,靠的是一整条上游链路。感谢:
- ComfyUI(comfyanonymous)——工作流引擎,以及被内嵌进面板的那个真实画布;
- Stable Diffusion WebUI(AUTOMATIC1111)——主后端与
/sdapi/v1/*接口面; - 秋叶整合包(绘世启动器)——把上述两套后端在 Windows 上装成开箱可用,并自带后处理所需的 Python 环境;本插件的默认路径、端口与启动方式都是按它设计的;
- rembg(danielgatis)与 U²-Net(Xuebin Qin)——抠图;
- Real-ESRGAN(Xintao Wang)、SwinIR(Jingyun Liang)与 4x-AnimeSharp——放大权重;
- WD14 tagger 扩展(picobyte)、DeepDanbooru(Kichang Kim)与 SmilingWolf 的 WD v1.4 系列模型——反推提示词;
- ControlNet(Lvmin Zhang)——保姿势 / 保构图;
dsh-better-sidebar——「美术工坊」面板赖以挂载的侧边栏宿主。
也感谢各家 checkpoint 与放大权重的作者:插件提供的是接口,画出来什么样是他们的功劳。
本插件自身以 MIT 分发(见 LICENSE)。上面每一项都有自己的许可证,它们的条款不因被本插件调用而改变——尤其是各家 checkpoint / 权重,商用与再分发前请先读它们各自的授权。
6. 安装与挂载
插件本体就是一个独立仓库(也就是你正在看的这个),通过与 godot-asset-planner 完全相同的 bundle 通道挂载。
推荐:走官方 CLI。 包内的 cordis.patch.yml 就是 dsh.bundle 层,CLI 装包时会据此把
godot-imagegen 追加进 dsh.profile.bundles,不需要手工改 profile:
dsh plugin --profile <profile> add godot-imagegen
手工挂载(本地开发 / 直接指向本仓库时用):
Junction(把包放进 profile 的
node_modules,客户端半端才能被 client-modules 扫描到)New-Item -ItemType Junction ` -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\godot-imagegen" ` -Target "<本仓库的绝对路径>"profile 的 package.json:
dependencies加"godot-imagegen": "link:<本仓库的绝对路径>",dsh.profile.bundles数组追加"godot-imagegen"。包自身的cordis.patch.yml会插入宿主插件行。profiles/desktop(桌面应用)与profiles/web(dsh web)都需要按上述方式挂载。重启 DSH Desktop:新增 bundle 行在启动时合并,重启前
/gig路由与image_*工具都不存在。 之后纯客户端改动(lib/client.js)刷新页面即可;宿主改动(godot-imagegen.ts)需再次重启。
安装后:
- 工具出现在会话的工具目录里(所有 profile 下的会话共享);
- 「美术工坊」出现在 better-sidebar(需要
dsh-better-sidebar,desktop / web profile 都有); - 在面板「设置」里填 Godot 项目根目录,之后产物才会带
res://路径。
7. 配置项
都写在 profile 的 cordis.patch.yml 里,作为 godot-imagegen 行的 config(全部可选,都有可用默认值):
- id: godot-imagegen
config:
godotProject: D:/my-game # 产物落 <root>/assets/generated 并获得 res:// 路径
# outDir: D:/art-out # 显式覆盖输出根目录(优先于 godotProject)
# unitName: godot_imagegen # 图库 KV 单元名
# sdwebuiRoot: <秋叶根目录>/sd-webui-aki-v4.10
# sdwebuiPython: <秋叶根目录>/sd-webui-aki-v4.10/python/python.exe
# sdwebuiPort: 7860
# comfyRoot: <秋叶根目录>/ComfyUI-aki-v2
# comfyPython: <秋叶根目录>/ComfyUI-aki-v2/python/python.exe
# comfyPort: 8188
# postPython: 后处理解释器(默认用 ComfyUI 的那个)
# defaultBackend: sdwebui | comfyui
# sdwebuiModel / comfyModel: 默认 checkpoint 文件名
# dataRoot: <DSH_HOME>/godot-imagegen # 工作流库 / 布局缓存 / 备份 / 安装清单
# extensionSource: <本仓库>/comfyui-extension # 画布扩展源码目录
# requestTimeoutMs: 1800000 # 单次后端操作超时(默认 30 分钟;SDXL 长任务需要)
# startTimeoutMs: 360000 # 「启动后端」等待就绪的上限
godotProject 也可以直接在面板「设置」里填,会存进插件自己的 storage(跨会话保留),不必改配置。
extensionSource 默认取本模块旁边的 comfyui-extension/(按 import.meta.url 解析),所以仓库搬家、
或者经 profile junction 加载,都不需要改这一项。
8. 工具速查
image_generate — 文生图
必填 prompt。关键参数:category(character / item / ui / tile / background / texture / concept)、
width height steps cfg_scale sampler seed batch model backend、
transparent + transparent_method(auto / rembg / chroma / generative)、seamless、
control_image + control_type + control_weight、project_id out_dir。
category 决定落盘子目录、默认尺寸与默认是否透明:
| category | 默认尺寸 | 默认透明 | 说明 |
|---|---|---|---|
| character | 832×1216 | ✅ | 角色立绘 / 精灵图 |
| item | 1024×1024 | ✅ | 道具 / 技能 / 状态图标 |
| ui | 1024×1024 | ✅ | 按钮 / 边框 / 九宫格 |
| tile | 768×768 | ❌ | 瓦片(默认做无缝平铺后处理) |
| background | 1344×768 | ❌ | 背景 / 视差层 |
| texture | 1024×1024 | ❌ | 3D / PBR 贴图(默认做无缝平铺) |
| concept | 1024×1024 | ❌ | 概念图 |
其余七个
image_edit— 图生图 / 局部重绘(image+mask+denoise),支持 ControlNet 保姿势image_variants— 一次出多个变体(count),SD WebUI 走单次 batchimage_upscale—method: auto|esrgan|lanczos,优先 SD WebUI 的动漫向 RealESRGANimage_cutout—method: auto|rembg|chroma,输出带 alpha 的 PNG(首次 rembg 会下载 ~176 MB 的 u2net 模型)image_interrogate— 反推提示词:先试内置 deepdanbooru,404 时自动改用 WD14 tagger 扩展 (首次调用会下载所选的 ONNX 模型,可能数分钟),用来对齐已有美术风格image_gallery—list/delete/clear,按项目与品类过滤image_backend_status—status/models/start/stop
image 参数接受三种形式:绝对路径、res:// 路径、图库 id(如 img_1a2b3c4d5e6f)。
image_workflow — 把 ComfyUI 工作流当成可编辑对象
action:list / read / create / patch / save / to-api / to-ui / run / open / delete
关键是 patch 按参数名改,而不是按下标:
{ "action": "patch", "id": "sd15-basic",
"edits": [
{ "classType": "CLIPTextEncode", "title": "正向提示词", "set": { "text": "a red potion, game icon" } },
{ "classType": "KSampler", "set": { "steps": 9, "seed": 1234 } }
] }
为什么这点很重要:UI 格式的 widgets_values 是位置数组,而 ComfyUI 给任何带
control_after_generate 的 widget(也就是每个 seed)多留一个槽位。按下标改会让 seed
之后的每个字段整体错位——不报错,只是安静地出一张参数全乱的图。本插件的布局是从
/object_info 推导的,并且会缓存,所以后端关着也能继续编辑。
run 会把工作流跑出来的图按普通生成物一样归档:落进 <项目>/assets/generated/<品类>/、
拿到 res:// 路径、进图库。工作流没有 SaveImage/PreviewImage 节点时会明确报错,而不是空转到超时。
open 会同时把整份图推给内嵌画布(走桥接扩展);to_canvas: false 可以只给面板。
image_workshop — 让代理直接操作面板与画布
action:fill-form / focus / select-image / toast / read-state / status
- 画布:
bridge-status/install-bridge/uninstall-bridge/read-canvas
面板在它已有的 1.5s 轮询里一次往返完成两件事:上报自己在看什么(含未保存的草稿),取走代理排队的指令。 面板没打开时指令会排队(保留 10 分钟),工具会明确告诉你"已排队、未送达",而不是假装成功。
画布四个动作用的是另一条通道(桥接扩展的轮询),状态与面板互不影响。
9. 与 godot-asset-planner 的协作
不共享 storage。 godot_asset_planner 这个 KV 单元每个后端只允许一个活动句柄,归 godot-asset-planner 所有,
所以本插件用自己的 godot_imagegen 单元,二者互不干扰。
两条联动路径:
- 对话里:
image_generate的返回值里就带着res://路径,直接godot_register_asset(asset_name, asset_type="image", file_path="res://assets/generated/...")。 - 面板里:灯箱的「一键注册」走同源 HTTP 调 godot-asset-planner 的
POST /gap/assets({ assetName, assetType:"image", filePath, projectId, linkedGoal? }),不碰它的存储实现。 面板顶部会显示从/gap/state读到的当前 planner 项目作为归属。
10. 安全边界
- 只接管、不重复启动:后端已在运行时
action=start直接复用,绝不新起一个抢端口。 - 绝不终止非本插件启动的进程:
action=stop对你自己用绘世启动器拉起的实例会明确拒绝并说明原因。 - 后端启动是面板上点按钮才发生,不会在你不知情时占显存。
- 插件 dispose 时会 abort 在飞任务并关闭 KV 单元,避免热重载后
unit is already open。 - 唯一的写入点在画布桥接扩展,而且它守四条规矩:
- 写入对象只有
<comfyRoot>/ComfyUI/custom_nodes/godot-imagegen-bridge/,绝不碰别处; - 写之前先做带
BEFORE.json的时间戳备份,备份永不自动删除; - 每个写过的文件都进清单,卸载只删清单里的路径,越出目标目录的记录会被拒绝执行并报错;
- 只回收它自己创建的目录(安装前就存在的目录不记录、不删)。 目录里出现清单之外的文件时,卸载会报告残留而不是假装干净——那些不是它写的,它不删。
- 写入对象只有
- 画布通道不开任何网络入口:扩展不发 HTTP、不开 socket,只用 postMessage 与父窗口(也就是本面板)通信,
并且双向都要过 origin + 每次安装的令牌。宿主这一侧没有任何跨域路由可以被打(
/gig全部同源)。 - 工作流库、节点布局缓存、备份目录都在
$DSH_HOME/godot-imagegen/下,不在你的 ComfyUI 安装里。 - 不自动安装:桥接扩展的首次安装必须是显式动作(面板按钮或
install-bridge); 只有"已经装过"时,「启动后端」才会在拉起进程之前重新同步一次,且只在内容不一致时才写。
11. 验证
cd <本仓库根目录>
npm test # = 下面五条"离线"套件;每套自己打印 N passed / M failed
node tests/test-workflow.mjs # 工作流层,含 10 个真实 ComfyUI 导出文件
node tests/test-workflow-library.mjs # 工作流库、6 套模板、布局缓存
node tests/test-imagegen.mjs # 10 个工具 + REST + 真实 Python 后处理 + 面板那条路与进度行
node tests/test-extension.mjs # 桥接:备份/清单/卸载/令牌/中继/心跳/零残留/真跑扩展/启动同步
node tests/test-canvas-e2e.mjs # 端到端——用**本机真实安装的那个 .js** 走真实宿主路由,
# 跑通心跳/推送/拒绝/回执,并复现"16 秒后是否掉线";只读
node tests/test-render-imagegen.mjs # 客户端面板渲染,含入口 / 工作台 / 全屏三种布局与浮窗几何补丁,
# 真的跑一遍画布中继与心跳,
# 并断言 启动/停止 真的挂在面板上、点下去真的打到宿主
node tests/test-imagegen-live.mjs # 真实 SD WebUI 端到端(后端没起时自动跳过)——
# 含面板「生成」按钮真正用的 POST /gig/generate 那条路
node tests/test-imagegen-live-comfy.mjs # 真实 ComfyUI 端到端,含真跑内置模板(没起时跳过)
node tests/test-extension-live.mjs # 已装好时:对真实安装的**副本**验证卸载零残留,真实目录不动;
# 没装时走写路径:真装真卸、全树扫描无残片
node tests/test-extension-live.mjs --serve # 先安装、再自己启动 ComfyUI,验证它真的把扩展 serve 出来
# (要 ComfyUI 先关着)
node --check lib/client.js
# 技能 frontmatter / 行尾校验(可选:需要本机另有 godot-asset-planner 仓库与 game-art 技能目录)
# node <godot-asset-planner>/check-skill-frontmatter.mjs "<技能目录>"
这套件不引用固定的断言条数:每个套件自己打印 N passed, M failed,以实际输出为准。
条数会随两件事变化,都是设计而不是漏测:
- 后端是否在跑:
test-workflow.mjs与test-workflow-library.mjs在后端应答时会顺手拿 真实的/object_info再验一遍「真实工作流的 UI→API 转换」与「模板能否在本机跑」;后端没起时 它们打印一行NOTE ... skipped,而不是假装通过。 - 本机装了什么模型:那几条实时检查只能描述跑它的那台机器。缺某个 checkpoint 时它打印一行
NOTE,点名缺哪个模型、影响哪些模板,而不是让整套变红——所以一台只装了 SDXL 的机器,跑出来的 条数会比全装齐的机器少。
这里此前引用的是一组实测总数(按套件逐条列出)。它们随开发不断漂移,早已不再准确, 也每加一条断言就要重修一次——所以改成了不引用条数,只说明变化的原因。
画布扩展的测试分层(每层都跑在能跑到的最真实的一层):
| 层 | 谁在测 | 覆盖 |
|---|---|---|
| 管理器 | test-extension.mjs |
备份内容、清单、令牌复用、越界拒删、既有目录不回收、零残留(含 __pycache__)、启动前同步、在线窗口 |
| 宿主路由 | test-extension.mjs |
心跳信封、指令队列与 ack、失败上报、卸载后不再 ping、409 拒绝无去处的推送 |
| 扩展本体 | test-extension.mjs |
把装进 ComfyUI 的那个 .js 丢进 node:vm:错 origin/错令牌/无令牌/垃圾载荷都不载入,正确信封真的调到 app.loadGraphData,拒绝时回 ok:false |
| 面板中继 | test-render-imagegen.mjs |
真的跑 effect:信封以 postMessage 投进 iframe(targetOrigin 是 ComfyUI 源)、回包搭下一次轮询上行、心跳在空转 tick 上照样发出、冒充窗口的消息被丢弃 |
| 端到端 | test-canvas-e2e.mjs |
真实安装的 .js + 真实宿主路由 + 真实工作流库:心跳→在线、推送→loadGraphData→回执→清队列、拒绝→错误可见、16 秒不掉线;只读(断言清单与扩展的 mtime 未变) |
| 真机写路径 | test-extension-live.mjs |
真的写进你真实的 <comfyRoot> 再真的卸干净、全树扫描无残片。已装好时自动改为对副本验证:复制真实安装(含 __pycache__)到临时目录、改写清单路径后卸载,并断言真实目录与清单分毫未动 |
- 离线套件用两个真实 HTTP 服务冒充 A1111 与 ComfyUI(覆盖
/sdapi/v1/*、/controlnet/model_list、/tagger/v1/*、/prompt、/history、/view),并用真实的秋叶 Python 跑抠图与放大, 因此断言的是真实行为而不是 mock 行为;同时覆盖重启持久化与热重载时的 KV 单元释放。 - 扩展套件在一个临时假 ComfyUI 目录上把桥接的每条承诺都跑一遍:备份内容、清单内容、
令牌复用、越界路径拒删、既有目录不回收、非本插件文件保留、卸载后零残留(含 ComfyUI 导入时
生成的
__pycache__),以及中继的令牌校验、指令投递与 ack、失败上报,并且断言 不存在任何跨域 HTTP 入口(扩展只会用 postMessage)。 它还把真正装进 ComfyUI 的那个 .js 放进一个node:vm里,配一个假的 window 与假的 ComfyUI app 跑起来:错 origin / 错令牌 / 无令牌 / 垃圾载荷都不得触发载入,正确的信封必须真的调到app.loadGraphData并回一个带 seq 的结果,画布拒绝载入时必须回ok:false与原因。 - 客户端套件用最小 React 替身递归执行每个组件函数(本机磁盘上没有 react/react-dom,
DSH 的客户端运行时在 app.asar 内),因此未定义标识符、错误的解构、坏的 createElement 调用都会暴露。
它还会真的跑一遍 effect:装一个假
fetch与假定时器,让面板的空转轮询与窗口消息监听真正执行, 于是中继的两个方向都被验证——宿主排队的信封确实以postMessage投进 iframe(且 targetOrigin 是 ComfyUI 的源、绝不是*),而画布的回包会搭下一次轮询送回宿主,冒充窗口发来的消息被丢弃。 - 实时套件驱动真正安装的 SD WebUI / ComfyUI:真实出图、真实 rembg 抠图、真实 R-ESRGAN 放大、
真实 ControlNet 引导,并断言产物真的落在 Godot 项目的
assets/generated/<品类>/且带res://路径; 桥接的实时套件则真的写进你真实的<comfyRoot>,再真的卸干净并全树扫描确认无残片。--serve只有在"先安装、再由它启动 ComfyUI"的顺序下才能验证「ComfyUI 把扩展 serve 出来」—— 它会自己启动、自己关掉;发现 ComfyUI 已经开着时跳过这两项并说明原因,而不是假装失败或假装通过。
已实测的关键前提(用浏览器实际会发的请求头打真实 ComfyUI):
| 请求 | 结果 | 含义 |
|---|---|---|
iframe 导航到 :8188/(sec-fetch-site: cross-site + DSH referer) |
200,无 X-Frame-Options、无 CSP |
画布可以被内嵌 |
从 DSH 页取 :8188/extensions/...(无 Origin) |
200(装好后)/ 404(没装) | 扩展脚本能在页面里加载 |
从 DSH 页 fetch(':8188/...')(带 Origin: DSH) |
403(origin_only_middleware) |
面板不能直接跟 ComfyUI 说话——所以工作流层必须走宿主 |
| 画布内部同源 API 调用 | 200 | 画布自身功能正常 |
「拖到主会话区」那个手势(已无意义,留作记录)
早期文档在这里给过一个补丁:把查找中心列的 JS 字符串从"只认旧槽位名"改成"两个都认", 以换回「把 tab 拖到主会话区」的浮窗手势。这个补丁现在没有意义了,不要打——
- 两个 profile 早已不是那时钉的
0.18.0/0.16.1,而是 0.24.1; - 更根本的是:
dsh-better-sidebar从 v0.19.0 起删掉了浮窗功能本身, 所以那个手势即使修好也无处可去。
画布现在就在侧边栏里用(见第 2 节)。这一节只用来解释为什么旧文档里会出现那段操作。
12. 已知限制
- 没有浮窗:
dsh-better-sidebar从 v0.19.0 起删掉了floatWindows,所以内嵌画布只能在侧边栏里用 (见第 2 节)。插件按能力判断而不是版本号:侧边栏若重新报出floats,浮窗与全屏按钮会自己回来。 详见第 2 节。 - 后端调用不使用全局
fetch:undici 有 300s 的headersTimeout/bodyTimeout且无法在无 undici 包时调高, 真实 SDXL 出图(7GB 换模型 + 采样)会超过它并抛UND_ERR_HEADERS_TIMEOUT。插件改用node:http, 超时只受自身的requestTimeoutMs控制。 - ComfyUI 通道只实现文生图 / 图生图。它能看到 SD WebUI 共享来的 ControlNet / 放大权重, 但通过工作流 JSON 驱动 ControlNet、UltimateSDUpscale、WD14 尚未接线;这些能力请走 SD WebUI。
transparent_method: generative(ComfyUI layerdiffuse 直出)已实现工作流接线, 但需要先把 layerdiffuse 权重放进ComfyUI/models/layerdiffuse(extra_model_paths没有映射它)。image_cutout的rembg首次运行会下载约 176 MB 的 u2net 模型(本机已缓存则瞬间完成)。- 桥接扩展有
.js改动就必须重启 ComfyUI(WEB_DIRECTORY只在启动时读),不是热重载的。 - 画布通道经过面板(postMessage),所以面板必须开着;用系统浏览器「外部打开」的 ComfyUI 不在通道里。这不是偷懒:DSH 会给"frame origin 不是载体 origin"的请求一律 403, iframe 自己发不出任何请求,父窗口是唯一通路。
- 面板不做九宫格切片、图集打包、调色板量化等"后期"加工——那些更适合放在 Godot 导入侧或技能里。
- RTX 5060 Laptop 上 SDXL 出图偏慢,建议先用 SD1.5 试参数再放大。
No comments yet. Be the first to write one.