天晴动作工具组文档 天晴动作工具组文档
首页
内网站 (opens new window)
天晴盒子
Biped动画库
脚本文档
开发公约
  • MAXScript2020 Help (opens new window)
  • 3dsmax-2023-MAXScript Help (opens new window)
  • 3dsmax-2023-Max-Python Help (opens new window)
  • DeveloperSDK2023 Help (opens new window)
教程
更新
关于
  • 动画重定向
  • 3ds Max 文件降版本
  • GIF播放器
  • 表情绑定助手
  • MAXtoUnrealTools
  • MMD4Max
  • AnimFiltersMax2021
  • 分类
  • 标签
  • 归档
首页
内网站 (opens new window)
天晴盒子
Biped动画库
脚本文档
开发公约
  • MAXScript2020 Help (opens new window)
  • 3dsmax-2023-MAXScript Help (opens new window)
  • 3dsmax-2023-Max-Python Help (opens new window)
  • DeveloperSDK2023 Help (opens new window)
教程
更新
关于
  • 动画重定向
  • 3ds Max 文件降版本
  • GIF播放器
  • 表情绑定助手
  • MAXtoUnrealTools
  • MMD4Max
  • AnimFiltersMax2021
  • 分类
  • 标签
  • 归档
  • CATRig

  • NetSDK(C#)动画曲线插件课程
  • skinRig

  • comfy-ui

    • ComfyUI 独立环境插件路由注册问题排障实录
    • ComfyUI-Sundaybox上传下载插件
    • ComfyUI节点上传文件后立即加载失败排障实录
      • 背景
      • 现象复盘
      • 根因
        • 失败案例代码:枚举字段导致前置拦截
      • 方案演进(踩坑记录)
        • 方案 A:仅靠 VALIDATE_INPUTS
        • 方案 B:双后端字段(mesh_file 下拉 + file_path STRING)
        • 方案 C(最终):后端仅保留 file_path(STRING) + 前端 UI 下拉回填
      • 最终实现设计
        • 后端(nodes/mesh_io.py)
        • 前端(web/load_mesh_dynamic.js)
      • 技术点补充:VALIDATE_INPUTS 与 IS_CHANGED 的边界
        • VALIDATE_INPUTS
        • IS_CHANGED
      • 快速排障代码片段
      • 为什么重启后会“暂时正常”
      • 验证清单
      • 常见误区
      • 经验总结
  • FBXMetaData
  • Engine

  • MaxPython_Msx

  • motionbuilder

  • Ai

  • superman

  • AI视频动捕产品调研
  • RigNet自动绑定角色-AI- 部署测试
  • MotoricaAI-MoGen 动画合成
  • 3dsMax与Spine互导工具
  • EasyMocap视频动捕部署测试
  • FreeMocap无标记视频动捕部署
  • RootMotion和InPlace动画差异
  • 虚幻物理资产导出XML
  • 关闭骨骼移动带转父级的特性
  • 简易Biped绑定框架方案
  • 关于蒙皮权重镜像匹配问题介绍以及解决方案
  • GVHMR视频动捕部署
  • UniRig自动骨骼蒙皮部署
  • 从AI视频跟踪到2D骨骼动画制作
  • PC资源转口袋资源流程
  • 教程
  • comfy-ui
Joe
2026-04-28
目录

ComfyUI节点上传文件后立即加载失败排障实录

# ComfyUI节点上传文件后立即加载失败排障实录

# 背景

在 ComfyUI-UniRig 的 UniRigLoadMesh 节点中,用户通过上传得到新文件后,界面下拉里已经能看到该文件,但执行时仍报错:

  • Value not in list
  • POST /api/prompt 400 (Bad Request)
  • 典型信息:file_path 或 mesh_file 不在后端枚举列表中

重启 ComfyUI 后同一文件又可加载,说明问题与“运行时状态/校验时序”有关,而不是文件本身损坏。


# 现象复盘

实际链路中出现了一个典型矛盾:

  1. 前端下拉已刷新,用户可见新文件;
  2. 但后端在接收 prompt 时,仍按旧的枚举快照做校验;
  3. 校验失败后直接返回 400,节点函数 load_mesh 根本不执行。

这也解释了为什么在 load_mesh 里加日志看不到输出:报错发生在函数调用之前。


# 根因

根因是 ComfyUI 对“枚举型输入”有执行前强校验。

当 INPUT_TYPES 中某个字段定义为:

  • (list_of_values, {...})

该字段会在 prompt 校验阶段要求“值必须在当前列表中”。 如果上传与列表刷新存在短暂竞态(前端看到了新值,但后端校验用的是旧列表),就会触发 value_not_in_list。

关键点:这一步在节点执行之前,VALIDATE_INPUTS/load_mesh 都可能来不及介入。

# 失败案例代码:枚举字段导致前置拦截

下面这种写法会把 file_path 作为“必须命中列表”的后端输入。一旦前后端快照短暂不一致,就会触发 value_not_in_list:

@classmethod
def INPUT_TYPES(cls):
    mesh_files = cls.get_mesh_files_from_input()
    if not mesh_files:
        mesh_files = ["No mesh files found"]
    return {
        "required": {
            "source_folder": (["input", "output"], {}),
            "file_path": (mesh_files, {  # 枚举输入,执行前会做 in-list 校验
                "file_upload": True,
            }),
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 方案演进(踩坑记录)

# 方案 A:仅靠 VALIDATE_INPUTS

结论:不够。 VALIDATE_INPUTS 只能补充业务校验,不能稳定绕过枚举字段的系统前置校验。

# 方案 B:双后端字段(mesh_file 下拉 + file_path STRING)

思路:执行用 file_path,下拉仅辅助。 问题:只要 mesh_file 仍是后端输入字段,prompt 仍会校验它;一旦它值不在后端快照列表,依旧 400。

# 方案 C(最终):后端仅保留 file_path(STRING) + 前端 UI 下拉回填

最终稳定方案:

  1. 后端 INPUT_TYPES 不再暴露枚举文件字段;
  2. 执行参数仅用 file_path(STRING + file_upload: true);
  3. 前端扩展动态创建一个纯 UI 下拉(不进入 prompt 数据);
  4. 用户选择下拉时,把值写入 file_path。

这样既保留下拉体验,又规避了 value_not_in_list。


# 最终实现设计

# 后端(nodes/mesh_io.py)

  • source_folder: 保留枚举(input / output)
  • file_path: 改为 STRING,作为唯一执行路径输入
  • obj_path: 继续保留上传覆盖能力
  • load_mesh 内路径解析顺序:
    1. obj_path(若非空)
    2. file_path
    3. 依据 source_folder 在 input/output 中解析相对路径
  • 对路径做规范化(去引号、统一分隔符)减少 Windows/URL 差异

后端关键片段(简化版):

@classmethod
def INPUT_TYPES(cls):
    selected_source = getattr(cls, "_last_source_folder", "input")
    return {
        "required": {
            "source_folder": (["input", "output"], {
                "default": selected_source,
            }),
            "file_path": ("STRING", {
                "default": "",
                "multiline": False,
                "file_upload": True,  # 上传完成后可把路径写入 file_path
            }),
            "obj_path": ("STRING", {
                "default": "",
                "multiline": False,
            }),
        }
    }

def load_mesh(self, source_folder, file_path="", obj_path=""):
    self.__class__._last_source_folder = source_folder if source_folder in ("input", "output") else "input"

    # 上传覆盖优先
    if obj_path and obj_path.strip():
        full_path = resolve_obj_path(obj_path)
        return (must_load_mesh(full_path),)

    if not file_path or not file_path.strip():
        raise ValueError("file_path is empty")

    normalized = file_path.strip().strip('"')
    if not os.path.isabs(normalized):
        normalized = normalized.replace("\\\\", "/")

    full_path = resolve_by_source_folder(source_folder, normalized)
    return (must_load_mesh(full_path),)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 前端(web/load_mesh_dynamic.js)

  • 监听 source_folder
  • 请求 /unirig/mesh-files?source_folder=... 获取候选
  • 在节点上动态添加 combo(如 mesh_selector)作为“纯 UI 下拉”
  • 下拉变化时自动回填 file_path

因为 mesh_selector 不是后端输入字段,所以不会参与 prompt 枚举校验。

前端关键片段(简化版):

const sourceWidget = this.widgets?.find((w) => w.name === "source_folder");
const filePathWidget = this.widgets?.find((w) => w.name === "file_path");
const selectorWidget =
  this.widgets?.find((w) => w.name === "mesh_selector") ||
  this.addWidget("combo", "mesh_selector", "", () => {}, { values: ["No mesh files found"] });

const refreshFileOptions = async () => {
  const source = sourceWidget.value || "input";
  const resp = await fetch(`/unirig/mesh-files?source_folder=${encodeURIComponent(source)}`);
  if (!resp.ok) return;

  const data = await resp.json();
  const files = Array.isArray(data?.files) && data.files.length > 0 ? data.files : ["No mesh files found"];
  selectorWidget.options = selectorWidget.options || {};
  selectorWidget.options.values = files;

  if (!files.includes(selectorWidget.value)) selectorWidget.value = files[0];
  if (!filePathWidget.value || !files.includes(filePathWidget.value)) {
    filePathWidget.value = selectorWidget.value;
  }
};

selectorWidget.callback = () => {
  filePathWidget.value = selectorWidget.value || "";
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 技术点补充:VALIDATE_INPUTS 与 IS_CHANGED 的边界

# VALIDATE_INPUTS

  • 作用:业务校验(路径空值、扩展名等)
  • 局限:无法稳定绕过枚举字段的系统前置校验

示例:

@classmethod
def VALIDATE_INPUTS(cls, source_folder, file_path="", obj_path=""):
    if source_folder not in ("input", "output"):
        return "source_folder must be input/output"
    if obj_path and obj_path.strip():
        return True
    if not isinstance(file_path, str) or not file_path.strip():
        return "file_path cannot be empty"
    return True
1
2
3
4
5
6
7
8
9

# IS_CHANGED

  • 作用:告诉 ComfyUI 缓存是否失效
  • 建议:根据文件 mtime 返回变化值,而不是删除该函数

示例:

@classmethod
def IS_CHANGED(cls, source_folder, file_path="", obj_path=""):
    target = (obj_path or file_path or "").strip().strip('"')
    if not target:
        return f"{source_folder}:empty"
    if os.path.exists(target):
        return os.path.getmtime(target)
    return f"{source_folder}:{target}"
1
2
3
4
5
6
7
8

# 快速排障代码片段

当怀疑“函数未执行”时,可在 load_mesh 入口加入:

_emit_visible_log("load_mesh entered: source=%s file_path=%s obj_path=%s", source_folder, file_path, obj_path)
1

若控制台没有这条日志,同时前端有 /api/prompt 400,基本可判定是“执行前校验失败”,不是加载逻辑错误。


# 为什么重启后会“暂时正常”

重启后前后端状态被统一重建:

  • 后端重新扫描并构建枚举;
  • 前端也拿到同步后的列表;
  • 短时间内前后端快照一致,因此不报错。

但只要再次出现“上传完成 -> 前端可见 -> 后端校验快照未同步”的窗口期,问题会复现。


# 验证清单

实施最终方案后,建议按以下步骤验证:

  1. 上传一个新 mesh(此前不存在);
  2. UI 下拉能看到该文件;
  3. file_path 自动回填到对应路径;
  4. 执行不再出现 value_not_in_list;
  5. Network 中 /api/prompt 返回 200;
  6. load_mesh 日志可见,证明进入节点执行阶段。

# 常见误区

  1. 误区:​​load_mesh​​ ** 没日志就是函数没写对** 实际可能是前置校验已拦截,函数根本没机会执行。
  2. 误区:​​VALIDATE_INPUTS​​ ** 一定能兜底** 对枚举输入不成立,系统层校验优先级更高。
  3. 误区:只要前端下拉显示了就一定能执行 显示的是前端状态,执行依赖后端 prompt 校验状态。

# 经验总结

在 ComfyUI 自定义节点里,凡是“动态变化快”的值(上传文件、外部列表、异步扫描结果),尽量避免把它作为后端枚举输入。 更稳妥的模式是:

  • 执行参数用 ​​STRING​
  • 可视化选择放前端 UI 层
  • 前端只做“辅助选值”,后端只做“路径解析与业务校验”

这能显著减少前后端状态竞态导致的 400 校验错误。

ComfyUI-Sundaybox上传下载插件
FBXMetaData

← ComfyUI-Sundaybox上传下载插件 FBXMetaData→

最近更新
01
ComfyUI-Sundaybox上传下载插件
04-27
02
ComfyUI 独立环境插件路由注册问题排障实录
04-24
03
Biped骨骼缩放控制
04-23
更多文章>
Theme by Vdoing | Copyright © 2019-2026 ND|99u:199505| 鄂ICP备2022012500号 | 鄂公网安备 42022202000122号

共产主义:是对生产资料的共享,不是对生活资料财产的均分

  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式