diff --git a/README.ZH_CN.md b/README.ZH_CN.md index 1c6b435..3e0e137 100644 --- a/README.ZH_CN.md +++ b/README.ZH_CN.md @@ -1,486 +1,486 @@ -![comfyui-easy-use](https://github.com/user-attachments/assets/9b7a5e44-f5e2-4c27-aed2-d0e6b50c46bb) - -
-视频介绍 | -文档 (康明孙) | -工作流合集 | -捐助 -

- - -
- -**ComfyUI-Easy-Use** 是一个化繁为简的节点整合包, 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上进行延展,并针对了诸多主流的节点包做了整合与优化,以达到更快更方便使用ComfyUI的目的,在保证自由度的同时还原了本属于Stable Diffusion的极致畅快出图体验。 - -## 👨🏻‍🎨 特色介绍 - -- 沿用了 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的思路,大大减少了折腾工作流的时间成本。 -- UI界面美化,首次安装的用户,如需使用UI主题,请在 Settings -> Color Palette 中自行切换主题并**刷新页面**即可 -- 增加了预采样参数配置的节点,可与采样节点分离,更方便预览。 -- 支持通配符与Lora的提示词节点,如需使用Lora Block Weight用法,需先保证自定义节点包中安装了 [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) -- 可多选的风格化提示词选择器,默认是Fooocus的样式json,可自定义json放在styles底下,samples文件夹里可放预览图(名称和name一致,图片文件名如有空格需转为下划线'_') -- 加载器可开启A1111提示词风格模式,可重现与webui生成近乎相同的图像,需先安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) -- 可使用`easy latentNoisy`或`easy preSamplingNoiseIn`节点实现对潜空间的噪声注入 -- 简化 SD1.x、SD2.x、SDXL、SVD、Zero123等流程 -- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#1-13-stable-cascade) -- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-3-layerdiffusion) -- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) -- 简化 IPAdapter, 需先保证自定义节点包中安装最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) -- 扩展 XYplot 的可用性 -- 整合了Fooocus Inpaint功能 -- 整合了常用的逻辑计算、转换类型、展示所有类型等 -- 支持节点上checkpoint、lora模型子目录分类及预览图 (请在设置中开启上下文菜单嵌套子目录) -- 支持BriaAI的RMBG-1.4模型的背景去除节点,[技术参考](https://huggingface.co/briaai/RMBG-1.4) -- 支持 强制清理comfyUI模型显存占用 -- 支持Stable Diffusion 3 多账号API节点 -- 支持IC-Light的应用 [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-5-ic-light) | [代码整合来源](https://github.com/huchenlei/ComfyUI-IC-Light) | [技术参考](https://github.com/lllyasviel/IC-Light) -- 中文提示词自动识别,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en) -- 支持 sd3 模型 -- 支持 kolors 模型 -- 支持 flux 模型 -- 支持 惰性条件判断(ifElse)和 for循环 - -## 👨🏻‍🔧 安装 - -1. 将存储库克隆到 **custom_nodes** 目录并安装依赖 -```shell -#1. git下载 -git clone https://github.com/yolain/ComfyUI-Easy-Use -#2. 安装依赖 -双击install.bat安装依赖 -``` - -## 👨🏻‍🚀 计划 - -- [x] 更新便于维护的新前端代码 - - [x] 使用sass维护css样式 - - [x] 对原有扩展进行优化 - - [x] 增加新的组件(如节点时间统计等) -- [ ] 在[ComfyUI-Yolain-Workflows](https://github.com/yolain/ComfyUI-Yolain-Workflows)中上传更多的工作流(如kolors,sd3等),并更新english版本的readme -- [ ] 更详细功能介绍的 gitbook - -## 📜 更新日志 - -**v1.2.5** - -- 支持mochi模型 -- 实现在循环主体中重复使用终端节点输出(例如预览图像和显示任何内容等输出节点...) - -**v1.2.4** - -- 增加 `easy imageSplitTiles` and `easy imageTilesFromBatch` - 图像分块 -- 支持 `model_override`,`vae_override`,`clip_override` 可以在 `easy fullLoader` 中单独输入 -- 增加 `easy saveImageLazy` -- 增加 `easy loadImageForLoop` -- 增加 `easy isFileExist` -- 增加 `easy saveText` - -**v1.2.3** - -- `easy showAnything` 和 `easy cleanGPUUsed` 增加输出插槽 -- 添加新的人体分割在 `easy humanSegmentation` 节点上 - 代码从 [ComfyUI_Human_Parts](https://github.com/metal3d/ComfyUI_Human_Parts) 整合 -- 当你在 `easy preSamplingCustom` 节点上选择basicGuider,CFG>0 且当前模型为Flux时,将使用FluxGuidance -- 增加 `easy loraStackApply` and `easy controlnetStackApply` - -**v1.2.2** - -- 增加 `easy batchAny` -- 增加 `easy anythingIndexSwitch` -- 增加 `easy forLoopStart` 和 `easy forLoopEnd` -- 增加 `easy ifElse` -- 增加 v2 版本新前端代码 -- 增加 `easy fluxLoader` -- 增加 `controlnetApply` 相关节点对sd3和hunyuanDiT的支持 -- 修复 当使用fooocus inpaint后,再使用Lora模型无法生效的问题 - -**v1.2.1** - -- 增加 `easy ipadapterApplyFaceIDKolors` -- `easy ipadapterApply` 和 `easy ipadapterApplyADV` 增加 **PLUS (kolors genernal)** 和 **FACEID PLUS KOLORS** 预置项 -- `easy imageRemBg` 增加 **inspyrenet** 选项 -- 增加 `easy controlnetLoader++` -- 去除 `easy positive` `easy negative` 等prompt节点的自动将中文翻译功能,自动翻译仅在 `easy a1111Loader` 等不支持中文TE的加载器中生效 -- 增加 `easy kolorsLoader` - 可灵加载器,参考了 [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) 和 [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper) 的代码。 - -**v1.2.0** - -- 增加 `easy pulIDApply` 和 `easy pulIDApplyADV` -- 增加 `easy hunyuanDiTLoader` 和 `easy pixArtLoader` -- 当新菜单的位置在上或者下时增加上 crystools 的显示,推荐开两个就好(如果后续crystools有更新UI适配我可能会删除掉) -- 增加 **easy sliderControl** - 滑块控制节点,当前可用于控制ipadapterMS的参数 (双击滑块可重置为默认值) -- 增加 **layer_weights** 属性在 `easy ipadapterApplyADV` 节点 - -**v1.1.9** - -- 增加 新的调度器 **gitsScheduler** -- 增加 `easy imageBatchToImageList` 和 `easy imageListToImageBatch` (修复Impact版的一点小问题) -- 递归模型子目录嵌套 -- 支持 sd3 模型 -- 增加 `easy applyInpaint` - 局部重绘全模式节点 (相比与之前的kSamplerInpating节点逻辑会更合理些) - -**v1.1.8** - -- 增加中文提示词自动翻译,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en), 默认已对wildcard、lora正则处理, 其他需要保留的中文,可使用`@你的提示词@`包裹 (若依赖安装完成后报错, 请重启),测算大约会占0.3GB显存 -- 增加 `easy controlnetStack` - controlnet堆 -- 增加 `easy applyBrushNet` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json) -- 增加 `easy applyPowerPaint` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json) - -**v1.1.7** - -- 修复 一些模型(如controlnet模型等)未成功写入缓存,导致修改前置节点束参数(如提示词)需要二次载入模型的问题 -- 增加 `easy prompt` - 主体和光影预置项,后期可能会调整 -- 增加 `easy icLightApply` - 重绘光影, 从[ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)优化 -- 增加 `easy imageSplitGrid` - 图像网格拆分 -- `easy kSamplerInpainting` 的 **additional** 属性增加差异扩散和brushnet等相关选项 -- 增加 brushnet模型加载的支持 - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) -- 增加 `easy applyFooocusInpaint` - Fooocus内补节点 替代原有的 FooocusInpaintLoader -- 移除 `easy fooocusInpaintLoader` - 容易bug,不再使用 -- 修改 easy kSampler等采样器中并联的model 不再替换输出中pipe里的model - -**v1.1.6** - -- 增加步调齐整适配 - 在所有的预采样和全采样器节点中的 调度器(schedulder) 增加了 **alignYourSteps** 选项 -- `easy kSampler` 和 `easy fullkSampler` 的 **image_output** 增加 **Preview&Choose**选项 -- 增加 `easy styleAlignedBatchAlign` - 风格对齐 [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy) -- 增加 `easy ckptNames` -- 增加 `easy controlnetNames` -- 增加 `easy imagesSplitimage` - 批次图像拆分单张 -- 增加 `easy imageCount` - 图像数量 -- 增加 `easy textSwitch` - 文字切换 - -**v1.1.5** - -- 重写 `easy cleanGPUUsed` - 可强制清理comfyUI的模型显存占用 -- 增加 `easy humanSegmentation` - 多类分割、人像分割 -- 增加 `easy imageColorMatch` -- 增加 `easy ipadapterApplyRegional` -- 增加 `easy ipadapterApplyFromParams` -- 增加 `easy imageInterrogator` - 图像反推 -- 增加 `easy stableDiffusion3API` - 简易的Stable Diffusion 3 多账号API节点 - -**v1.1.4** - -- 增加 `easy imageChooser` - 从[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker)简化的图片选择器 -- 增加 `easy preSamplingCustom` - 自定义预采样,可支持cosXL-edit -- 增加 `easy ipadapterStyleComposition` -- 增加 在Loaders上右键菜单可查看 checkpoints、lora 信息 -- 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本 -- 修复 FooocusInpaint修改ModelPatcher计算权重引发的问题,理应在生成model后重置ModelPatcher为默认值 - -**v1.1.3** - -- `easy ipadapterApply` 增加 **COMPOSITION** 预置项 -- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持 -- 增加 `easy promptLine` -- 增加 `easy promptReplace` -- 增加 `easy promptConcat` -- `easy wildcards` 增加 **multiline_mode**属性 -- 增加 当节点需要下载模型时,若huggingface连接超时,会切换至镜像地址下载模型 - -
-v1.1.2 - -- 改写 EasyUse 相关节点的部分插槽推荐节点 -- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图 -- 增加 `easy sv3dLoader` -- 增加 `easy dynamiCrafterLoader` -- 增加 `easy ipadapterApply` -- 增加 `easy ipadapterApplyADV` -- 增加 `easy ipadapterApplyEncoder` -- 增加 `easy ipadapterApplyEmbeds` -- 增加 `easy preMaskDetailerFix` -- `easy kSamplerInpainting` 增加 **additional** 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning -- 修复 `easy stylesSelector` 当未选择样式时,原有提示词发生了变化 -- 修复 `easy pipeEdit` 提示词输入lora时报错 -- 修复 layerDiffuse xyplot相关bug -
- -
-v1.1.1/b> - -- 修复首次添加含seed的节点且当前模式为control_before_generate时,seed为0的问题 -- `easy preSamplingAdvanced` 增加 **return_with_leftover_noise** -- 修复 `easy stylesSelector` 当选择自定义样式文件时运行队列报错 -- `easy preSamplingLayerDiffusion` 增加 mask 可选传入参数 -- 将所有 **seed_num** 调整回 **seed** -- 修补官方BUG: 当control_mode为before 在首次加载页面时未修改节点中widget名称为 control_before_generate -- 去除强制**control_before_generate**设定 -- 增加 `easy imageRemBg` - 默认为BriaAI的RMBG-1.4模型, 移除背景效果更加,速度更快 -
- -
-v1.1.0 - -- 增加 `easy imageSplitList` - 拆分每 N 张图像 -- 增加 `easy preSamplingDiffusionADDTL` - 可配置前景、背景、blended的additional_prompt等 -- 增加 `easy preSamplingNoiseIn` 可替代需要前置的`easy latentNoisy`节点 实现效果更好的噪声注入 -- `easy pipeEdit` 增加 条件拼接模式选择,可选择替换、合并、联结、平均、设置条件时间 -- 增加 `easy pipeEdit` - 可编辑Pipe的节点(包含可重新输入提示词) -- 增加 `easy preSamplingLayerDiffusion` 与 `easy kSamplerLayerDiffusion` (连接 `easy kSampler` 也能通) -- 增加 在 加载器、预采样、采样器、Controlnet等节点上右键可快速替换同类型节点的便捷菜单 -- 增加 `easy instantIDApplyADV` 可连入 positive 与 negative -- 修复 `easy wildcards` 读取lora未填写完整路径时未自动检索导致加载lora失败的问题 -- 修复 `easy instantIDApply` mask 未传入正确值 -- 修复 在 非a1111提示词风格下 BREAK 不生效的问题 -
- -
-v1.0.9 - -- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错 -- 修复 `easy pipeIn` - pipe设为可不必选 -- 增加 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid) -- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表 -- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错 -
- -
-v1.0.8 - -- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)) -- `easy styleSelector` 搜索框修改为不区分大小写匹配 -- `easy fullLoader` 增加 **positive**、**negative**、**latent** 输出项 -- 修复 SDXLClipModel 在 ComfyUI 修订版本号 2016[c2cb8e88] 及以上的报错(判断了版本号可兼容老版本) -- 修复 `easy detailerFix` 批次大小大于1时生成出错 -- 修复`easy preSampling`等 latent传入后无法根据批次索引生成的问题 -- 修复 `easy svdLoader` 报错 -- 优化代码,减少了诸多冗余,提升运行速度 -- 去除中文翻译对照文本 - -(翻译对照已由 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation) 统一维护啦! -首次下载或者版本较早的朋友请更新 AIGODLIKE-COMFYUI-TRANSLATION 和本节点包至最新版本。) -
- -
-v1.0.7 - -- 增加 `easy cascadeLoader` - stable cascade 加载器 -- 增加 `easy preSamplingCascade` - stabled cascade stage_c 预采样参数 -- 增加 `easy fullCascadeKSampler` - stable cascade stage_c 完整版采样器 -- 增加 `easy cascadeKSampler` - stable cascade stage-c ksampler simple -
- -
-v1.0.6 - -- 增加 `easy XYInputs: Checkpoint` -- 增加 `easy XYInputs: Lora` -- `easy seed` 增加固定种子值时可手动切换随机种 -- 修复 `easy fullLoader`等加载器切换lora时自动调整节点大小的问题 -- 去除原有ttn的图片保存逻辑并适配ComfyUI默认的图片保存格式化扩展 -
- -
-v1.0.5 - -- 增加 `easy isSDXL` -- `easy svdLoader` 增加提示词控制, 可配合open_clip模型进行使用 -- `easy wildcards` 增加 **populated_text** 可输出通配填充后文本 -
- -
-v1.0.4 - -- 增加 `easy showLoaderSettingsNames` 可显示与输出加载器部件中的 模型与VAE名称 -- 增加 `easy promptList` - 提示词列表 -- 增加 `easy fooocusInpaintLoader` - Fooocus内补节点(仅支持XL模型的流程) -- 增加 **Logic** 逻辑类节点 - 包含类型、计算、判断和转换类型等 -- 增加 `easy imageSave` - 带日期转换和宽高格式化的图像保存节点 -- 增加 `easy joinImageBatch` - 合并图像批次 -- `easy showAnything` 增加支持转换其他类型(如:tensor类型的条件、图像等) -- `easy kSamplerInpainting` 增加 **patch** 传入值,配合Fooocus内补节点使用 -- `easy imageSave` 增加 **only_preivew** - -- 修复 xyplot在pillow>9.5中报错 -- 修复 `easy wildcards` 在使用PS扩展插件运行时报错 -- 修复 `easy latentCompositeMaskedWithCond` -- 修复 `easy XYInputs: ControlNet` 报错 -- 修复 `easy loraStack` **toggle** 为 disabled 时报错 - -- 修改首次安装节点包不再自动替换主题,需手动调整并刷新页面 -
- -
-v1.0.3 - -- 增加 `easy stylesSelector` 风格化提示词选择器 -- 增加队列进度条设置项,默认为未启用状态 -- `easy controlnetLoader` 和 `easy controlnetLoaderADV` 增加参数 **scale_soft_weights** - - -- 修复 `easy XYInputs: Sampler/Scheduler` 报错 -- 修复 右侧菜单 点击按钮时老是跑位的问题 -- 修复 styles 路径在其他环境报错 -- 修复 `easy comfyLoader` 读取错误 -- 修复 xyPlot 在连接 zero123 时报错 -- 修复加载器中提示词为组件时报错 -- 修复 `easy getNode` 和 `easy setNode` 加载时标题未更改 -- 修复所有采样器中存储图片使用子目录前缀不生效的问题 - - -- 调整UI主题 -
- -
-v1.0.2 - -- 增加 **autocomplete** 文件夹,如果您安装了 [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts), 将在启动时合并该文件夹下的所有txt文件并覆盖到pyssss包里的autocomplete.txt文件。 -- 增加 `easy XYPlotAdvanced` 和 `easy XYInputs` 等相关节点 -- 增加 **Alt+1到9** 快捷键,可快速粘贴 Node templates 的节点预设 (对应 1到9 顺序) - -- 修复 `easy imageInsetCrop` 测量值为百分比时步进为1 -- 修复 开启 `a1111_prompt_style` 时XY图表无法使用的问题 -- 右键菜单中增加了一个 `📜Groups Map(EasyUse)` - -- 修复在Comfy新版本中UI加载失败 -- 修复 `easy pipeToBasicPipe` 报错 -- 修改 `easy fullLoader` 和 `easy a1111Loader` 中的 **a1111_prompt_style** 默认值为 False -- `easy XYInputs ModelMergeBlocks` 支持csv文件导入数值 - -- 替换了XY图生成时的字体文件 - -- 移除 `easy imageRemBg` -- 移除包中的介绍图和工作流文件,减少包体积 - -
- -
-v1.0.1 - -- 新增 `easy seed` - 简易随机种 -- `easy preDetailerFix` 新增了 `optional_image` 传入图像可选,如未传默认取值为pipe里的图像 -- 新增 `easy kSamplerInpainting` 用于内补潜空间的采样器 -- 新增 `easy pipeToBasicPipe` 用于转换到Impact的某些节点上 - -- 修复 `easy comfyLoader` 报错 -- 修复所有包含输出图片尺寸的节点取值方式无法批处理的问题 -- 修复 `width` 和 `height` 无法在 `easy svdLoader` 自定义的报错问题 -- 修复所有采样器预览图片的地址链接 (解决在 MACOS 系统中图片无法在采样器中预览的问题) -- 修复 `vae_name` 在 `easy fullLoader` 和 `easy a1111Loader` 和 `easy comfyLoader` 中选择但未替换原始vae问题 -- 修复 `easy fullkSampler` 除pipe外其他输出值的报错 -- 修复 `easy hiresFix` 输入连接pipe和image、vae同时存在时报错 -- 修复 `easy fullLoader` 中 `model_override` 连接后未执行 -- 修复 因新增`easy seed` 导致action错误 -- 修复 `easy xyplot` 的字体文件路径读取错误 -- 修复 convert 到 `easy seed` 随机种无法固定的问题 -- 修复 `easy pipeIn` 值传入的报错问题 -- 修复 `easy zero123Loader` 和 `easy svdLoader` 读取模型时将模型加入到缓存中 -- 修复 `easy kSampler` `easy kSamplerTiled` `easy detailerFix` 的 `image_output` 默认值为 Preview -- `easy fullLoader` 和 `easy a1111Loader` 新增了 `a1111_prompt_style` 参数可以重现和webui生成相同的图像,当前您需要安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) 才能使用此功能 -
- -
-v1.0.0 - -- 新增`easy positive` - 简易正面提示词文本 -- 新增`easy negative` - 简易负面提示词文本 -- 新增`easy wildcards` - 支持通配符和Lora选择的提示词文本 -- 新增`easy portraitMaster` - 肖像大师v2.2 -- 新增`easy loraStack` - Lora堆 -- 新增`easy fullLoader` - 完整版的加载器 -- 新增`easy zero123Loader` - 简易zero123加载器 -- 新增`easy svdLoader` - 简易svd加载器 -- 新增`easy fullkSampler` - 完整版的采样器(无分离) -- 新增`easy hiresFix` - 支持Pipe的高清修复 -- 新增`easy predetailerFix` `easy DetailerFix` - 支持Pipe的细节修复 -- 新增`easy ultralyticsDetectorPipe` `easy samLoaderPipe` - 检测加载器(细节修复的输入项) -- 新增`easy pipein` `easy pipeout` - Pipe的输入与输出 -- 新增`easy xyPlot` - 简易的xyplot (后续会更新更多可控参数) -- 新增`easy imageRemoveBG` - 图像去除背景 -- 新增`easy imagePixelPerfect` - 图像完美像素 -- 新增`easy poseEditor` - 姿势编辑器 -- 新增UI主题(黑曜石)- 默认自动加载UI, 也可在设置中自行更替 - -- 修复 `easy globalSeed` 不生效问题 -- 修复所有的`seed_num` 因 [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) 实时更新图表导致值错乱的问题 -- 修复`easy imageSize` `easy imageSizeBySide` `easy imageSizeByLongerSide` 可作为终节点 -- 修复 `seed_num` (随机种子值) 在历史记录中读取无法一致的Bug -
- - -
-v0.5 - -- 新增 `easy controlnetLoaderADV` 节点 -- 新增 `easy imageSizeBySide` 节点,可选输出为长边或短边 -- 新增 `easy LLLiteLoader` 节点,如果您预先安装过 kohya-ss/ControlNet-LLLite-ComfyUI 包,请将 models 里的模型文件移动至 ComfyUI\models\controlnet\ (即comfy默认的controlnet路径里,请勿修改模型的文件名,不然会读取不到)。 -- 新增 `easy imageSize` 和 `easy imageSizeByLongerSize` 输出的尺寸显示。 -- 新增 `easy showSpentTime` 节点用于展示图片推理花费时间与VAE解码花费时间。 -- `easy controlnetLoaderADV` 和 `easy controlnetLoader` 新增 `control_net` 可选传入参数 -- `easy preSampling` 和 `easy preSamplingAdvanced` 新增 `image_to_latent` 可选传入参数 -- `easy a1111Loader` 和 `easy comfyLoader` 新增 `batch_size` 传入参数 - -- 修改 `easy controlnetLoader` 到 loader 分类底下。 -
- -## 整合参考到的相关节点包 - -声明: 非常尊重这些原作者们的付出,开源不易,我仅仅只是做了一些整合与优化。 - -| 节点名 (搜索名) | 相关的库 | 库相关的节点 | -|:-------------------------------|:----------------------------------------------------------------------------|:------------------------| -| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode | -| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode | -| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 | -| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master | -| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader | -| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) | -| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | -| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | -| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop | -| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode | -| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute | -| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 | -| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter | -| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser | -| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign | -| easy icLightApply | [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) | ICLightApply等 | -| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader | - -## Credits - -[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - 功能强大且模块化的Stable Diffusion GUI - -[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI管理器 - -[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - 管道节点(节点束)让用户减少了不必要的连接 - -[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - diffus3的获取与设置点让用户可以分离工作流构成 - -[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - 常规整合包1 - -[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2 - -[ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) - ComfyUI逻辑运算 - -[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - 让模型生成不受训练分辨率限制 - -[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - 风格迁移 - -[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - 人脸迁移 - -[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - 人脸迁移 - -[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss 小蛇🐍脚本 - -[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - 图片选择器 - -[ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) - BrushNet 内补节点 - -[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT架构相关节点(Pixart、混元DiT等) - -## ☕️ Donation - -**Comfyui-Easy-Use** 是一个 GPL 许可的开源项目。为了项目取得更好、可持续的发展,我希望能够获得更多的支持。 如果我的自定义节点为您的一天增添了价值,请考虑喝杯咖啡来进一步补充能量! 💖感谢您的支持,每一杯咖啡都是我创作的动力! - -- [BiliBili充电](https://space.bilibili.com/1840885116) -- [爱发电](https://afdian.com/a/yolain) -- [Wechat/Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03) - -感谢您的捐助,我将用这些费用来租用 GPU 或购买其他 GPT 服务,以便更好地调试和完善 ComfyUI-Easy-Use 功能 - -## 🌟Stargazers - -My gratitude extends to the generous souls who bestow a star. Your support is much appreciated! - +![comfyui-easy-use](https://github.com/user-attachments/assets/9b7a5e44-f5e2-4c27-aed2-d0e6b50c46bb) + +
+视频介绍 | +文档 (康明孙) | +工作流合集 | +捐助 +

+ + +
+ +**ComfyUI-Easy-Use** 是一个化繁为简的节点整合包, 在 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的基础上进行延展,并针对了诸多主流的节点包做了整合与优化,以达到更快更方便使用ComfyUI的目的,在保证自由度的同时还原了本属于Stable Diffusion的极致畅快出图体验。 + +## 👨🏻‍🎨 特色介绍 + +- 沿用了 [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) 的思路,大大减少了折腾工作流的时间成本。 +- UI界面美化,首次安装的用户,如需使用UI主题,请在 Settings -> Color Palette 中自行切换主题并**刷新页面**即可 +- 增加了预采样参数配置的节点,可与采样节点分离,更方便预览。 +- 支持通配符与Lora的提示词节点,如需使用Lora Block Weight用法,需先保证自定义节点包中安装了 [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) +- 可多选的风格化提示词选择器,默认是Fooocus的样式json,可自定义json放在styles底下,samples文件夹里可放预览图(名称和name一致,图片文件名如有空格需转为下划线'_') +- 加载器可开启A1111提示词风格模式,可重现与webui生成近乎相同的图像,需先安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) +- 可使用`easy latentNoisy`或`easy preSamplingNoiseIn`节点实现对潜空间的噪声注入 +- 简化 SD1.x、SD2.x、SDXL、SVD、Zero123等流程 +- 简化 Stable Cascade [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#1-13-stable-cascade) +- 简化 Layer Diffuse [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-3-layerdiffusion) +- 简化 InstantID [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid), 需先保证自定义节点包中安装了 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) +- 简化 IPAdapter, 需先保证自定义节点包中安装最新版v2的 [ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) +- 扩展 XYplot 的可用性 +- 整合了Fooocus Inpaint功能 +- 整合了常用的逻辑计算、转换类型、展示所有类型等 +- 支持节点上checkpoint、lora模型子目录分类及预览图 (请在设置中开启上下文菜单嵌套子目录) +- 支持BriaAI的RMBG-1.4模型的背景去除节点,[技术参考](https://huggingface.co/briaai/RMBG-1.4) +- 支持 强制清理comfyUI模型显存占用 +- 支持Stable Diffusion 3 多账号API节点 +- 支持IC-Light的应用 [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-5-ic-light) | [代码整合来源](https://github.com/huchenlei/ComfyUI-IC-Light) | [技术参考](https://github.com/lllyasviel/IC-Light) +- 中文提示词自动识别,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en) +- 支持 sd3 模型 +- 支持 kolors 模型 +- 支持 flux 模型 +- 支持 惰性条件判断(ifElse)和 for循环 + +## 👨🏻‍🔧 安装 + +1. 将存储库克隆到 **custom_nodes** 目录并安装依赖 +```shell +#1. git下载 +git clone https://github.com/yolain/ComfyUI-Easy-Use +#2. 安装依赖 +双击install.bat安装依赖 +``` + +## 📜 更新日志 + +**v1.2.6** + +- 修复因设置节点中缺少相连接的自定义节点而导致弄乱画布的问题 + +**v1.2.5** + +- 在 `easy preSamplingCustom` 和 `easy preSamplingAdvanced` 上增加 `enable (GPU=A1111)` 噪波生成模式选择项 +- 增加 `easy makeImageForICLora` +- 在 `easy ipadapterApply` 添加 `REGULAR - FLUX and SD3.5 only (high strength)` 预置项以支持 InstantX Flux ipadapter +- 修复brushnet 无法在 `--fast` 模式下使用 +- 支持briaai RMBG-2.0 +- 支持mochi模型 +- 实现在循环主体中重复使用终端节点输出(例如预览图像和显示任何内容等输出节点...) + +**v1.2.4** + +- 增加 `easy imageSplitTiles` and `easy imageTilesFromBatch` - 图像分块 +- 支持 `model_override`,`vae_override`,`clip_override` 可以在 `easy fullLoader` 中单独输入 +- 增加 `easy saveImageLazy` +- 增加 `easy loadImageForLoop` +- 增加 `easy isFileExist` +- 增加 `easy saveText` + +**v1.2.3** + +- `easy showAnything` 和 `easy cleanGPUUsed` 增加输出插槽 +- 添加新的人体分割在 `easy humanSegmentation` 节点上 - 代码从 [ComfyUI_Human_Parts](https://github.com/metal3d/ComfyUI_Human_Parts) 整合 +- 当你在 `easy preSamplingCustom` 节点上选择basicGuider,CFG>0 且当前模型为Flux时,将使用FluxGuidance +- 增加 `easy loraStackApply` and `easy controlnetStackApply` + +**v1.2.2** + +- 增加 `easy batchAny` +- 增加 `easy anythingIndexSwitch` +- 增加 `easy forLoopStart` 和 `easy forLoopEnd` +- 增加 `easy ifElse` +- 增加 v2 版本新前端代码 +- 增加 `easy fluxLoader` +- 增加 `controlnetApply` 相关节点对sd3和hunyuanDiT的支持 +- 修复 当使用fooocus inpaint后,再使用Lora模型无法生效的问题 + +**v1.2.1** + +- 增加 `easy ipadapterApplyFaceIDKolors` +- `easy ipadapterApply` 和 `easy ipadapterApplyADV` 增加 **PLUS (kolors genernal)** 和 **FACEID PLUS KOLORS** 预置项 +- `easy imageRemBg` 增加 **inspyrenet** 选项 +- 增加 `easy controlnetLoader++` +- 去除 `easy positive` `easy negative` 等prompt节点的自动将中文翻译功能,自动翻译仅在 `easy a1111Loader` 等不支持中文TE的加载器中生效 +- 增加 `easy kolorsLoader` - 可灵加载器,参考了 [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) 和 [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper) 的代码。 + +**v1.2.0** + +- 增加 `easy pulIDApply` 和 `easy pulIDApplyADV` +- 增加 `easy hunyuanDiTLoader` 和 `easy pixArtLoader` +- 当新菜单的位置在上或者下时增加上 crystools 的显示,推荐开两个就好(如果后续crystools有更新UI适配我可能会删除掉) +- 增加 **easy sliderControl** - 滑块控制节点,当前可用于控制ipadapterMS的参数 (双击滑块可重置为默认值) +- 增加 **layer_weights** 属性在 `easy ipadapterApplyADV` 节点 + +**v1.1.9** + +- 增加 新的调度器 **gitsScheduler** +- 增加 `easy imageBatchToImageList` 和 `easy imageListToImageBatch` (修复Impact版的一点小问题) +- 递归模型子目录嵌套 +- 支持 sd3 模型 +- 增加 `easy applyInpaint` - 局部重绘全模式节点 (相比与之前的kSamplerInpating节点逻辑会更合理些) + +**v1.1.8** + +- 增加中文提示词自动翻译,使用[opus-mt-zh-en模型](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en), 默认已对wildcard、lora正则处理, 其他需要保留的中文,可使用`@你的提示词@`包裹 (若依赖安装完成后报错, 请重启),测算大约会占0.3GB显存 +- 增加 `easy controlnetStack` - controlnet堆 +- 增加 `easy applyBrushNet` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json) +- 增加 `easy applyPowerPaint` - [示例参考](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json) + +**v1.1.7** + +- 修复 一些模型(如controlnet模型等)未成功写入缓存,导致修改前置节点束参数(如提示词)需要二次载入模型的问题 +- 增加 `easy prompt` - 主体和光影预置项,后期可能会调整 +- 增加 `easy icLightApply` - 重绘光影, 从[ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light)优化 +- 增加 `easy imageSplitGrid` - 图像网格拆分 +- `easy kSamplerInpainting` 的 **additional** 属性增加差异扩散和brushnet等相关选项 +- 增加 brushnet模型加载的支持 - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) +- 增加 `easy applyFooocusInpaint` - Fooocus内补节点 替代原有的 FooocusInpaintLoader +- 移除 `easy fooocusInpaintLoader` - 容易bug,不再使用 +- 修改 easy kSampler等采样器中并联的model 不再替换输出中pipe里的model + +**v1.1.6** + +- 增加步调齐整适配 - 在所有的预采样和全采样器节点中的 调度器(schedulder) 增加了 **alignYourSteps** 选项 +- `easy kSampler` 和 `easy fullkSampler` 的 **image_output** 增加 **Preview&Choose**选项 +- 增加 `easy styleAlignedBatchAlign` - 风格对齐 [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy) +- 增加 `easy ckptNames` +- 增加 `easy controlnetNames` +- 增加 `easy imagesSplitimage` - 批次图像拆分单张 +- 增加 `easy imageCount` - 图像数量 +- 增加 `easy textSwitch` - 文字切换 + +**v1.1.5** + +- 重写 `easy cleanGPUUsed` - 可强制清理comfyUI的模型显存占用 +- 增加 `easy humanSegmentation` - 多类分割、人像分割 +- 增加 `easy imageColorMatch` +- 增加 `easy ipadapterApplyRegional` +- 增加 `easy ipadapterApplyFromParams` +- 增加 `easy imageInterrogator` - 图像反推 +- 增加 `easy stableDiffusion3API` - 简易的Stable Diffusion 3 多账号API节点 + +**v1.1.4** + +- 增加 `easy imageChooser` - 从[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker)简化的图片选择器 +- 增加 `easy preSamplingCustom` - 自定义预采样,可支持cosXL-edit +- 增加 `easy ipadapterStyleComposition` +- 增加 在Loaders上右键菜单可查看 checkpoints、lora 信息 +- 修复 `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` 以兼容ComfyUI Revision>=2098 [0542088e] 以上版本 +- 修复 FooocusInpaint修改ModelPatcher计算权重引发的问题,理应在生成model后重置ModelPatcher为默认值 + +**v1.1.3** + +- `easy ipadapterApply` 增加 **COMPOSITION** 预置项 +- 增加 对[ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) lora模型 的加载支持 +- 增加 `easy promptLine` +- 增加 `easy promptReplace` +- 增加 `easy promptConcat` +- `easy wildcards` 增加 **multiline_mode**属性 +- 增加 当节点需要下载模型时,若huggingface连接超时,会切换至镜像地址下载模型 + +
+v1.1.2 + +- 改写 EasyUse 相关节点的部分插槽推荐节点 +- 增加 **启用上下文菜单自动嵌套子目录** 设置项,默认为启用状态,可分类子目录及checkpoints、loras预览图 +- 增加 `easy sv3dLoader` +- 增加 `easy dynamiCrafterLoader` +- 增加 `easy ipadapterApply` +- 增加 `easy ipadapterApplyADV` +- 增加 `easy ipadapterApplyEncoder` +- 增加 `easy ipadapterApplyEmbeds` +- 增加 `easy preMaskDetailerFix` +- `easy kSamplerInpainting` 增加 **additional** 属性,可设置成 Differential Diffusion 或 Only InpaintModelConditioning +- 修复 `easy stylesSelector` 当未选择样式时,原有提示词发生了变化 +- 修复 `easy pipeEdit` 提示词输入lora时报错 +- 修复 layerDiffuse xyplot相关bug +
+ +
+v1.1.1/b> + +- 修复首次添加含seed的节点且当前模式为control_before_generate时,seed为0的问题 +- `easy preSamplingAdvanced` 增加 **return_with_leftover_noise** +- 修复 `easy stylesSelector` 当选择自定义样式文件时运行队列报错 +- `easy preSamplingLayerDiffusion` 增加 mask 可选传入参数 +- 将所有 **seed_num** 调整回 **seed** +- 修补官方BUG: 当control_mode为before 在首次加载页面时未修改节点中widget名称为 control_before_generate +- 去除强制**control_before_generate**设定 +- 增加 `easy imageRemBg` - 默认为BriaAI的RMBG-1.4模型, 移除背景效果更加,速度更快 +
+ +
+v1.1.0 + +- 增加 `easy imageSplitList` - 拆分每 N 张图像 +- 增加 `easy preSamplingDiffusionADDTL` - 可配置前景、背景、blended的additional_prompt等 +- 增加 `easy preSamplingNoiseIn` 可替代需要前置的`easy latentNoisy`节点 实现效果更好的噪声注入 +- `easy pipeEdit` 增加 条件拼接模式选择,可选择替换、合并、联结、平均、设置条件时间 +- 增加 `easy pipeEdit` - 可编辑Pipe的节点(包含可重新输入提示词) +- 增加 `easy preSamplingLayerDiffusion` 与 `easy kSamplerLayerDiffusion` (连接 `easy kSampler` 也能通) +- 增加 在 加载器、预采样、采样器、Controlnet等节点上右键可快速替换同类型节点的便捷菜单 +- 增加 `easy instantIDApplyADV` 可连入 positive 与 negative +- 修复 `easy wildcards` 读取lora未填写完整路径时未自动检索导致加载lora失败的问题 +- 修复 `easy instantIDApply` mask 未传入正确值 +- 修复 在 非a1111提示词风格下 BREAK 不生效的问题 +
+ +
+v1.0.9 + +- 修复未安装 ComfyUI-Impack-Pack 和 ComfyUI_InstantID 时报错 +- 修复 `easy pipeIn` - pipe设为可不必选 +- 增加 `easy instantIDApply` - 需要先安装 [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID), 工作流参考[示例](https://github.com/yolain/ComfyUI-Yolain-Workflows?tab=readme-ov-file#2-2-instantid) +- 修复 `easy detailerFix` 未添加到保存图片格式化扩展名可用节点列表 +- 修复 `easy XYInputs: PromptSR` 在替换负面提示词时报错 +
+ +
+v1.0.8 + +- `easy cascadeLoader` stage_c 与 stage_b 支持checkpoint模型 (需要下载[checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints)) +- `easy styleSelector` 搜索框修改为不区分大小写匹配 +- `easy fullLoader` 增加 **positive**、**negative**、**latent** 输出项 +- 修复 SDXLClipModel 在 ComfyUI 修订版本号 2016[c2cb8e88] 及以上的报错(判断了版本号可兼容老版本) +- 修复 `easy detailerFix` 批次大小大于1时生成出错 +- 修复`easy preSampling`等 latent传入后无法根据批次索引生成的问题 +- 修复 `easy svdLoader` 报错 +- 优化代码,减少了诸多冗余,提升运行速度 +- 去除中文翻译对照文本 + +(翻译对照已由 [AIGODLIKE-COMFYUI-TRANSLATION](https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation) 统一维护啦! +首次下载或者版本较早的朋友请更新 AIGODLIKE-COMFYUI-TRANSLATION 和本节点包至最新版本。) +
+ +
+v1.0.7 + +- 增加 `easy cascadeLoader` - stable cascade 加载器 +- 增加 `easy preSamplingCascade` - stabled cascade stage_c 预采样参数 +- 增加 `easy fullCascadeKSampler` - stable cascade stage_c 完整版采样器 +- 增加 `easy cascadeKSampler` - stable cascade stage-c ksampler simple +
+ +
+v1.0.6 + +- 增加 `easy XYInputs: Checkpoint` +- 增加 `easy XYInputs: Lora` +- `easy seed` 增加固定种子值时可手动切换随机种 +- 修复 `easy fullLoader`等加载器切换lora时自动调整节点大小的问题 +- 去除原有ttn的图片保存逻辑并适配ComfyUI默认的图片保存格式化扩展 +
+ +
+v1.0.5 + +- 增加 `easy isSDXL` +- `easy svdLoader` 增加提示词控制, 可配合open_clip模型进行使用 +- `easy wildcards` 增加 **populated_text** 可输出通配填充后文本 +
+ +
+v1.0.4 + +- 增加 `easy showLoaderSettingsNames` 可显示与输出加载器部件中的 模型与VAE名称 +- 增加 `easy promptList` - 提示词列表 +- 增加 `easy fooocusInpaintLoader` - Fooocus内补节点(仅支持XL模型的流程) +- 增加 **Logic** 逻辑类节点 - 包含类型、计算、判断和转换类型等 +- 增加 `easy imageSave` - 带日期转换和宽高格式化的图像保存节点 +- 增加 `easy joinImageBatch` - 合并图像批次 +- `easy showAnything` 增加支持转换其他类型(如:tensor类型的条件、图像等) +- `easy kSamplerInpainting` 增加 **patch** 传入值,配合Fooocus内补节点使用 +- `easy imageSave` 增加 **only_preivew** + +- 修复 xyplot在pillow>9.5中报错 +- 修复 `easy wildcards` 在使用PS扩展插件运行时报错 +- 修复 `easy latentCompositeMaskedWithCond` +- 修复 `easy XYInputs: ControlNet` 报错 +- 修复 `easy loraStack` **toggle** 为 disabled 时报错 + +- 修改首次安装节点包不再自动替换主题,需手动调整并刷新页面 +
+ +
+v1.0.3 + +- 增加 `easy stylesSelector` 风格化提示词选择器 +- 增加队列进度条设置项,默认为未启用状态 +- `easy controlnetLoader` 和 `easy controlnetLoaderADV` 增加参数 **scale_soft_weights** + + +- 修复 `easy XYInputs: Sampler/Scheduler` 报错 +- 修复 右侧菜单 点击按钮时老是跑位的问题 +- 修复 styles 路径在其他环境报错 +- 修复 `easy comfyLoader` 读取错误 +- 修复 xyPlot 在连接 zero123 时报错 +- 修复加载器中提示词为组件时报错 +- 修复 `easy getNode` 和 `easy setNode` 加载时标题未更改 +- 修复所有采样器中存储图片使用子目录前缀不生效的问题 + + +- 调整UI主题 +
+ +
+v1.0.2 + +- 增加 **autocomplete** 文件夹,如果您安装了 [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts), 将在启动时合并该文件夹下的所有txt文件并覆盖到pyssss包里的autocomplete.txt文件。 +- 增加 `easy XYPlotAdvanced` 和 `easy XYInputs` 等相关节点 +- 增加 **Alt+1到9** 快捷键,可快速粘贴 Node templates 的节点预设 (对应 1到9 顺序) + +- 修复 `easy imageInsetCrop` 测量值为百分比时步进为1 +- 修复 开启 `a1111_prompt_style` 时XY图表无法使用的问题 +- 右键菜单中增加了一个 `📜Groups Map(EasyUse)` + +- 修复在Comfy新版本中UI加载失败 +- 修复 `easy pipeToBasicPipe` 报错 +- 修改 `easy fullLoader` 和 `easy a1111Loader` 中的 **a1111_prompt_style** 默认值为 False +- `easy XYInputs ModelMergeBlocks` 支持csv文件导入数值 + +- 替换了XY图生成时的字体文件 + +- 移除 `easy imageRemBg` +- 移除包中的介绍图和工作流文件,减少包体积 + +
+ +
+v1.0.1 + +- 新增 `easy seed` - 简易随机种 +- `easy preDetailerFix` 新增了 `optional_image` 传入图像可选,如未传默认取值为pipe里的图像 +- 新增 `easy kSamplerInpainting` 用于内补潜空间的采样器 +- 新增 `easy pipeToBasicPipe` 用于转换到Impact的某些节点上 + +- 修复 `easy comfyLoader` 报错 +- 修复所有包含输出图片尺寸的节点取值方式无法批处理的问题 +- 修复 `width` 和 `height` 无法在 `easy svdLoader` 自定义的报错问题 +- 修复所有采样器预览图片的地址链接 (解决在 MACOS 系统中图片无法在采样器中预览的问题) +- 修复 `vae_name` 在 `easy fullLoader` 和 `easy a1111Loader` 和 `easy comfyLoader` 中选择但未替换原始vae问题 +- 修复 `easy fullkSampler` 除pipe外其他输出值的报错 +- 修复 `easy hiresFix` 输入连接pipe和image、vae同时存在时报错 +- 修复 `easy fullLoader` 中 `model_override` 连接后未执行 +- 修复 因新增`easy seed` 导致action错误 +- 修复 `easy xyplot` 的字体文件路径读取错误 +- 修复 convert 到 `easy seed` 随机种无法固定的问题 +- 修复 `easy pipeIn` 值传入的报错问题 +- 修复 `easy zero123Loader` 和 `easy svdLoader` 读取模型时将模型加入到缓存中 +- 修复 `easy kSampler` `easy kSamplerTiled` `easy detailerFix` 的 `image_output` 默认值为 Preview +- `easy fullLoader` 和 `easy a1111Loader` 新增了 `a1111_prompt_style` 参数可以重现和webui生成相同的图像,当前您需要安装 [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) 才能使用此功能 +
+ +
+v1.0.0 + +- 新增`easy positive` - 简易正面提示词文本 +- 新增`easy negative` - 简易负面提示词文本 +- 新增`easy wildcards` - 支持通配符和Lora选择的提示词文本 +- 新增`easy portraitMaster` - 肖像大师v2.2 +- 新增`easy loraStack` - Lora堆 +- 新增`easy fullLoader` - 完整版的加载器 +- 新增`easy zero123Loader` - 简易zero123加载器 +- 新增`easy svdLoader` - 简易svd加载器 +- 新增`easy fullkSampler` - 完整版的采样器(无分离) +- 新增`easy hiresFix` - 支持Pipe的高清修复 +- 新增`easy predetailerFix` `easy DetailerFix` - 支持Pipe的细节修复 +- 新增`easy ultralyticsDetectorPipe` `easy samLoaderPipe` - 检测加载器(细节修复的输入项) +- 新增`easy pipein` `easy pipeout` - Pipe的输入与输出 +- 新增`easy xyPlot` - 简易的xyplot (后续会更新更多可控参数) +- 新增`easy imageRemoveBG` - 图像去除背景 +- 新增`easy imagePixelPerfect` - 图像完美像素 +- 新增`easy poseEditor` - 姿势编辑器 +- 新增UI主题(黑曜石)- 默认自动加载UI, 也可在设置中自行更替 + +- 修复 `easy globalSeed` 不生效问题 +- 修复所有的`seed_num` 因 [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) 实时更新图表导致值错乱的问题 +- 修复`easy imageSize` `easy imageSizeBySide` `easy imageSizeByLongerSide` 可作为终节点 +- 修复 `seed_num` (随机种子值) 在历史记录中读取无法一致的Bug +
+ + +
+v0.5 + +- 新增 `easy controlnetLoaderADV` 节点 +- 新增 `easy imageSizeBySide` 节点,可选输出为长边或短边 +- 新增 `easy LLLiteLoader` 节点,如果您预先安装过 kohya-ss/ControlNet-LLLite-ComfyUI 包,请将 models 里的模型文件移动至 ComfyUI\models\controlnet\ (即comfy默认的controlnet路径里,请勿修改模型的文件名,不然会读取不到)。 +- 新增 `easy imageSize` 和 `easy imageSizeByLongerSize` 输出的尺寸显示。 +- 新增 `easy showSpentTime` 节点用于展示图片推理花费时间与VAE解码花费时间。 +- `easy controlnetLoaderADV` 和 `easy controlnetLoader` 新增 `control_net` 可选传入参数 +- `easy preSampling` 和 `easy preSamplingAdvanced` 新增 `image_to_latent` 可选传入参数 +- `easy a1111Loader` 和 `easy comfyLoader` 新增 `batch_size` 传入参数 + +- 修改 `easy controlnetLoader` 到 loader 分类底下。 +
+ +## 整合参考到的相关节点包 + +声明: 非常尊重这些原作者们的付出,开源不易,我仅仅只是做了一些整合与优化。 + +| 节点名 (搜索名) | 相关的库 | 库相关的节点 | +|:-------------------------------|:----------------------------------------------------------------------------|:------------------------| +| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode | +| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode | +| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 | +| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master | +| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader | +| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) | +| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | +| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | +| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop | +| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode | +| easy if | [ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) | IfExecute | +| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply等 | +| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter | +| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser | +| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign | +| easy icLightApply | [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) | ICLightApply等 | +| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader | + +## Credits + +[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - 功能强大且模块化的Stable Diffusion GUI + +[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI管理器 + +[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - 管道节点(节点束)让用户减少了不必要的连接 + +[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - diffus3的获取与设置点让用户可以分离工作流构成 + +[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - 常规整合包1 + +[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - 常规整合包2 + +[ComfyUI-Logic](https://github.com/theUpsider/ComfyUI-Logic) - ComfyUI逻辑运算 + +[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - 让模型生成不受训练分辨率限制 + +[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - 风格迁移 + +[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - 人脸迁移 + +[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - 人脸迁移 + +[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss 小蛇🐍脚本 + +[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - 图片选择器 + +[ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) - BrushNet 内补节点 + +[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT架构相关节点(Pixart、混元DiT等) + +## ☕️ Donation + +**Comfyui-Easy-Use** 是一个 GPL 许可的开源项目。为了项目取得更好、可持续的发展,我希望能够获得更多的支持。 如果我的自定义节点为您的一天增添了价值,请考虑喝杯咖啡来进一步补充能量! 💖感谢您的支持,每一杯咖啡都是我创作的动力! + +- [BiliBili充电](https://space.bilibili.com/1840885116) +- [爱发电](https://afdian.com/a/yolain) +- [Wechat/Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03) + +感谢您的捐助,我将用这些费用来租用 GPU 或购买其他 GPT 服务,以便更好地调试和完善 ComfyUI-Easy-Use 功能 + +## 🌟Stargazers + +My gratitude extends to the generous souls who bestow a star. Your support is much appreciated! + [![Stargazers repo roster for @yolain/ComfyUI-Easy-Use](https://reporoster.com/stars/yolain/ComfyUI-Easy-Use)](https://github.com/yolain/ComfyUI-Easy-Use/stargazers) \ No newline at end of file diff --git a/README.md b/README.md index 4d8703c..2659bf4 100644 --- a/README.md +++ b/README.md @@ -1,475 +1,475 @@ -![comfyui-easy-use](https://github.com/user-attachments/assets/9b7a5e44-f5e2-4c27-aed2-d0e6b50c46bb) - -
-Video Tutorial | -Docs (Cooming Soon) | -Workflow Collection | -Donation -

- - -
- -**ComfyUI-Easy-Use** is an efficiency custom nodes integration package, which is extended on the basis of [TinyTerraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes). It has been integrated and optimized for many popular awesome custom nodes to achieve the purpose of faster and more convenient use of ComfyUI. While ensuring the degree of freedom, it restores the ultimate smooth image production experience that belongs to Stable Diffusion. - -## 👨🏻‍🎨 Introduce - -- Inspire by [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes), which greatly reduces the time cost of tossing workflows。 -- UI interface beautification, the first time you install the user, if you need to use the UI theme, please switch the theme in Settings -> Color Palette and refresh page. -- Added a node for pre-sampling parameter configuration, which can be separated from the sampling node for easier previewing -- Wildcards and lora's are supported, for Lora Block Weight usage, ensure that the custom node package has the [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) -- Multi-selectable styled cue word selector, default is Fooocus style json, custom json can be placed under styles, samples folder can be placed in the preview image (name and name consistent, image file name such as spaces need to be converted to underscores '_') -- The loader enables the A1111 prompt mode, which reproduces nearly identical images to those generated by webui, and needs to be installed [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) first. -- Noise injection into the latent space can be achieved using the `easy latentNoisy` or `easy preSamplingNoiseIn` node -- Simplified processes for SD1.x, SD2.x, SDXL, SVD, Zero123, etc. [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableDiffusion) -- Simplified Stable Cascade [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade) -- Simplified Layer Diffuse [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#LayerDiffusion),The first time you use it you may need to run `pip install -r requirements.txt` to install the required dependencies. -- Simplified InstantID [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID), You need to make sure that the custom node package has the [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) -- Extending the usability of XYplot -- Fooocus Inpaint integration -- Integration of common logical calculations, conversion of types, display of all types, etc. -- Background removal nodes for the RMBG-1.4 model supporting BriaAI, [BriaAI Guide](https://huggingface.co/briaai/RMBG-1.4) -- Forcibly cleared the memory usage of the comfy UI model are supported -- Stable Diffusion 3 multi-account API nodes are supported -- Support SD3's model -- Support Kolors‘s model -- Support Flux's model -- Support lazy if else and for loops - -## 👨🏻‍🔧 Installation -Clone the repo into the **custom_nodes** directory and install the requirements: -```shell -#1. Clone the repo -git clone https://github.com/yolain/ComfyUI-Easy-Use -#2. Install the requirements -Double-click install.bat to install the required dependencies -``` - -## 👨🏻‍🚀 Plan - -- [x] Updated new front-end code for easier maintenance - - [x] Maintain css styles using sass - - [x] Optimize existing extensions - - [x] Add new components -- [ ] Upload new workflows to [ComfyUI-Yolain-Workflows](https://github.com/yolain/ComfyUI-Yolain-Workflows) and translate readme to english version. -- [ ] Write gitbook with more detailed function introdution - -## 📜 Changelog - -**v1.2.5** - -- Support mochi -- Implement reuse of end nodes output in the loop body (e.g: previewImage and showAnything and sth.) - -**v1.2.4** - -- Added `easy imageSplitTiles` and `easy imageTilesFromBatch` -- Support `model_override`,`vae_override`,`clip_override` can be input separately to `easy fullLoader` -- Added `easy saveImageLazy` -- Added `easy loadImageForLoop` -- Added `easy isFileExist` -- Added `easy saveText` - -**v1.2.3** - -- `easy showAnything` and `easy cleanGPUUsed` added slot of output -- Added human parts segmentation to `easy humanSegmentation` - Code based on [ComfyUI_Human_Parts](https://github.com/metal3d/ComfyUI_Human_Parts) -- Using FluxGuidance when you are using a flux model and choose basicGuider and set the cfg>0 on `easy preSamplingCustom` -- Added `easy loraStackApply` and `easy controlnetStackApply` - Apply loraStack and controlnetStack - -**v1.2.2** - -- Added `easy batchAny` -- Added `easy anythingIndexSwitch` -- Added `easy forLoopStart` and `easy forLoopEnd` -- Added `easy ifElse` -- Added v2 web frond-end code -- Added `easy fluxLoader` -- Added support for `controlnetApply` Related nodes with SD3 and hunyuanDiT -- Fixed after using `easy applyFooocusInpaint`, all lora models become unusable - -**v1.2.1** - -- Added `easy ipadapterApplyFaceIDKolors` -- Added **inspyrenet** to `easy imageRemBg` -- Added `easy controlnetLoader++` -- Added **PLUS (kolors genernal)** and **FACEID PLUS KOLORS** preset to `easy ipadapterApply` and `easy ipadapterApplyADV` (Supported kolors ipadapter) -- Added `easy kolorsLoader` - Code based on [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ)'s and [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper)'s repo, thanks for their contribution. - -**v1.2.0** - -- Added `easy pulIDApply` and `easy pulIDApplyADV` -- Added `easy huanyuanDiTLoader` and `easy pixArtLoader` -- Added **easy sliderControl** - Slider control node, which can currently be used to control the parameters of ipadapterMS (double-click the slider to reset to default) -- Added **layer_weights** in `easy ipadapterApplyADV` - -**v1.1.9** - -- Added **gitsScheduler** -- Added `easy imageBatchToImageList` and `easy imageListToImageBatch` -- Recursive subcategories nested for models -- Support for Stable Diffusion 3 model -- Added `easy applyInpaint` - All inpainting mode in this node - -**v1.1.8** - -- Added `easy controlnetStack` -- Added `easy applyBrushNet` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json) -- Added `easy applyPowerPaint` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json) - -**v1.1.7** - -- Added `easy prompt` - Subject and light presets, maybe adjusted later -- Added `easy icLightApply` - Light and shadow migration, Code based on [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) -- Added `easy imageSplitGrid` -- `easy kSamplerInpainting` added options such as different diffusion and brushnet in **additional** widget -- Support for brushnet model loading - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) -- Added `easy applyFooocusInpaint` - Replace FooocusInpaintLoader -- Removed `easy fooocusInpaintLoader` - -**v1.1.6** - -- Added **alignYourSteps** to **schedulder** widget in all `easy preSampling` and `easy fullkSampler` -- Added **Preview&Choose** to **image_output** widget in `easy kSampler` & `easy fullkSampler` -- Added `easy styleAlignedBatchAlign` - Credit of [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy) -- Added `easy ckptNames` -- Added `easy controlnetNames` -- Added `easy imagesSplitimage` - Batch images split into single images -- Added `easy imageCount` - Get Image Count -- Added `easy textSwitch` - Text Switch - -**v1.1.5** - -- Rewrite `easy cleanGPUUsed` - the memory usage of the comfyUI can to be cleared -- Added `easy humanSegmentation` - Human Part Segmentation -- Added `easy imageColorMatch` -- Added `easy ipadapterApplyRegional` -- Added `easy ipadapterApplyFromParams` -- Added `easy imageInterrogator` - Image To Prompt -- Added `easy stableDiffusion3API` - Easy Stable Diffusion 3 Multiple accounts API Node - -**v1.1.4** - -- Added `easy preSamplingCustom` - Custom-PreSampling, can be supported cosXL-edit -- Added `easy ipadapterStyleComposition` -- Added the right-click menu to view checkpoints and lora information in all Loaders -- Fixed `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` compatible with ComfyUI Revision>=2098 [0542088e] or later - - -**v1.1.3** - -- `easy ipadapterApply` Added **COMPOSITION** preset -- Supported [ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) when load ResAdapter lora -- Added `easy promptLine` -- Added `easy promptReplace` -- Added `easy promptConcat` -- `easy wildcards` Added **multiline_mode** - -
-v1.1.2 - -- Optimized some of the recommended nodes for slots related to EasyUse -- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews -- Added `easy sv3dLoader` -- Added `easy dynamiCrafterLoader` -- Added `easy ipadapterApply` -- Added `easy ipadapterApplyADV` -- Added `easy ipadapterApplyEncoder` -- Added `easy ipadapterApplyEmbeds` -- Added `easy preMaskDetailerFix` -- Fixed `easy stylesSelector` is change the prompt when not select the style -- Fixed `easy pipeEdit` error when add lora to prompt -- Fixed layerDiffuse xyplot bug -- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning' -
- -
-v1.1.1 - -- The issue that the seed is 0 when a node with a seed control is added and **control before generate** is fixed for the first time run queue prompt. -- `easy preSamplingAdvanced` Added **return_with_leftover_noise** -- Fixed `easy stylesSelector` error when choose the custom file -- `easy preSamplingLayerDiffusion` Added optional input parameter for mask -- Renamed all nodes widget name named seed_num to seed -- Remove forced **control_before_generate** settings。 If you want to use control_before_generate, change widget_value_control_mode to before in system settings -- Added `easy imageRemBg` - The default is BriaAI's RMBG-1.4 model, which removes the background effect more and faster -
- -
-v1.1.0 - -- Added `easy imageSplitList` - to split every N images -- Added `easy preSamplingDiffusionADDTL` - It can modify foreground、background or blended additional prompt -- Added `easy preSamplingNoiseIn` It can replace the `easy latentNoisy` node that needs to be fronted to achieve better noise injection -- `easy pipeEdit` Added conditioning splicing mode selection, you can choose to replace, concat, combine, average, and set timestep range -- Added `easy pipeEdit` - nodes that can edit pipes (including re-enterable prompts) -- Added `easy preSamplingLayerDiffusion` and `easy kSamplerLayerDiffusion` -- Added a convenient menu to right-click on nodes such as Loader, Presampler, Sampler, Controlnet, etc. to quickly replace nodes of the same type -- Added `easy instantIDApplyADV` can link positive and negative -- Fixed layerDiffusion error when batch size greater than 1 -- Fixed `easy wildcards` When LoRa is not filled in completely, LoRa is not automatically retrieved, resulting in failure to load LoRa -- Fixed the issue that 'BREAK' non-initiation when didn't use a1111 prompt style -- Fixed `easy instantIDApply` mask not input right -
- -
-v1.0.9 - -- Fixed the error when ComfyUI-Impack-Pack and ComfyUI_InstantID were not installed -- Fixed `easy pipeIn` -- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID) -- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions -- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts -
- -
-v1.0.8 - -- `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models) -- `easy styleSelector` The search box is modified to be case-insensitive -- `easy fullLoader` **positive**、**negative**、**latent** added to the output items -- Fixed the issue that 'easy preSampling' and other similar node, latent could not be generated based on the batch index after passing in -- Fixed `easy svdLoader` error when the positive or negative is empty -- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision) -- Fixed `easy detailerFix` generation error when batch size is greater than 1 -- Optimize the code, reduce a lot of redundant code and improve the running speed -
- -
-v1.0.7 - -- Added `easy cascadeLoader` - stable cascade Loader -- Added `easy preSamplingCascade` - stable cascade preSampling Settings -- Added `easy fullCascadeKSampler` - stable cascade stage-c ksampler full -- Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple -- -- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#image-to-image) -
- -
-v1.0.6 - -- Added `easy XYInputs: Checkpoint` -- Added `easy XYInputs: Lora` -- `easy seed` can manually switch the random seed when increasing the fixed seed value -- Fixed `easy fullLoader` and all loaders to automatically adjust the node size when switching LoRa -- Removed the original ttn image saving logic and adapted to the default image saving format extension of ComfyUI -
- -
-v1.0.5 - -- Added `easy isSDXL` -- Added prompt word control on `easy svdLoader`, which can be used with open_clip model -- Added **populated_text** on `easy wildcards`, wildcard populated text can be output -
- -
-v1.0.4 - -- `easy showAnything` added support for converting other types (e.g., tensor conditions, images, etc.) -- Added `easy showLoaderSettingsNames` can display the model and VAE name in the output loader assembly -- Added `easy promptList` -- Added `easy fooocusInpaintLoader` (only the process of SDXLModel is supported) -- Added **Logic** nodes -- Added `easy imageSave` - Image saving node with date conversion and aspect and height formatting -- Added `easy joinImageBatch` -- `easy kSamplerInpainting` Added the **patch** input value to be used with the FooocusInpaintLoader node - -- Fixed xyplot error when with Pillow>9.5 -- Fixed `easy wildcards` An error is reported when running with the PS extension -- Fixed `easy XYInputs: ControlNet` Error -- Fixed `easy loraStack` error when **toggle** is disabled - - -- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page -- `easy imageSave` added **only_preivew** -- Adjust the `easy latentCompositeMaskedWithCond` node -
- -
-v1.0.3 - -- Added `easy stylesSelector` -- Added **scale_soft_weights** in `easy controlnetLoader` and `easy controlnetLoaderADV` -- Added the queue progress bar setting item, which is not enabled by default - - -- Fixed `easy XYInputs: Sampler/Scheduler` Error -- Fixed the right menu has a problem when clicking the button -- Fixed `easy comfyLoader` error -- Fixed xyPlot error when connecting to zero123 -- Fixed the error message in the loader when the prompt word was component -- Fixed `easy getNode` and `easy setNode` the title does not change when loading -- Fixed all samplers using subdirectories to store images - - -- Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings -- Modify the styles path to be compatible with other environments -
- -
-v1.0.2 - -- Added `easy XYPlotAdvanced` and some nodes about `easy XYInputs` -- Added **Alt+1-Alt+9** Shortcut keys to quickly paste node presets for Node templates (corresponding to 1~9 sequences) -- Added a `📜Groups Map(EasyUse)` to the context menu. -- An `autocomplete` folder has been added, If you have [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) installed, the txt files in that folder will be merged and overwritten to the autocomplete .txt file of the pyssss package at startup. - - -- Fixed XYPlot is not working when `a1111_prompt_style` is True -- Fixed UI loading failure in the new version of ComfyUI -- `easy XYInputs ModelMergeBlocks` Values can be imported from CSV files -- Fixed `easy pipeToBasicPipe` Bug - - -- Removed `easy imageRemBg` -- Remove the introductory diagram and workflow files from the package to reduce the package size -- Replaced the font file used in the generation of XY diagrams -
- -
-v1.0.1 - -- Fixed `easy comfyLoader` error -- Fixed All nodes that contain the value of the image size -- Added `easy kSamplerInpainting` -- Added `easy pipeToBasicPipe` -- Fixed `width` and `height` can not customize in `easy svdLoader` -- Fixed all preview image path (Previously, it was not possible to preview the image on the Mac system) -- Fixed `vae_name` is not working in `easy fullLoader` and `easy a1111Loader` and `easy comfyLoader` -- Fixed `easy fullkSampler` outputs error -- Fixed `model_override` is not working in `easy fullLoader` -- Fixed `easy hiresFix` error -- Fixed `easy xyplot` font file path error -- Fixed seed that cannot be fixed when you convert `seed_num` to `easy seed` -- Fixed `easy pipeIn` inputs bug -- `easy preDetailerFix` have added a new parameter `optional_image` -- Fixed `easy zero123Loader` and `easy svdLoader` model into cache. -- Added `easy seed` -- Fixed `image_output` default value is "Preview" -- `easy fullLoader` and `easy a1111Loader` have added a new parameter `a1111_prompt_style`,that can reproduce the same image generated from stable-diffusion-webui on comfyui, but you need to install [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) to use this feature in the current version -
- -
-v1.0.0 - -- Added `easy positive` - simple positive prompt text -- Added `easy negative` - simple negative prompt text -- Added `easy wildcards` - support for wildcards and hint text selected by Lora -- Added `easy portraitMaster` - PortraitMaster v2.2 -- Added `easy loraStack` - Lora stack -- Added `easy fullLoader` - full version of the loader -- Added `easy zero123Loader` - simple zero123 loader -- Added `easy svdLoader` - easy svd loader -- Added `easy fullkSampler` - full version of the sampler (no separation) -- Added `easy hiresFix` - support for HD repair of Pipe -- Added `easy predetailerFix` and `easy DetailerFix` - support for Pipe detail fixing -- Added `easy ultralyticsDetectorPipe` and `easy samLoaderPipe` - Detect loader (detail fixed input) -- Added `easy pipein` `easy pipeout` - Pipe input and output -- Added `easy xyPlot` - simple xyplot (more controllable parameters will be updated in the future) -- Added `easy imageRemoveBG` - image to remove background -- Added `easy imagePixelPerfect` - image pixel perfect -- Added `easy poseEditor` - Pose editor -- New UI Theme (Obsidian) - Auto-load UI by default, which can also be changed in the settings - -- Fixed `easy globalSeed` is not working -- Fixed an issue where all `seed_num` values were out of order due to [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) updating the chart in real time -- Fixed `easy imageSize`, `easy imageSizeBySide`, `easy imageSizeByLongerSide` as end nodes -- Fixed the bug that `seed_num` (random seed value) could not be read consistently in history -
- -
-Updated at 12/14/2023 - -- `easy a1111Loader` and `easy comfyLoader` added `batch_size` of required input parameters -- Added the `easy controlnetLoaderADV` node -- `easy controlnetLoaderADV` and `easy controlnetLoader` added `control_net ` of optional input parameters -- `easy preSampling` and `easy preSamplingAdvanced` added `image_to_latent` optional input parameters -- Added the `easy imageSizeBySide` node, which can be output as a long side or a short side -
- -
-Updated at 12/13/2023 - -- Added the `easy LLLiteLoader` node, if you have pre-installed the kohya-ss/ControlNet-LLLite-ComfyUI package, please move the model files in the models to `ComfyUI\models\controlnet\` (i.e. in the default controlnet path of comfy, please do not change the file name of the model, otherwise it will not be read). -- Modify `easy controlnetLoader` to the bottom of the loader category. -- Added size display for `easy imageSize` and `easy imageSizeByLongerSize` outputs. -
- -
-Updated at 12/11/2023 -- Added the `showSpentTime` node to display the time spent on image diffusion and the time spent on VAE decoding images -
- -## The relevant node package involved - -Disclaimer: Opened source was not easy. I have a lot of respect for the contributions of these original authors. I just did some integration and optimization. - -| Nodes Name(Search Name) | Related libraries | Library-related node | -|:-------------------------------|:----------------------------------------------------------------------------|:-------------------------| -| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode | -| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode | -| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 | -| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master | -| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader | -| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) | -| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | -| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | -| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop | -| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode | -| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply... | -| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter | -| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser | -| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign | -| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader | - - -## Credits - -[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - Powerful and modular Stable Diffusion GUI - -[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI Manager - -[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - Pipe nodes (node bundles) allow users to reduce unnecessary connections - -[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - Diffus3 gets and sets points that allow the user to detach the composition of the workflow - -[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - General modpack 1 - -[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - General Modpack 2 - -[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - Make model generation independent of training resolution - -[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - Style migration - -[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - Face migration - -[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - Face migration - -[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍 - -[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - Image Preview Chooser - -[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT custom nodes - -## ☕️ Donation - -**Comfyui-Easy-Use** is an GPL-licensed open source project. In order to achieve better and sustainable development of the project, i expect to gain more backers.
-If my custom nodes has added value to your day, consider indulging in a coffee to fuel it further!
-💖You can support me in any of the following ways: - -- [BiliBili](https://space.bilibili.com/1840885116) -- [Afdian](https://afdian.com/a/yolain) -- [Wechat / Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03) -- 🪙 Wallet Address: - - ETH: 0x01f7CEd3245CaB3891A0ec8f528178db352EaC74 - - USDT(tron): TP3AnJXkAzfebL2GKmFAvQvXgsxzivweV6 - -(This is a newly created wallet, and if it receives sponsorship, I'll use it to rent GPUs or other GPT services for better debugging and refinement of ComfyUI-Easy-Use features.) - -## 🌟Stargazers - -My gratitude extends to the generous souls who bestow a star. Your support is much appreciated! - +![comfyui-easy-use](https://github.com/user-attachments/assets/9b7a5e44-f5e2-4c27-aed2-d0e6b50c46bb) + +
+Video Tutorial | +Docs (Cooming Soon) | +Workflow Collection | +Donation +

+ + +
+ +**ComfyUI-Easy-Use** is an efficiency custom nodes integration package, which is extended on the basis of [TinyTerraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes). It has been integrated and optimized for many popular awesome custom nodes to achieve the purpose of faster and more convenient use of ComfyUI. While ensuring the degree of freedom, it restores the ultimate smooth image production experience that belongs to Stable Diffusion. + +## 👨🏻‍🎨 Introduce + +- Inspire by [tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes), which greatly reduces the time cost of tossing workflows。 +- UI interface beautification, the first time you install the user, if you need to use the UI theme, please switch the theme in Settings -> Color Palette and refresh page. +- Added a node for pre-sampling parameter configuration, which can be separated from the sampling node for easier previewing +- Wildcards and lora's are supported, for Lora Block Weight usage, ensure that the custom node package has the [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) +- Multi-selectable styled cue word selector, default is Fooocus style json, custom json can be placed under styles, samples folder can be placed in the preview image (name and name consistent, image file name such as spaces need to be converted to underscores '_') +- The loader enables the A1111 prompt mode, which reproduces nearly identical images to those generated by webui, and needs to be installed [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) first. +- Noise injection into the latent space can be achieved using the `easy latentNoisy` or `easy preSamplingNoiseIn` node +- Simplified processes for SD1.x, SD2.x, SDXL, SVD, Zero123, etc. [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableDiffusion) +- Simplified Stable Cascade [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#StableCascade) +- Simplified Layer Diffuse [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#LayerDiffusion),The first time you use it you may need to run `pip install -r requirements.txt` to install the required dependencies. +- Simplified InstantID [Example](https://github.com/yolain/ComfyUI-Easy-Use?tab=readme-ov-file#InstantID), You need to make sure that the custom node package has the [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) +- Extending the usability of XYplot +- Fooocus Inpaint integration +- Integration of common logical calculations, conversion of types, display of all types, etc. +- Background removal nodes for the RMBG-1.4 model supporting BriaAI, [BriaAI Guide](https://huggingface.co/briaai/RMBG-1.4) +- Forcibly cleared the memory usage of the comfy UI model are supported +- Stable Diffusion 3 multi-account API nodes are supported +- Support SD3's model +- Support Kolors‘s model +- Support Flux's model +- Support lazy if else and for loops + +## 👨🏻‍🔧 Installation +Clone the repo into the **custom_nodes** directory and install the requirements: +```shell +#1. Clone the repo +git clone https://github.com/yolain/ComfyUI-Easy-Use +#2. Install the requirements +Double-click install.bat to install the required dependencies +``` + +## 📜 Changelog + +**v1.2.6** + +- Fix the issue due to set nodes missing custom nodes which their connected, causing canvas to be messed up. + +**v1.2.5** + +- Added `enable (GPU=A1111)` noise mode on `easy preSamplingCustom` and `easy preSamplingAdvanced` +- Added `easy makeImageForICLora` +- Added `REGULAR - FLUX and SD3.5 only (high strength)` preset for InstantX Flux ipadapter on `easy ipadapterApply` +- Fix brushnet can not be used with startup arg `--fast` mode +- Support briaai RMBG-2.0 +- Support mochi +- Implement reuse of end nodes output in the loop body (e.g: previewImage and showAnything and sth.) + +**v1.2.4** + +- Added `easy imageSplitTiles` and `easy imageTilesFromBatch` +- Support `model_override`,`vae_override`,`clip_override` can be input separately to `easy fullLoader` +- Added `easy saveImageLazy` +- Added `easy loadImageForLoop` +- Added `easy isFileExist` +- Added `easy saveText` + +**v1.2.3** + +- `easy showAnything` and `easy cleanGPUUsed` added slot of output +- Added human parts segmentation to `easy humanSegmentation` - Code based on [ComfyUI_Human_Parts](https://github.com/metal3d/ComfyUI_Human_Parts) +- Using FluxGuidance when you are using a flux model and choose basicGuider and set the cfg>0 on `easy preSamplingCustom` +- Added `easy loraStackApply` and `easy controlnetStackApply` - Apply loraStack and controlnetStack + +**v1.2.2** + +- Added `easy batchAny` +- Added `easy anythingIndexSwitch` +- Added `easy forLoopStart` and `easy forLoopEnd` +- Added `easy ifElse` +- Added v2 web frond-end code +- Added `easy fluxLoader` +- Added support for `controlnetApply` Related nodes with SD3 and hunyuanDiT +- Fixed after using `easy applyFooocusInpaint`, all lora models become unusable + +**v1.2.1** + +- Added `easy ipadapterApplyFaceIDKolors` +- Added **inspyrenet** to `easy imageRemBg` +- Added `easy controlnetLoader++` +- Added **PLUS (kolors genernal)** and **FACEID PLUS KOLORS** preset to `easy ipadapterApply` and `easy ipadapterApplyADV` (Supported kolors ipadapter) +- Added `easy kolorsLoader` - Code based on [MinusZoneAI](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ)'s and [kijai](https://github.com/kijai/ComfyUI-KwaiKolorsWrapper)'s repo, thanks for their contribution. + +**v1.2.0** + +- Added `easy pulIDApply` and `easy pulIDApplyADV` +- Added `easy huanyuanDiTLoader` and `easy pixArtLoader` +- Added **easy sliderControl** - Slider control node, which can currently be used to control the parameters of ipadapterMS (double-click the slider to reset to default) +- Added **layer_weights** in `easy ipadapterApplyADV` + +**v1.1.9** + +- Added **gitsScheduler** +- Added `easy imageBatchToImageList` and `easy imageListToImageBatch` +- Recursive subcategories nested for models +- Support for Stable Diffusion 3 model +- Added `easy applyInpaint` - All inpainting mode in this node + +**v1.1.8** + +- Added `easy controlnetStack` +- Added `easy applyBrushNet` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4brushnet_1.1.8.json) +- Added `easy applyPowerPaint` - [Workflow Example](https://github.com/yolain/ComfyUI-Yolain-Workflows/blob/main/workflows/2_advanced/2-4inpainting/2-4powerpaint_outpaint_1.1.8.json) + +**v1.1.7** + +- Added `easy prompt` - Subject and light presets, maybe adjusted later +- Added `easy icLightApply` - Light and shadow migration, Code based on [ComfyUI-IC-Light](https://github.com/huchenlei/ComfyUI-IC-Light) +- Added `easy imageSplitGrid` +- `easy kSamplerInpainting` added options such as different diffusion and brushnet in **additional** widget +- Support for brushnet model loading - [ComfyUI-BrushNet](https://github.com/nullquant/ComfyUI-BrushNet) +- Added `easy applyFooocusInpaint` - Replace FooocusInpaintLoader +- Removed `easy fooocusInpaintLoader` + +**v1.1.6** + +- Added **alignYourSteps** to **schedulder** widget in all `easy preSampling` and `easy fullkSampler` +- Added **Preview&Choose** to **image_output** widget in `easy kSampler` & `easy fullkSampler` +- Added `easy styleAlignedBatchAlign` - Credit of [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy) +- Added `easy ckptNames` +- Added `easy controlnetNames` +- Added `easy imagesSplitimage` - Batch images split into single images +- Added `easy imageCount` - Get Image Count +- Added `easy textSwitch` - Text Switch + +**v1.1.5** + +- Rewrite `easy cleanGPUUsed` - the memory usage of the comfyUI can to be cleared +- Added `easy humanSegmentation` - Human Part Segmentation +- Added `easy imageColorMatch` +- Added `easy ipadapterApplyRegional` +- Added `easy ipadapterApplyFromParams` +- Added `easy imageInterrogator` - Image To Prompt +- Added `easy stableDiffusion3API` - Easy Stable Diffusion 3 Multiple accounts API Node + +**v1.1.4** + +- Added `easy preSamplingCustom` - Custom-PreSampling, can be supported cosXL-edit +- Added `easy ipadapterStyleComposition` +- Added the right-click menu to view checkpoints and lora information in all Loaders +- Fixed `easy preSamplingNoiseIn`、`easy latentNoisy`、`east Unsampler` compatible with ComfyUI Revision>=2098 [0542088e] or later + + +**v1.1.3** + +- `easy ipadapterApply` Added **COMPOSITION** preset +- Supported [ResAdapter](https://huggingface.co/jiaxiangc/res-adapter) when load ResAdapter lora +- Added `easy promptLine` +- Added `easy promptReplace` +- Added `easy promptConcat` +- `easy wildcards` Added **multiline_mode** + +
+v1.1.2 + +- Optimized some of the recommended nodes for slots related to EasyUse +- Added **Enable ContextMenu Auto Nest Subdirectories** The setting item is enabled by default, and it can be classified into subdirectories, checkpoints and loras previews +- Added `easy sv3dLoader` +- Added `easy dynamiCrafterLoader` +- Added `easy ipadapterApply` +- Added `easy ipadapterApplyADV` +- Added `easy ipadapterApplyEncoder` +- Added `easy ipadapterApplyEmbeds` +- Added `easy preMaskDetailerFix` +- Fixed `easy stylesSelector` is change the prompt when not select the style +- Fixed `easy pipeEdit` error when add lora to prompt +- Fixed layerDiffuse xyplot bug +- `easy kSamplerInpainting` add *additional* widget,you can choose 'Differential Diffusion' or 'Only InpaintModelConditioning' +
+ +
+v1.1.1 + +- The issue that the seed is 0 when a node with a seed control is added and **control before generate** is fixed for the first time run queue prompt. +- `easy preSamplingAdvanced` Added **return_with_leftover_noise** +- Fixed `easy stylesSelector` error when choose the custom file +- `easy preSamplingLayerDiffusion` Added optional input parameter for mask +- Renamed all nodes widget name named seed_num to seed +- Remove forced **control_before_generate** settings。 If you want to use control_before_generate, change widget_value_control_mode to before in system settings +- Added `easy imageRemBg` - The default is BriaAI's RMBG-1.4 model, which removes the background effect more and faster +
+ +
+v1.1.0 + +- Added `easy imageSplitList` - to split every N images +- Added `easy preSamplingDiffusionADDTL` - It can modify foreground、background or blended additional prompt +- Added `easy preSamplingNoiseIn` It can replace the `easy latentNoisy` node that needs to be fronted to achieve better noise injection +- `easy pipeEdit` Added conditioning splicing mode selection, you can choose to replace, concat, combine, average, and set timestep range +- Added `easy pipeEdit` - nodes that can edit pipes (including re-enterable prompts) +- Added `easy preSamplingLayerDiffusion` and `easy kSamplerLayerDiffusion` +- Added a convenient menu to right-click on nodes such as Loader, Presampler, Sampler, Controlnet, etc. to quickly replace nodes of the same type +- Added `easy instantIDApplyADV` can link positive and negative +- Fixed layerDiffusion error when batch size greater than 1 +- Fixed `easy wildcards` When LoRa is not filled in completely, LoRa is not automatically retrieved, resulting in failure to load LoRa +- Fixed the issue that 'BREAK' non-initiation when didn't use a1111 prompt style +- Fixed `easy instantIDApply` mask not input right +
+ +
+v1.0.9 + +- Fixed the error when ComfyUI-Impack-Pack and ComfyUI_InstantID were not installed +- Fixed `easy pipeIn` +- Added `easy instantIDApply` - you need installed [ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) fisrt, Workflow[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#InstantID) +- Fixed `easy detailerFix` not added to the list of nodes available for saving images formatting extensions +- Fixed `easy XYInputs: PromptSR` errors are reported when replacing negative prompts +
+ +
+v1.0.8 + +- `easy cascadeLoader` stage_c and stage_b support the checkpoint model (Download [checkpoints](https://huggingface.co/stabilityai/stable-cascade/tree/main/comfyui_checkpoints) models) +- `easy styleSelector` The search box is modified to be case-insensitive +- `easy fullLoader` **positive**、**negative**、**latent** added to the output items +- Fixed the issue that 'easy preSampling' and other similar node, latent could not be generated based on the batch index after passing in +- Fixed `easy svdLoader` error when the positive or negative is empty +- Fixed the error of SDXLClipModel in ComfyUI revision 2016[c2cb8e88] and above (the revision number was judged to be compatible with the old revision) +- Fixed `easy detailerFix` generation error when batch size is greater than 1 +- Optimize the code, reduce a lot of redundant code and improve the running speed +
+ +
+v1.0.7 + +- Added `easy cascadeLoader` - stable cascade Loader +- Added `easy preSamplingCascade` - stable cascade preSampling Settings +- Added `easy fullCascadeKSampler` - stable cascade stage-c ksampler full +- Added `easy cascadeKSampler` - stable cascade stage-c ksampler simple +- +- Optimize the image to image[Example](https://github.com/yolain/ComfyUI-Easy-Use/blob/main/README.en.md#image-to-image) +
+ +
+v1.0.6 + +- Added `easy XYInputs: Checkpoint` +- Added `easy XYInputs: Lora` +- `easy seed` can manually switch the random seed when increasing the fixed seed value +- Fixed `easy fullLoader` and all loaders to automatically adjust the node size when switching LoRa +- Removed the original ttn image saving logic and adapted to the default image saving format extension of ComfyUI +
+ +
+v1.0.5 + +- Added `easy isSDXL` +- Added prompt word control on `easy svdLoader`, which can be used with open_clip model +- Added **populated_text** on `easy wildcards`, wildcard populated text can be output +
+ +
+v1.0.4 + +- `easy showAnything` added support for converting other types (e.g., tensor conditions, images, etc.) +- Added `easy showLoaderSettingsNames` can display the model and VAE name in the output loader assembly +- Added `easy promptList` +- Added `easy fooocusInpaintLoader` (only the process of SDXLModel is supported) +- Added **Logic** nodes +- Added `easy imageSave` - Image saving node with date conversion and aspect and height formatting +- Added `easy joinImageBatch` +- `easy kSamplerInpainting` Added the **patch** input value to be used with the FooocusInpaintLoader node + +- Fixed xyplot error when with Pillow>9.5 +- Fixed `easy wildcards` An error is reported when running with the PS extension +- Fixed `easy XYInputs: ControlNet` Error +- Fixed `easy loraStack` error when **toggle** is disabled + + +- Changing the first-time install node package no longer automatically replaces the theme, you need to manually adjust and refresh the page +- `easy imageSave` added **only_preivew** +- Adjust the `easy latentCompositeMaskedWithCond` node +
+ +
+v1.0.3 + +- Added `easy stylesSelector` +- Added **scale_soft_weights** in `easy controlnetLoader` and `easy controlnetLoaderADV` +- Added the queue progress bar setting item, which is not enabled by default + + +- Fixed `easy XYInputs: Sampler/Scheduler` Error +- Fixed the right menu has a problem when clicking the button +- Fixed `easy comfyLoader` error +- Fixed xyPlot error when connecting to zero123 +- Fixed the error message in the loader when the prompt word was component +- Fixed `easy getNode` and `easy setNode` the title does not change when loading +- Fixed all samplers using subdirectories to store images + + +- Adjust the UI theme, divided into two sets of styles: the official default background and the dark black background, which can be switched in the color palette in the settings +- Modify the styles path to be compatible with other environments +
+ +
+v1.0.2 + +- Added `easy XYPlotAdvanced` and some nodes about `easy XYInputs` +- Added **Alt+1-Alt+9** Shortcut keys to quickly paste node presets for Node templates (corresponding to 1~9 sequences) +- Added a `📜Groups Map(EasyUse)` to the context menu. +- An `autocomplete` folder has been added, If you have [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) installed, the txt files in that folder will be merged and overwritten to the autocomplete .txt file of the pyssss package at startup. + + +- Fixed XYPlot is not working when `a1111_prompt_style` is True +- Fixed UI loading failure in the new version of ComfyUI +- `easy XYInputs ModelMergeBlocks` Values can be imported from CSV files +- Fixed `easy pipeToBasicPipe` Bug + + +- Removed `easy imageRemBg` +- Remove the introductory diagram and workflow files from the package to reduce the package size +- Replaced the font file used in the generation of XY diagrams +
+ +
+v1.0.1 + +- Fixed `easy comfyLoader` error +- Fixed All nodes that contain the value of the image size +- Added `easy kSamplerInpainting` +- Added `easy pipeToBasicPipe` +- Fixed `width` and `height` can not customize in `easy svdLoader` +- Fixed all preview image path (Previously, it was not possible to preview the image on the Mac system) +- Fixed `vae_name` is not working in `easy fullLoader` and `easy a1111Loader` and `easy comfyLoader` +- Fixed `easy fullkSampler` outputs error +- Fixed `model_override` is not working in `easy fullLoader` +- Fixed `easy hiresFix` error +- Fixed `easy xyplot` font file path error +- Fixed seed that cannot be fixed when you convert `seed_num` to `easy seed` +- Fixed `easy pipeIn` inputs bug +- `easy preDetailerFix` have added a new parameter `optional_image` +- Fixed `easy zero123Loader` and `easy svdLoader` model into cache. +- Added `easy seed` +- Fixed `image_output` default value is "Preview" +- `easy fullLoader` and `easy a1111Loader` have added a new parameter `a1111_prompt_style`,that can reproduce the same image generated from stable-diffusion-webui on comfyui, but you need to install [ComfyUI_smZNodes](https://github.com/shiimizu/ComfyUI_smZNodes) to use this feature in the current version +
+ +
+v1.0.0 + +- Added `easy positive` - simple positive prompt text +- Added `easy negative` - simple negative prompt text +- Added `easy wildcards` - support for wildcards and hint text selected by Lora +- Added `easy portraitMaster` - PortraitMaster v2.2 +- Added `easy loraStack` - Lora stack +- Added `easy fullLoader` - full version of the loader +- Added `easy zero123Loader` - simple zero123 loader +- Added `easy svdLoader` - easy svd loader +- Added `easy fullkSampler` - full version of the sampler (no separation) +- Added `easy hiresFix` - support for HD repair of Pipe +- Added `easy predetailerFix` and `easy DetailerFix` - support for Pipe detail fixing +- Added `easy ultralyticsDetectorPipe` and `easy samLoaderPipe` - Detect loader (detail fixed input) +- Added `easy pipein` `easy pipeout` - Pipe input and output +- Added `easy xyPlot` - simple xyplot (more controllable parameters will be updated in the future) +- Added `easy imageRemoveBG` - image to remove background +- Added `easy imagePixelPerfect` - image pixel perfect +- Added `easy poseEditor` - Pose editor +- New UI Theme (Obsidian) - Auto-load UI by default, which can also be changed in the settings + +- Fixed `easy globalSeed` is not working +- Fixed an issue where all `seed_num` values were out of order due to [cg-use-everywhere](https://github.com/chrisgoringe/cg-use-everywhere) updating the chart in real time +- Fixed `easy imageSize`, `easy imageSizeBySide`, `easy imageSizeByLongerSide` as end nodes +- Fixed the bug that `seed_num` (random seed value) could not be read consistently in history +
+ +
+Updated at 12/14/2023 + +- `easy a1111Loader` and `easy comfyLoader` added `batch_size` of required input parameters +- Added the `easy controlnetLoaderADV` node +- `easy controlnetLoaderADV` and `easy controlnetLoader` added `control_net ` of optional input parameters +- `easy preSampling` and `easy preSamplingAdvanced` added `image_to_latent` optional input parameters +- Added the `easy imageSizeBySide` node, which can be output as a long side or a short side +
+ +
+Updated at 12/13/2023 + +- Added the `easy LLLiteLoader` node, if you have pre-installed the kohya-ss/ControlNet-LLLite-ComfyUI package, please move the model files in the models to `ComfyUI\models\controlnet\` (i.e. in the default controlnet path of comfy, please do not change the file name of the model, otherwise it will not be read). +- Modify `easy controlnetLoader` to the bottom of the loader category. +- Added size display for `easy imageSize` and `easy imageSizeByLongerSize` outputs. +
+ +
+Updated at 12/11/2023 +- Added the `showSpentTime` node to display the time spent on image diffusion and the time spent on VAE decoding images +
+ +## The relevant node package involved + +Disclaimer: Opened source was not easy. I have a lot of respect for the contributions of these original authors. I just did some integration and optimization. + +| Nodes Name(Search Name) | Related libraries | Library-related node | +|:-------------------------------|:----------------------------------------------------------------------------|:-------------------------| +| easy setNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.SetNode | +| easy getNode | [ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) | diffus3.GetNode | +| easy bookmark | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | Bookmark 🔖 | +| easy portraitMarker | [comfyui-portrait-master](https://github.com/florestefano1975/comfyui-portrait-master) | Portrait Master | +| easy LLLiteLoader | [ControlNet-LLLite-ComfyUI](https://github.com/kohya-ss/ControlNet-LLLite-ComfyUI) | LLLiteLoader | +| easy globalSeed | [ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) | Global Seed (Inspire) | +| easy preSamplingDynamicCFG | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | +| dynamicThresholdingFull | [sd-dynamic-thresholding](https://github.com/mcmonkeyprojects/sd-dynamic-thresholding) | DynamicThresholdingFull | +| easy imageInsetCrop | [rgthree-comfy](https://github.com/rgthree/rgthree-comfy) | ImageInsetCrop | +| easy poseEditor | [ComfyUI_Custom_Nodes_AlekPet](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet) | poseNode | +| easy preSamplingLayerDiffusion | [ComfyUI-layerdiffusion](https://github.com/huchenlei/ComfyUI-layerdiffusion) | LayeredDiffusionApply... | +| easy dynamiCrafterLoader | [ComfyUI-layerdiffusion](https://github.com/ExponentialML/ComfyUI_Native_DynamiCrafter) | Apply Dynamicrafter | +| easy imageChooser | [cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) | Preview Chooser | +| easy styleAlignedBatchAlign | [style_aligned_comfy](https://github.com/chrisgoringe/cg-image-picker) | styleAlignedBatchAlign | +| easy kolorsLoader | [ComfyUI-Kolors-MZ](https://github.com/MinusZoneAI/ComfyUI-Kolors-MZ) | kolorsLoader | + + +## Credits + +[ComfyUI](https://github.com/comfyanonymous/ComfyUI) - Powerful and modular Stable Diffusion GUI + +[ComfyUI-ComfyUI-Manager](https://github.com/ltdrdata/ComfyUI-Manager) - ComfyUI Manager + +[tinyterraNodes](https://github.com/TinyTerra/ComfyUI_tinyterraNodes) - Pipe nodes (node bundles) allow users to reduce unnecessary connections + +[ComfyUI-extensions](https://github.com/diffus3/ComfyUI-extensions) - Diffus3 gets and sets points that allow the user to detach the composition of the workflow + +[ComfyUI-Impact-Pack](https://github.com/ltdrdata/ComfyUI-Impact-Pack) - General modpack 1 + +[ComfyUI-Inspire-Pack](https://github.com/ltdrdata/ComfyUI-Inspire-Pack) - General Modpack 2 + +[ComfyUI-ResAdapter](https://github.com/jiaxiangc/ComfyUI-ResAdapter) - Make model generation independent of training resolution + +[ComfyUI_IPAdapter_plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) - Style migration + +[ComfyUI_InstantID](https://github.com/cubiq/ComfyUI_InstantID) - Face migration + +[ComfyUI_PuLID](https://github.com/cubiq/PuLID_ComfyUI) - Face migration + +[ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) - pyssss🐍 + +[cg-image-picker](https://github.com/chrisgoringe/cg-image-picker) - Image Preview Chooser + +[ComfyUI_ExtraModels](https://github.com/city96/ComfyUI_ExtraModels) - DiT custom nodes + +## ☕️ Donation + +**Comfyui-Easy-Use** is an GPL-licensed open source project. In order to achieve better and sustainable development of the project, i expect to gain more backers.
+If my custom nodes has added value to your day, consider indulging in a coffee to fuel it further!
+💖You can support me in any of the following ways: + +- [BiliBili](https://space.bilibili.com/1840885116) +- [Afdian](https://afdian.com/a/yolain) +- [Wechat / Alipay](https://github.com/user-attachments/assets/803469bd-ed6a-4fab-932d-50e5088a2d03) +- 🪙 Wallet Address: + - ETH: 0x01f7CEd3245CaB3891A0ec8f528178db352EaC74 + - USDT(tron): TP3AnJXkAzfebL2GKmFAvQvXgsxzivweV6 + +(This is a newly created wallet, and if it receives sponsorship, I'll use it to rent GPUs or other GPT services for better debugging and refinement of ComfyUI-Easy-Use features.) + +## 🌟Stargazers + +My gratitude extends to the generous souls who bestow a star. Your support is much appreciated! + [![Stargazers repo roster for @yolain/ComfyUI-Easy-Use](https://reporoster.com/stars/yolain/ComfyUI-Easy-Use)](https://github.com/yolain/ComfyUI-Easy-Use/stargazers) \ No newline at end of file diff --git a/__init__.py b/__init__.py index 5dc003b..7538d55 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.2.5" +__version__ = "1.2.6" import yaml import os diff --git a/py/brushnet/__init__.py b/py/brushnet/__init__.py index 5955fe9..b700042 100644 --- a/py/brushnet/__init__.py +++ b/py/brushnet/__init__.py @@ -664,7 +664,7 @@ def add_brushnet_patch(model, brushnet, torch_dtype, conditioning_latents, is_SDXL = isinstance(model.model.model_config, comfy.supported_models.SDXL) if is_SDXL: - input_blocks = [[0, comfy.ops.disable_weight_init.Conv2d], + input_blocks = [[0, comfy.ops.manual_cast.Conv2d], [1, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock], [2, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock], [3, comfy.ldm.modules.diffusionmodules.openaimodel.Downsample], @@ -686,7 +686,7 @@ def add_brushnet_patch(model, brushnet, torch_dtype, conditioning_latents, [7, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock], [8, comfy.ldm.modules.diffusionmodules.openaimodel.ResBlock]] else: - input_blocks = [[0, comfy.ops.disable_weight_init.Conv2d], + input_blocks = [[0, comfy.ops.manual_cast.Conv2d], [1, comfy.ldm.modules.attention.SpatialTransformer], [2, comfy.ldm.modules.attention.SpatialTransformer], [3, comfy.ldm.modules.diffusionmodules.openaimodel.Downsample], diff --git a/py/config.py b/py/config.py index 27436cf..85d1ec3 100644 --- a/py/config.py +++ b/py/config.py @@ -190,6 +190,9 @@ REMBG_MODELS = { "RMBG-1.4": { "model_url": "https://huggingface.co/briaai/RMBG-1.4/resolve/main/model.pth" + }, + "RMBG-2.0": { + "model_url": "briaai/RMBG-2.0" } } @@ -197,7 +200,7 @@ IPADAPTER_DIR = os.path.join(folder_paths.models_dir, "ipadapter") IPADAPTER_MODELS = { "LIGHT - SD1.5 only (low strength)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_light_v11.bin" }, "sdxl": { @@ -205,7 +208,7 @@ } }, "STANDARD (medium strength)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15.safetensors" }, "sdxl": { @@ -213,7 +216,7 @@ } }, "VIT-G (medium strength)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter_sd15_vit-G.safetensors" }, "sdxl": { @@ -221,23 +224,33 @@ } }, "PLUS (high strength)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus_sd15.safetensors" }, "sdxl": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/sdxl_models/ip-adapter-plus_sdxl_vit-h.safetensors" } }, - "PLUS (kolors genernal)":{ - "sd15":{ - "model_url":"" + "PLUS (kolors genernal)": { + "sd1": { + "model_url": "" }, - "sdxl":{ + "sdxl": { "model_url":"https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/ip_adapter_plus_general.bin" } }, + "REGULAR - FLUX and SD3.5 only (high strength)": { + "flux": { + "model_url": "https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter/resolve/main/ip-adapter.bin", + "model_file_name": "ip-adapter_flux_1_dev.bin", + }, + "sd3": { + "model_url": "https://huggingface.co/InstantX/SD3.5-Large-IP-Adapter/resolve/main/ip-adapter.bin", + "model_file_name": "ip-adapter_sd35.bin", + }, + }, "PLUS FACE (portraits)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-plus-face_sd15.safetensors" }, "sdxl": { @@ -245,7 +258,7 @@ } }, "FULL FACE - SD1.5 only (portraits stronger)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter/resolve/main/models/ip-adapter-full-face_sd15.safetensors" }, "sdxl": { @@ -253,7 +266,7 @@ } }, "FACEID": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15.bin", "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid_sd15_lora.safetensors" }, @@ -263,7 +276,7 @@ } }, "FACEID PLUS - SD1.5 only": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15.bin", "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plus_sd15_lora.safetensors" }, @@ -273,7 +286,7 @@ } }, "FACEID PLUS V2": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15.bin", "lora_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-plusv2_sd15_lora.safetensors" }, @@ -283,7 +296,7 @@ } }, "FACEID PLUS KOLORS":{ - "sd15":{ + "sd1":{ }, "sdxl":{ @@ -291,7 +304,7 @@ } }, "FACEID PORTRAIT (style transfer)": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/h94/IP-Adapter-FaceID/resolve/main/ip-adapter-faceid-portrait-v11_sd15.bin", }, "sdxl": { @@ -299,7 +312,7 @@ } }, "FACEID PORTRAIT UNNORM - SDXL only (strong)": { - "sd15": { + "sd1": { "model_url":"" }, "sdxl": { @@ -307,7 +320,7 @@ } }, "COMPOSITION": { - "sd15": { + "sd1": { "model_url": "https://huggingface.co/ostris/ip-composition-adapter/resolve/main/ip_plus_composition_sd15.safetensors" }, "sdxl": { @@ -321,6 +334,9 @@ }, "clip-vit-h-14-laion2B-s32B-b79K":{ "model_url": "https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K/resolve/main/open_clip_pytorch_model.safetensors" + }, + "sigclip_vision_patch14_384":{ + "model_url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors" } } diff --git a/py/easyNodes.py b/py/easyNodes.py index a084be1..e7f6419 100644 --- a/py/easyNodes.py +++ b/py/easyNodes.py @@ -1,8067 +1,8200 @@ -import sys, os, re, json, time -import torch -import folder_paths -import numpy as np -import comfy.utils, comfy.sample, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management, comfy.sampler_helpers, comfy.supported_models -from comfy.sd import CLIP, VAE -from comfy.model_patcher import ModelPatcher -from comfy_extras.chainner_models import model_loading -from comfy_extras.nodes_mask import LatentCompositeMasked, GrowMask -from comfy_extras.nodes_compositing import JoinImageWithAlpha -from comfy.clip_vision import load as load_clip_vision -from urllib.request import urlopen -from PIL import Image - -from server import PromptServer -from nodes import MAX_RESOLUTION, LatentFromBatch, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, CLIPTextEncode, VAEEncodeForInpaint, InpaintModelConditioning, ConditioningZeroOut -from .config import MAX_SEED_NUM, BASE_RESOLUTIONS, RESOURCES_DIR, INPAINT_DIR, FOOOCUS_STYLES_DIR, FOOOCUS_INPAINT_HEAD, FOOOCUS_INPAINT_PATCH, BRUSHNET_MODELS, POWERPAINT_MODELS, IPADAPTER_DIR, IPADAPTER_CLIPVISION_MODELS, IPADAPTER_MODELS, DYNAMICRAFTER_DIR, DYNAMICRAFTER_MODELS, IC_LIGHT_MODELS -from .layer_diffuse import LayerDiffuse, LayerMethod -from .xyplot import * - -from .libs.log import log_node_info, log_node_error, log_node_warn -from .libs.adv_encode import advanced_encode -from .libs.wildcards import process_with_loras, get_wildcard_list, process -from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, AlwaysEqualProxy, get_sd_version -from .libs.loader import easyLoader -from .libs.sampler import easySampler, alignYourStepsScheduler, gitsScheduler -from .libs.xyplot import easyXYPlot -from .libs.controlnet import easyControlnet, union_controlnet_types -from .libs.conditioning import prompt_to_cond, set_cond -from .libs.easing import EasingBase -from .libs.translate import has_chinese, zh_to_en -from .libs import cache as backend_cache - -sampler = easySampler() -easyCache = easyLoader() - -new_schedulers = ['align_your_steps', 'gits'] -# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------# - -# 正面提示词 -class positivePrompt: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "positive": ("STRING", {"default": "", "multiline": True, "placeholder": "Positive"}),} - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("positive",) - FUNCTION = "main" - - CATEGORY = "EasyUse/Prompt" - - @staticmethod - def main(positive): - return positive, - -# 通配符提示词 -class wildcardsPrompt: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - wildcard_list = get_wildcard_list() - return {"required": { - "text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}), - "Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),), - "Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - "multiline_mode": ("BOOLEAN", {"default": False}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("STRING", "STRING") - RETURN_NAMES = ("text", "populated_text") - OUTPUT_IS_LIST = (True, True) - FUNCTION = "main" - - CATEGORY = "EasyUse/Prompt" - - def translate(self, text): - return text - - def main(self, *args, **kwargs): - prompt = kwargs["prompt"] if "prompt" in kwargs else None - seed = kwargs["seed"] - - # Clean loaded_objects - if prompt: - easyCache.update_loaded_objects(prompt) - - text = kwargs['text'] - if "multiline_mode" in kwargs and kwargs["multiline_mode"]: - populated_text = [] - _text = [] - text = text.split("\n") - for t in text: - t = self.translate(t) - _text.append(t) - populated_text.append(process(t, seed)) - text = _text - else: - text = self.translate(text) - populated_text = [process(text, seed)] - text = [text] - return {"ui": {"value": [seed]}, "result": (text, populated_text)} - -# 负面提示词 -class negativePrompt: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "negative": ("STRING", {"default": "", "multiline": True, "placeholder": "Negative"}),} - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("negative",) - FUNCTION = "main" - - CATEGORY = "EasyUse/Prompt" - - @staticmethod - def main(negative): - return negative, - -# 风格提示词选择器 -class stylesPromptSelector: - - @classmethod - def INPUT_TYPES(s): - styles = ["fooocus_styles"] - styles_dir = FOOOCUS_STYLES_DIR - for file_name in os.listdir(styles_dir): - file = os.path.join(styles_dir, file_name) - if os.path.isfile(file) and file_name.endswith(".json"): - styles.append(file_name.split(".")[0]) - return { - "required": { - "styles": (styles, {"default": "fooocus_styles"}), - }, - "optional": { - "positive": ("STRING", {"forceInput": True}), - "negative": ("STRING", {"forceInput": True}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("STRING", "STRING",) - RETURN_NAMES = ("positive", "negative",) - - CATEGORY = 'EasyUse/Prompt' - FUNCTION = 'run' - - def run(self, styles, positive='', negative='', prompt=None, extra_pnginfo=None, my_unique_id=None): - values = [] - all_styles = {} - positive_prompt, negative_prompt = '', negative - if styles == "fooocus_styles": - file = os.path.join(RESOURCES_DIR, styles + '.json') - else: - file = os.path.join(FOOOCUS_STYLES_DIR, styles + '.json') - f = open(file, 'r', encoding='utf-8') - data = json.load(f) - f.close() - for d in data: - all_styles[d['name']] = d - if my_unique_id in prompt: - if prompt[my_unique_id]["inputs"]['select_styles']: - values = prompt[my_unique_id]["inputs"]['select_styles'].split(',') - - has_prompt = False - if len(values) == 0: - return (positive, negative) - - for index, val in enumerate(values): - if 'prompt' in all_styles[val]: - if "{prompt}" in all_styles[val]['prompt'] and has_prompt == False: - positive_prompt = all_styles[val]['prompt'].replace('{prompt}', positive) - has_prompt = True - else: - positive_prompt += ', ' + all_styles[val]['prompt'].replace(', {prompt}', '').replace('{prompt}', '') - if 'negative_prompt' in all_styles[val]: - negative_prompt += ', ' + all_styles[val]['negative_prompt'] if negative_prompt else all_styles[val]['negative_prompt'] - - if has_prompt == False and positive: - positive_prompt = positive + ', ' - - return (positive_prompt, negative_prompt) - -#prompt -class prompt: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "prompt": ("STRING", {"default": "", "multiline": True, "placeholder": "Prompt"}), - "main": ([ - 'none', - 'beautiful woman, detailed face', - 'handsome man, detailed face', - 'pretty girl', - 'handsome boy', - 'dog', - 'cat', - 'Buddha', - 'toy' - ], {"default": "none"}), - "lighting": ([ - 'none', - 'sunshine from window', - 'neon light, city', - 'sunset over sea', - 'golden time', - 'sci-fi RGB glowing, cyberpunk', - 'natural lighting', - 'warm atmosphere, at home, bedroom', - 'magic lit', - 'evil, gothic, Yharnam', - 'light and shadow', - 'shadow from window', - 'soft studio lighting', - 'home atmosphere, cozy bedroom illumination', - 'neon, Wong Kar-wai, warm', - 'cinemative lighting', - 'neo punk lighting, cyberpunk', - ],{"default":'none'}) - }} - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("prompt",) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Prompt" - - def doit(self, prompt, main, lighting): - if lighting != 'none' and main != 'none': - prompt = main + ',' + lighting + ',' + prompt - elif lighting != 'none' and main == 'none': - prompt = prompt + ',' + lighting - elif main != 'none': - prompt = main + ',' + prompt - - return prompt, -#promptList -class promptList: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "prompt_1": ("STRING", {"multiline": True, "default": ""}), - "prompt_2": ("STRING", {"multiline": True, "default": ""}), - "prompt_3": ("STRING", {"multiline": True, "default": ""}), - "prompt_4": ("STRING", {"multiline": True, "default": ""}), - "prompt_5": ("STRING", {"multiline": True, "default": ""}), - }, - "optional": { - "optional_prompt_list": ("LIST",) - } - } - - RETURN_TYPES = ("LIST", "STRING") - RETURN_NAMES = ("prompt_list", "prompt_strings") - OUTPUT_IS_LIST = (False, True) - FUNCTION = "run" - CATEGORY = "EasyUse/Prompt" - - def run(self, **kwargs): - prompts = [] - - if "optional_prompt_list" in kwargs: - for l in kwargs["optional_prompt_list"]: - prompts.append(l) - - # Iterate over the received inputs in sorted order. - for k in sorted(kwargs.keys()): - v = kwargs[k] - - # Only process string input ports. - if isinstance(v, str) and v != '': - prompts.append(v) - - return (prompts, prompts) - -#promptLine -class promptLine: - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "prompt": ("STRING", {"multiline": True, "default": "text"}), - "start_index": ("INT", {"default": 0, "min": 0, "max": 9999}), - "max_rows": ("INT", {"default": 1000, "min": 1, "max": 9999}), - }, - "hidden":{ - "workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID" - } - } - - RETURN_TYPES = ("STRING", AlwaysEqualProxy('*')) - RETURN_NAMES = ("STRING", "COMBO") - OUTPUT_IS_LIST = (True, True) - FUNCTION = "generate_strings" - CATEGORY = "EasyUse/Prompt" - - def generate_strings(self, prompt, start_index, max_rows, workflow_prompt=None, my_unique_id=None): - lines = prompt.split('\n') - # lines = [zh_to_en([v])[0] if has_chinese(v) else v for v in lines if v] - - start_index = max(0, min(start_index, len(lines) - 1)) - - end_index = min(start_index + max_rows, len(lines)) - - rows = lines[start_index:end_index] - - return (rows, rows) - -class promptConcat: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - }, - "optional": { - "prompt1": ("STRING", {"multiline": False, "default": "", "forceInput": True}), - "prompt2": ("STRING", {"multiline": False, "default": "", "forceInput": True}), - "separator": ("STRING", {"multiline": False, "default": ""}), - }, - } - RETURN_TYPES = ("STRING", ) - RETURN_NAMES = ("prompt", ) - FUNCTION = "concat_text" - CATEGORY = "EasyUse/Prompt" - - def concat_text(self, prompt1="", prompt2="", separator=""): - - return (prompt1 + separator + prompt2,) - -class promptReplace: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "prompt": ("STRING", {"multiline": True, "default": "", "forceInput": True}), - }, - "optional": { - "find1": ("STRING", {"multiline": False, "default": ""}), - "replace1": ("STRING", {"multiline": False, "default": ""}), - "find2": ("STRING", {"multiline": False, "default": ""}), - "replace2": ("STRING", {"multiline": False, "default": ""}), - "find3": ("STRING", {"multiline": False, "default": ""}), - "replace3": ("STRING", {"multiline": False, "default": ""}), - }, - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("prompt",) - FUNCTION = "replace_text" - CATEGORY = "EasyUse/Prompt" - - def replace_text(self, prompt, find1="", replace1="", find2="", replace2="", find3="", replace3=""): - - prompt = prompt.replace(find1, replace1) - prompt = prompt.replace(find2, replace2) - prompt = prompt.replace(find3, replace3) - - return (prompt,) - - -# 肖像大师 -# Created by AI Wiz Art (Stefano Flore) -# Version: 2.2 -# https://stefanoflore.it -# https://ai-wiz.art -class portraitMaster: - - @classmethod - def INPUT_TYPES(s): - max_float_value = 1.95 - prompt_path = os.path.join(RESOURCES_DIR, 'portrait_prompt.json') - if not os.path.exists(prompt_path): - response = urlopen('https://raw.githubusercontent.com/yolain/ComfyUI-Easy-Use/main/resources/portrait_prompt.json') - temp_prompt = json.loads(response.read()) - prompt_serialized = json.dumps(temp_prompt, indent=4) - with open(prompt_path, "w") as f: - f.write(prompt_serialized) - del response, temp_prompt - # Load local - with open(prompt_path, 'r') as f: - list = json.load(f) - keys = [ - ['shot', 'COMBO', {"key": "shot_list"}], ['shot_weight', 'FLOAT'], - ['gender', 'COMBO', {"default": "Woman", "key": "gender_list"}], ['age', 'INT', {"default": 30, "min": 18, "max": 90, "step": 1, "display": "slider"}], - ['nationality_1', 'COMBO', {"default": "Chinese", "key": "nationality_list"}], ['nationality_2', 'COMBO', {"key": "nationality_list"}], ['nationality_mix', 'FLOAT'], - ['body_type', 'COMBO', {"key": "body_type_list"}], ['body_type_weight', 'FLOAT'], ['model_pose', 'COMBO', {"key": "model_pose_list"}], ['eyes_color', 'COMBO', {"key": "eyes_color_list"}], - ['facial_expression', 'COMBO', {"key": "face_expression_list"}], ['facial_expression_weight', 'FLOAT'], ['face_shape', 'COMBO', {"key": "face_shape_list"}], ['face_shape_weight', 'FLOAT'], ['facial_asymmetry', 'FLOAT'], - ['hair_style', 'COMBO', {"key": "hair_style_list"}], ['hair_color', 'COMBO', {"key": "hair_color_list"}], ['disheveled', 'FLOAT'], ['beard', 'COMBO', {"key": "beard_list"}], - ['skin_details', 'FLOAT'], ['skin_pores', 'FLOAT'], ['dimples', 'FLOAT'], ['freckles', 'FLOAT'], - ['moles', 'FLOAT'], ['skin_imperfections', 'FLOAT'], ['skin_acne', 'FLOAT'], ['tanned_skin', 'FLOAT'], - ['eyes_details', 'FLOAT'], ['iris_details', 'FLOAT'], ['circular_iris', 'FLOAT'], ['circular_pupil', 'FLOAT'], - ['light_type', 'COMBO', {"key": "light_type_list"}], ['light_direction', 'COMBO', {"key": "light_direction_list"}], ['light_weight', 'FLOAT'] - ] - widgets = {} - for i, obj in enumerate(keys): - if obj[1] == 'COMBO': - key = obj[2]['key'] if obj[2] and 'key' in obj[2] else obj[0] - _list = list[key].copy() - _list.insert(0, '-') - widgets[obj[0]] = (_list, {**obj[2]}) - elif obj[1] == 'FLOAT': - widgets[obj[0]] = ("FLOAT", {"default": 0, "step": 0.05, "min": 0, "max": max_float_value, "display": "slider",}) - elif obj[1] == 'INT': - widgets[obj[0]] = (obj[1], obj[2]) - del list - return { - "required": { - **widgets, - "photorealism_improvement": (["enable", "disable"],), - "prompt_start": ("STRING", {"multiline": True, "default": "raw photo, (realistic:1.5)"}), - "prompt_additional": ("STRING", {"multiline": True, "default": ""}), - "prompt_end": ("STRING", {"multiline": True, "default": ""}), - "negative_prompt": ("STRING", {"multiline": True, "default": ""}), - } - } - - RETURN_TYPES = ("STRING", "STRING",) - RETURN_NAMES = ("positive", "negative",) - - FUNCTION = "pm" - - CATEGORY = "EasyUse/Prompt" - - def pm(self, shot="-", shot_weight=1, gender="-", body_type="-", body_type_weight=0, eyes_color="-", - facial_expression="-", facial_expression_weight=0, face_shape="-", face_shape_weight=0, - nationality_1="-", nationality_2="-", nationality_mix=0.5, age=30, hair_style="-", hair_color="-", - disheveled=0, dimples=0, freckles=0, skin_pores=0, skin_details=0, moles=0, skin_imperfections=0, - wrinkles=0, tanned_skin=0, eyes_details=1, iris_details=1, circular_iris=1, circular_pupil=1, - facial_asymmetry=0, prompt_additional="", prompt_start="", prompt_end="", light_type="-", - light_direction="-", light_weight=0, negative_prompt="", photorealism_improvement="disable", beard="-", - model_pose="-", skin_acne=0): - - prompt = [] - - if gender == "-": - gender = "" - else: - if age <= 25 and gender == 'Woman': - gender = 'girl' - if age <= 25 and gender == 'Man': - gender = 'boy' - gender = " " + gender + " " - - if nationality_1 != '-' and nationality_2 != '-': - nationality = f"[{nationality_1}:{nationality_2}:{round(nationality_mix, 2)}]" - elif nationality_1 != '-': - nationality = nationality_1 + " " - elif nationality_2 != '-': - nationality = nationality_2 + " " - else: - nationality = "" - - if prompt_start != "": - prompt.append(f"{prompt_start}") - - if shot != "-" and shot_weight > 0: - prompt.append(f"({shot}:{round(shot_weight, 2)})") - - prompt.append(f"({nationality}{gender}{round(age)}-years-old:1.5)") - - if body_type != "-" and body_type_weight > 0: - prompt.append(f"({body_type}, {body_type} body:{round(body_type_weight, 2)})") - - if model_pose != "-": - prompt.append(f"({model_pose}:1.5)") - - if eyes_color != "-": - prompt.append(f"({eyes_color} eyes:1.25)") - - if facial_expression != "-" and facial_expression_weight > 0: - prompt.append( - f"({facial_expression}, {facial_expression} expression:{round(facial_expression_weight, 2)})") - - if face_shape != "-" and face_shape_weight > 0: - prompt.append(f"({face_shape} shape face:{round(face_shape_weight, 2)})") - - if hair_style != "-": - prompt.append(f"({hair_style} hairstyle:1.25)") - - if hair_color != "-": - prompt.append(f"({hair_color} hair:1.25)") - - if beard != "-": - prompt.append(f"({beard}:1.15)") - - if disheveled != "-" and disheveled > 0: - prompt.append(f"(disheveled:{round(disheveled, 2)})") - - if prompt_additional != "": - prompt.append(f"{prompt_additional}") - - if skin_details > 0: - prompt.append(f"(skin details, skin texture:{round(skin_details, 2)})") - - if skin_pores > 0: - prompt.append(f"(skin pores:{round(skin_pores, 2)})") - - if skin_imperfections > 0: - prompt.append(f"(skin imperfections:{round(skin_imperfections, 2)})") - - if skin_acne > 0: - prompt.append(f"(acne, skin with acne:{round(skin_acne, 2)})") - - if wrinkles > 0: - prompt.append(f"(skin imperfections:{round(wrinkles, 2)})") - - if tanned_skin > 0: - prompt.append(f"(tanned skin:{round(tanned_skin, 2)})") - - if dimples > 0: - prompt.append(f"(dimples:{round(dimples, 2)})") - - if freckles > 0: - prompt.append(f"(freckles:{round(freckles, 2)})") - - if moles > 0: - prompt.append(f"(skin pores:{round(moles, 2)})") - - if eyes_details > 0: - prompt.append(f"(eyes details:{round(eyes_details, 2)})") - - if iris_details > 0: - prompt.append(f"(iris details:{round(iris_details, 2)})") - - if circular_iris > 0: - prompt.append(f"(circular iris:{round(circular_iris, 2)})") - - if circular_pupil > 0: - prompt.append(f"(circular pupil:{round(circular_pupil, 2)})") - - if facial_asymmetry > 0: - prompt.append(f"(facial asymmetry, face asymmetry:{round(facial_asymmetry, 2)})") - - if light_type != '-' and light_weight > 0: - if light_direction != '-': - prompt.append(f"({light_type} {light_direction}:{round(light_weight, 2)})") - else: - prompt.append(f"({light_type}:{round(light_weight, 2)})") - - if prompt_end != "": - prompt.append(f"{prompt_end}") - - prompt = ", ".join(prompt) - prompt = prompt.lower() - - if photorealism_improvement == "enable": - prompt = prompt + ", (professional photo, balanced photo, balanced exposure:1.2), (film grain:1.15)" - - if photorealism_improvement == "enable": - negative_prompt = negative_prompt + ", (shinny skin, reflections on the skin, skin reflections:1.25)" - - log_node_info("Portrait Master as generate the prompt:", prompt) - - return (prompt, negative_prompt,) - -# ---------------------------------------------------------------提示词 结束----------------------------------------------------------------------# - -# ---------------------------------------------------------------潜空间 开始----------------------------------------------------------------------# -# 潜空间sigma相乘 -class latentNoisy: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS,), - "steps": ("INT", {"default": 10000, "min": 0, "max": 10000}), - "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), - "end_at_step": ("INT", {"default": 10000, "min": 1, "max": 10000}), - "source": (["CPU", "GPU"],), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - }, - "optional": { - "pipe": ("PIPE_LINE",), - "optional_model": ("MODEL",), - "optional_latent": ("LATENT",) - }} - - RETURN_TYPES = ("PIPE_LINE", "LATENT", "FLOAT",) - RETURN_NAMES = ("pipe", "latent", "sigma",) - FUNCTION = "run" - - CATEGORY = "EasyUse/Latent" - - def run(self, sampler_name, scheduler, steps, start_at_step, end_at_step, source, seed, pipe=None, optional_model=None, optional_latent=None): - model = optional_model if optional_model is not None else pipe["model"] - batch_size = pipe["loader_settings"]["batch_size"] - empty_latent_height = pipe["loader_settings"]["empty_latent_height"] - empty_latent_width = pipe["loader_settings"]["empty_latent_width"] - - if optional_latent is not None: - samples = optional_latent - else: - torch.manual_seed(seed) - if source == "CPU": - device = "cpu" - else: - device = comfy.model_management.get_torch_device() - noise = torch.randn((batch_size, 4, empty_latent_height // 8, empty_latent_width // 8), dtype=torch.float32, - device=device).cpu() - - samples = {"samples": noise} - - device = comfy.model_management.get_torch_device() - end_at_step = min(steps, end_at_step) - start_at_step = min(start_at_step, end_at_step) - comfy.model_management.load_model_gpu(model) - model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device()) - sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name, - scheduler=scheduler, denoise=1.0, model_options=model.model_options) - sigmas = sampler.sigmas - sigma = sigmas[start_at_step] - sigmas[end_at_step] - sigma /= model.model.latent_format.scale_factor - sigma = sigma.cpu().numpy() - - samples_out = samples.copy() - - s1 = samples["samples"] - samples_out["samples"] = s1 * sigma - - if pipe is None: - pipe = {} - new_pipe = { - **pipe, - "samples": samples_out - } - del pipe - - return (new_pipe, samples_out, sigma) - -# Latent遮罩复合 -class latentCompositeMaskedWithCond: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "text_combine": ("LIST",), - "source_latent": ("LATENT",), - "source_mask": ("MASK",), - "destination_mask": ("MASK",), - "text_combine_mode": (["add", "replace", "cover"], {"default": "add"}), - "replace_text": ("STRING", {"default": ""}) - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - OUTPUT_IS_LIST = (False, False, True) - RETURN_TYPES = ("PIPE_LINE", "LATENT", "CONDITIONING") - RETURN_NAMES = ("pipe", "latent", "conditioning",) - FUNCTION = "run" - - CATEGORY = "EasyUse/Latent" - - def run(self, pipe, text_combine, source_latent, source_mask, destination_mask, text_combine_mode, replace_text, prompt=None, extra_pnginfo=None, my_unique_id=None): - positive = None - clip = pipe["clip"] - destination_latent = pipe["samples"] - - conds = [] - - for text in text_combine: - if text_combine_mode == 'cover': - positive = text - elif text_combine_mode == 'replace' and replace_text != '': - positive = pipe["loader_settings"]["positive"].replace(replace_text, text) - else: - positive = pipe["loader_settings"]["positive"] + ',' + text - positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"] - positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"] - a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"] - positive_cond = pipe["positive"] - - log_node_warn("Positive encoding...") - steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 1 - positive_embeddings_final = advanced_encode(clip, positive, - positive_token_normalization, - positive_weight_interpretation, w_max=1.0, - apply_to_pooled='enable', a1111_prompt_style=a1111_prompt_style, steps=steps) - - # source cond - (cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1) - (cond_2,) = ConditioningSetMask().append(positive_embeddings_final, destination_mask, "default", 1) - positive_cond = cond_1 + cond_2 - - conds.append(positive_cond) - # latent composite masked - (samples,) = LatentCompositeMasked().composite(destination_latent, source_latent, 0, 0, False) - - new_pipe = { - **pipe, - "samples": samples, - "loader_settings": { - **pipe["loader_settings"], - "positive": positive, - } - } - - del pipe - - return (new_pipe, samples, conds) - -# 噪声注入到潜空间 -class injectNoiseToLatent: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "strength": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 200.0, "step": 0.0001}), - "normalize": ("BOOLEAN", {"default": False}), - "average": ("BOOLEAN", {"default": False}), - }, - "optional": { - "pipe_to_noise": ("PIPE_LINE",), - "image_to_latent": ("IMAGE",), - "latent": ("LATENT",), - "noise": ("LATENT",), - "mask": ("MASK",), - "mix_randn_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.001}), - "seed": ("INT", {"default": 123, "min": 0, "max": 0xffffffffffffffff, "step": 1}), - } - } - - RETURN_TYPES = ("LATENT",) - FUNCTION = "inject" - CATEGORY = "EasyUse/Latent" - - def inject(self,strength, normalize, average, pipe_to_noise=None, noise=None, image_to_latent=None, latent=None, mix_randn_amount=0, mask=None, seed=None): - - vae = pipe_to_noise["vae"] if pipe_to_noise is not None else pipe_to_noise["vae"] - batch_size = pipe_to_noise["loader_settings"]["batch_size"] if pipe_to_noise is not None and "batch_size" in pipe_to_noise["loader_settings"] else 1 - if noise is None and pipe_to_noise is not None: - noise = pipe_to_noise["samples"] - elif noise is None: - raise Exception("InjectNoiseToLatent: No noise provided") - - if image_to_latent is not None and vae is not None: - samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} - latents = RepeatLatentBatch().repeat(samples, batch_size)[0] - elif latent is not None: - latents = latent - else: - latents = {"samples": noise["samples"].clone()} - - samples = latents.copy() - if latents["samples"].shape != noise["samples"].shape: - raise ValueError("InjectNoiseToLatent: Latent and noise must have the same shape") - if average: - noised = (samples["samples"].clone() + noise["samples"].clone()) / 2 - else: - noised = samples["samples"].clone() + noise["samples"].clone() * strength - if normalize: - noised = noised / noised.std() - if mask is not None: - mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), - size=(noised.shape[2], noised.shape[3]), mode="bilinear") - mask = mask.expand((-1, noised.shape[1], -1, -1)) - if mask.shape[0] < noised.shape[0]: - mask = mask.repeat((noised.shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:noised.shape[0]] - noised = mask * noised + (1 - mask) * latents["samples"] - if mix_randn_amount > 0: - if seed is not None: - torch.manual_seed(seed) - rand_noise = torch.randn_like(noised) - noised = ((1 - mix_randn_amount) * noised + mix_randn_amount * - rand_noise) / ((mix_randn_amount ** 2 + (1 - mix_randn_amount) ** 2) ** 0.5) - samples["samples"] = noised - return (samples,) - -# ---------------------------------------------------------------潜空间 结束----------------------------------------------------------------------# - -# ---------------------------------------------------------------随机种 开始----------------------------------------------------------------------# -# 随机种 -class easySeed: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("INT",) - RETURN_NAMES = ("seed",) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Seed" - - def doit(self, seed=0, prompt=None, extra_pnginfo=None, my_unique_id=None): - return seed, - -# 全局随机种 -class globalSeed: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "value": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - "mode": ("BOOLEAN", {"default": True, "label_on": "control_before_generate", "label_off": "control_after_generate"}), - "action": (["fixed", "increment", "decrement", "randomize", - "increment for each node", "decrement for each node", "randomize for each node"], ), - "last_seed": ("STRING", {"default": ""}), - } - } - - RETURN_TYPES = () - FUNCTION = "doit" - - CATEGORY = "EasyUse/Seed" - - OUTPUT_NODE = True - - def doit(self, **kwargs): - return {} - -# ---------------------------------------------------------------随机种 结束----------------------------------------------------------------------# - -# ---------------------------------------------------------------加载器 开始----------------------------------------------------------------------# -class setCkptName: - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), - } - } - - RETURN_TYPES = (AlwaysEqualProxy('*'),) - RETURN_NAMES = ("ckpt_name",) - FUNCTION = "set_name" - CATEGORY = "EasyUse/Util" - - def set_name(self, ckpt_name): - return (ckpt_name,) - -class setControlName: - - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "controlnet_name": (folder_paths.get_filename_list("controlnet"),), - } - } - - RETURN_TYPES = (AlwaysEqualProxy('*'),) - RETURN_NAMES = ("controlnet_name",) - FUNCTION = "set_name" - CATEGORY = "EasyUse/Util" - - def set_name(self, controlnet_name): - return (controlnet_name,) - -# 简易加载器完整 -resolution_strings = [f"{width} x {height} (custom)" if width == 'width' and height == 'height' else f"{width} x {height}" for width, height in BASE_RESOLUTIONS] -class fullLoader: - - @classmethod - def INPUT_TYPES(cls): - a1111_prompt_style_default = False - - return {"required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), - "config_name": (["Default", ] + folder_paths.get_filename_list("configs"), {"default": "Default"}), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), - - "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - - "resolution": (resolution_strings,), - "empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - "positive_token_normalization": (["none", "mean", "length", "length+mean"],), - "positive_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), - - "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), - "negative_token_normalization": (["none", "mean", "length", "length+mean"],), - "negative_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), - - "batch_size": ( - "INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) - }, - "optional": {"model_override": ("MODEL",), "clip_override": ("CLIP",), "vae_override": ("VAE",), "optional_lora_stack": ("LORA_STACK",), "optional_controlnet_stack": ("CONTROL_NET_STACK",), "a1111_prompt_style": ("BOOLEAN", {"default": a1111_prompt_style_default})}, - "hidden": {"video_length": "INT", "prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE", "CLIP", "CONDITIONING", "CONDITIONING", "LATENT") - RETURN_NAMES = ("pipe", "model", "vae", "clip", "positive", "negative", "latent") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def adv_pipeloader(self, ckpt_name, config_name, vae_name, clip_skip, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, positive_token_normalization, positive_weight_interpretation, - negative, negative_token_normalization, negative_weight_interpretation, - batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, optional_controlnet_stack=None, a1111_prompt_style=False, video_length=25, prompt=None, - my_unique_id=None - ): - - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - # Load models - log_node_warn("Loading models...") - model, clip, vae, clip_vision, lora_stack = easyCache.load_main(ckpt_name, config_name, vae_name, lora_name, lora_model_strength, lora_clip_strength, optional_lora_stack, model_override, clip_override, vae_override, prompt) - - # Create Empty Latent - model_type = get_sd_version(model) - samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size, model_type=model_type, video_length=video_length) - - # Prompt to Conditioning - positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, lora_stack, positive, positive_token_normalization, positive_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache, model_type=model_type) - negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, clip_skip, lora_stack, negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache, model_type=model_type) - - if negative_embeddings_final is None: - negative_embeddings_final, = ConditioningZeroOut().zero_out(positive_embeddings_final) - - # Conditioning add controlnet - if optional_controlnet_stack is not None and len(optional_controlnet_stack) > 0: - for controlnet in optional_controlnet_stack: - positive_embeddings_final, negative_embeddings_final = easyControlnet().apply(controlnet[0], controlnet[5], positive_embeddings_final, negative_embeddings_final, controlnet[1], start_percent=controlnet[2], end_percent=controlnet[3], control_net=None, scale_soft_weights=controlnet[4], mask=None, easyCache=easyCache, use_cache=True, model=model, vae=vae) - - pipe = { - "model": model, - "positive": positive_embeddings_final, - "negative": negative_embeddings_final, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": None, - - "loader_settings": { - "ckpt_name": ckpt_name, - "vae_name": vae_name, - "lora_name": lora_name, - "lora_model_strength": lora_model_strength, - "lora_clip_strength": lora_clip_strength, - "lora_stack": lora_stack, - - "clip_skip": clip_skip, - "a1111_prompt_style": a1111_prompt_style, - "positive": positive, - "positive_token_normalization": positive_token_normalization, - "positive_weight_interpretation": positive_weight_interpretation, - "negative": negative, - "negative_token_normalization": negative_token_normalization, - "negative_weight_interpretation": negative_weight_interpretation, - "resolution": resolution, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": batch_size, - } - } - - return {"ui": {"positive": positive_wildcard_prompt, "negative": negative_wildcard_prompt}, "result": (pipe, model, vae, clip, positive_embeddings_final, negative_embeddings_final, samples)} - -# A1111简易加载器 -class a1111Loader(fullLoader): - @classmethod - def INPUT_TYPES(cls): - a1111_prompt_style_default = False - checkpoints = folder_paths.get_filename_list("checkpoints") - loras = ["None"] + folder_paths.get_filename_list("loras") - return { - "required": { - "ckpt_name": (checkpoints,), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), - - "lora_name": (loras,), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - - "resolution": (resolution_strings, {"default": "512 x 512"}), - "empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default":"", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default":"", "placeholder": "Negative", "multiline": True}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) - }, - "optional": { - "optional_lora_stack": ("LORA_STACK",), - "optional_controlnet_stack": ("CONTROL_NET_STACK",), - "a1111_prompt_style": ("BOOLEAN", {"default": a1111_prompt_style_default}), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "a1111loader" - CATEGORY = "EasyUse/Loaders" - - def a1111loader(self, ckpt_name, vae_name, clip_skip, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, negative, batch_size, optional_lora_stack=None, optional_controlnet_stack=None, a1111_prompt_style=False, prompt=None, - my_unique_id=None): - - return super().adv_pipeloader(ckpt_name, 'Default', vae_name, clip_skip, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, 'mean', 'A1111', - negative,'mean','A1111', - batch_size, None, None, None, optional_lora_stack=optional_lora_stack, optional_controlnet_stack=optional_controlnet_stack,a1111_prompt_style=a1111_prompt_style, prompt=prompt, - my_unique_id=my_unique_id - ) - -# Comfy简易加载器 -class comfyLoader(fullLoader): - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), - - "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - - "resolution": (resolution_strings, {"default": "512 x 512"}), - "empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) - }, - "optional": {"optional_lora_stack": ("LORA_STACK",), "optional_controlnet_stack": ("CONTROL_NET_STACK",),}, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "comfyloader" - CATEGORY = "EasyUse/Loaders" - - def comfyloader(self, ckpt_name, vae_name, clip_skip, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, negative, batch_size, optional_lora_stack=None, optional_controlnet_stack=None, prompt=None, - my_unique_id=None): - return super().adv_pipeloader(ckpt_name, 'Default', vae_name, clip_skip, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, 'none', 'comfy', - negative, 'none', 'comfy', - batch_size, None, None, None, optional_lora_stack=optional_lora_stack, optional_controlnet_stack=optional_controlnet_stack, a1111_prompt_style=False, prompt=prompt, - my_unique_id=my_unique_id - ) - -# hydit简易加载器 -class hunyuanDiTLoader(fullLoader): - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - - "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - - "resolution": (resolution_strings, {"default": "1024 x 1024"}), - "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), - }, - "optional": {"optional_lora_stack": ("LORA_STACK",), "optional_controlnet_stack": ("CONTROL_NET_STACK",),}, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "hyditloader" - CATEGORY = "EasyUse/Loaders" - - def hyditloader(self, ckpt_name, vae_name, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, negative, batch_size, optional_lora_stack=None, optional_controlnet_stack=None, prompt=None, - my_unique_id=None): - - return super().adv_pipeloader(ckpt_name, 'Default', vae_name, 0, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, 'none', 'comfy', - negative, 'none', 'comfy', - batch_size, None, None, None, optional_lora_stack=optional_lora_stack, optional_controlnet_stack=optional_controlnet_stack, a1111_prompt_style=False, prompt=prompt, - my_unique_id=my_unique_id - ) - -# stable Cascade -class cascadeLoader: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - - return {"required": { - "stage_c": (folder_paths.get_filename_list("unet") + folder_paths.get_filename_list("checkpoints"),), - "stage_b": (folder_paths.get_filename_list("unet") + folder_paths.get_filename_list("checkpoints"),), - "stage_a": (["Baked VAE"]+folder_paths.get_filename_list("vae"),), - "clip_name": (["None"] + folder_paths.get_filename_list("clip"),), - - "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - - "resolution": (resolution_strings, {"default": "1024 x 1024"}), - "empty_latent_width": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - "compression": ("INT", {"default": 42, "min": 32, "max": 64, "step": 1}), - - "positive": ("STRING", {"default":"", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default":"", "placeholder": "Negative", "multiline": True}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), - }, - "optional": {"optional_lora_stack": ("LORA_STACK",), }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "LATENT", "VAE") - RETURN_NAMES = ("pipe", "model_c", "latent_c", "vae") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def is_ckpt(self, name): - is_ckpt = False - path = folder_paths.get_full_path("checkpoints", name) - if path is not None: - is_ckpt = True - return is_ckpt - - def adv_pipeloader(self, stage_c, stage_b, stage_a, clip_name, lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, compression, - positive, negative, batch_size, optional_lora_stack=None,prompt=None, - my_unique_id=None): - - vae: VAE | None = None - model_c: ModelPatcher | None = None - model_b: ModelPatcher | None = None - clip: CLIP | None = None - can_load_lora = True - pipe_lora_stack = [] - - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - # Create Empty Latent - samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size, compression) - - if self.is_ckpt(stage_c): - model_c, clip, vae_c, clip_vision = easyCache.load_checkpoint(stage_c) - else: - model_c = easyCache.load_unet(stage_c) - vae_c = None - if self.is_ckpt(stage_b): - model_b, clip, vae_b, clip_vision = easyCache.load_checkpoint(stage_b) - else: - model_b = easyCache.load_unet(stage_b) - vae_b = None - - if optional_lora_stack is not None and can_load_lora: - for lora in optional_lora_stack: - lora = {"lora_name": lora[0], "model": model_c, "clip": clip, "model_strength": lora[1], "clip_strength": lora[2]} - model_c, clip = easyCache.load_lora(lora) - lora['model'] = model_c - lora['clip'] = clip - pipe_lora_stack.append(lora) - - if lora_name != "None" and can_load_lora: - lora = {"lora_name": lora_name, "model": model_c, "clip": clip, "model_strength": lora_model_strength, - "clip_strength": lora_clip_strength} - model_c, clip = easyCache.load_lora(lora) - pipe_lora_stack.append(lora) - - model = (model_c, model_b) - # Load clip - if clip_name != 'None': - clip = easyCache.load_clip(clip_name, "stable_cascade") - # Load vae - if stage_a not in ["Baked VAE", "Baked-VAE"]: - vae_b = easyCache.load_vae(stage_a) - - vae = (vae_c, vae_b) - # 判断是否连接 styles selector - is_positive_linked_styles_selector = is_linked_styles_selector(prompt, my_unique_id, 'positive') - is_negative_linked_styles_selector = is_linked_styles_selector(prompt, my_unique_id, 'negative') - - positive_seed = find_wildcards_seed(my_unique_id, positive, prompt) - # Translate cn to en - if has_chinese(positive): - positive = zh_to_en([positive])[0] - model_c, clip, positive, positive_decode, show_positive_prompt, pipe_lora_stack = process_with_loras(positive, - model_c, clip, - "positive", - positive_seed, - can_load_lora, - pipe_lora_stack, - easyCache) - positive_wildcard_prompt = positive_decode if show_positive_prompt or is_positive_linked_styles_selector else "" - negative_seed = find_wildcards_seed(my_unique_id, negative, prompt) - # Translate cn to en - if has_chinese(negative): - negative = zh_to_en([negative])[0] - model_c, clip, negative, negative_decode, show_negative_prompt, pipe_lora_stack = process_with_loras(negative, - model_c, clip, - "negative", - negative_seed, - can_load_lora, - pipe_lora_stack, - easyCache) - negative_wildcard_prompt = negative_decode if show_negative_prompt or is_negative_linked_styles_selector else "" - - tokens = clip.tokenize(positive) - cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) - positive_embeddings_final = [[cond, {"pooled_output": pooled}]] - - tokens = clip.tokenize(negative) - cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) - negative_embeddings_final = [[cond, {"pooled_output": pooled}]] - - image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) - - pipe = { - "model": model, - "positive": positive_embeddings_final, - "negative": negative_embeddings_final, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": image, - "seed": 0, - - "loader_settings": { - "vae_name": stage_a, - "lora_name": lora_name, - "lora_model_strength": lora_model_strength, - "lora_clip_strength": lora_clip_strength, - "lora_stack": pipe_lora_stack, - - "positive": positive, - "positive_token_normalization": 'none', - "positive_weight_interpretation": 'comfy', - "negative": negative, - "negative_token_normalization": 'none', - "negative_weight_interpretation": 'comfy', - "resolution": resolution, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": batch_size, - "compression": compression - } - } - - return {"ui": {"positive": positive_wildcard_prompt, "negative": negative_wildcard_prompt}, - "result": (pipe, model_c, model_b, vae)} - -# Zero123简易加载器 (3D) -try: - from comfy_extras.nodes_stable3d import camera_embeddings -except FileNotFoundError: - log_node_error("EasyUse[zero123Loader]", "请更新ComfyUI到最新版本") - -class zero123Loader: - - @classmethod - def INPUT_TYPES(cls): - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "zero123" in file.lower()] - - return {"required": { - "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - - "init_image": ("IMAGE",), - "empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), - - "elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}), - "azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, elevation, azimuth, prompt=None, my_unique_id=None): - model: ModelPatcher | None = None - vae: VAE | None = None - clip: CLIP | None = None - clip_vision = None - - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) - - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, -1) - encode_pixels = pixels[:, :, :, :3] - t = vae.encode(encode_pixels) - cam_embeds = camera_embeddings(elevation, azimuth) - cond = torch.cat([pooled, cam_embeds.repeat((pooled.shape[0], 1, 1))], dim=-1) - - positive = [[cond, {"concat_latent_image": t}]] - negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]] - latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]) - samples = {"samples": latent} - - image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) - - pipe = {"model": model, - "positive": positive, - "negative": negative, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": image, - "seed": 0, - - "loader_settings": {"ckpt_name": ckpt_name, - "vae_name": vae_name, - - "positive": positive, - "negative": negative, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": batch_size, - "seed": 0, - } - } - - return (pipe, model, vae) - -# SV3D加载器 -class sv3DLoader(EasingBase): - - def __init__(self): - super().__init__() - - @classmethod - def INPUT_TYPES(cls): - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "sv3d" in file] - - return {"required": { - "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - - "init_image": ("IMAGE",), - "empty_latent_width": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - - "batch_size": ("INT", {"default": 21, "min": 1, "max": 4096}), - "interp_easing": (["linear", "ease_in", "ease_out", "ease_in_out"], {"default": "linear"}), - "easing_mode": (["azimuth", "elevation", "custom"], {"default": "azimuth"}), - }, - "optional": {"scheduler": ("STRING", {"default": "", "multiline": True})}, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "STRING") - RETURN_NAMES = ("pipe", "model", "interp_log") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, interp_easing, easing_mode, scheduler='',prompt=None, my_unique_id=None): - model: ModelPatcher | None = None - vae: VAE | None = None - clip: CLIP | None = None - - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) - - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, - -1) - encode_pixels = pixels[:, :, :, :3] - t = vae.encode(encode_pixels) - - azimuth_points = [] - elevation_points = [] - if easing_mode == 'azimuth': - azimuth_points = [(0, 0), (batch_size-1, 360)] - elevation_points = [(0, 0)] * batch_size - elif easing_mode == 'elevation': - azimuth_points = [(0, 0)] * batch_size - elevation_points = [(0, -90), (batch_size-1, 90)] - else: - schedulers = scheduler.rstrip('\n') - for line in schedulers.split('\n'): - frame_str, point_str = line.split(':') - point_str = point_str.strip()[1:-1] - point = point_str.split(',') - azimuth_point = point[0] - elevation_point = point[1] if point[1] else 0.0 - frame = int(frame_str.strip()) - azimuth = float(azimuth_point) - azimuth_points.append((frame, azimuth)) - elevation_val = float(elevation_point) - elevation_points.append((frame, elevation_val)) - azimuth_points.sort(key=lambda x: x[0]) - elevation_points.sort(key=lambda x: x[0]) - - #interpolation - next_point = 1 - next_elevation_point = 1 - elevations = [] - azimuths = [] - # For azimuth interpolation - for i in range(batch_size): - # Find the interpolated azimuth for the current frame - while next_point < len(azimuth_points) and i >= azimuth_points[next_point][0]: - next_point += 1 - if next_point == len(azimuth_points): - next_point -= 1 - prev_point = max(next_point - 1, 0) - - if azimuth_points[next_point][0] != azimuth_points[prev_point][0]: - timing = (i - azimuth_points[prev_point][0]) / ( - azimuth_points[next_point][0] - azimuth_points[prev_point][0]) - interpolated_azimuth = self.ease(azimuth_points[prev_point][1], azimuth_points[next_point][1], self.easing(timing, interp_easing)) - else: - interpolated_azimuth = azimuth_points[prev_point][1] - - # Interpolate the elevation - next_elevation_point = 1 - while next_elevation_point < len(elevation_points) and i >= elevation_points[next_elevation_point][0]: - next_elevation_point += 1 - if next_elevation_point == len(elevation_points): - next_elevation_point -= 1 - prev_elevation_point = max(next_elevation_point - 1, 0) - - if elevation_points[next_elevation_point][0] != elevation_points[prev_elevation_point][0]: - timing = (i - elevation_points[prev_elevation_point][0]) / ( - elevation_points[next_elevation_point][0] - elevation_points[prev_elevation_point][0]) - interpolated_elevation = self.ease(elevation_points[prev_point][1], elevation_points[next_point][1], self.easing(timing, interp_easing)) - else: - interpolated_elevation = elevation_points[prev_elevation_point][1] - - azimuths.append(interpolated_azimuth) - elevations.append(interpolated_elevation) - - log_node_info("easy sv3dLoader", "azimuths:" + str(azimuths)) - log_node_info("easy sv3dLoader", "elevations:" + str(elevations)) - - log = 'azimuths:' + str(azimuths) + '\n\n' + "elevations:" + str(elevations) - # Structure the final output - positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]] - negative = [[torch.zeros_like(pooled), - {"concat_latent_image": torch.zeros_like(t), "elevation": elevations, "azimuth": azimuths}]] - - latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]) - samples = {"samples": latent} - - image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) - - - pipe = {"model": model, - "positive": positive, - "negative": negative, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": image, - "seed": 0, - - "loader_settings": {"ckpt_name": ckpt_name, - "vae_name": vae_name, - - "positive": positive, - "negative": negative, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": batch_size, - "seed": 0, - } - } - - return (pipe, model, log) - -#svd加载器 -class svdLoader: - - @classmethod - def INPUT_TYPES(cls): - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "svd" in file.lower()] - - return {"required": { - "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - "clip_name": (["None"] + folder_paths.get_filename_list("clip"),), - - "init_image": ("IMAGE",), - "resolution": (resolution_strings, {"default": "1024 x 576"}), - "empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - - "video_frames": ("INT", {"default": 14, "min": 1, "max": 4096}), - "motion_bucket_id": ("INT", {"default": 127, "min": 1, "max": 1023}), - "fps": ("INT", {"default": 6, "min": 1, "max": 1024}), - "augmentation_level": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01}) - }, - "optional": { - "optional_positive": ("STRING", {"default": "", "multiline": True}), - "optional_negative": ("STRING", {"default": "", "multiline": True}), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def adv_pipeloader(self, ckpt_name, vae_name, clip_name, init_image, resolution, empty_latent_width, empty_latent_height, video_frames, motion_bucket_id, fps, augmentation_level, optional_positive=None, optional_negative=None, prompt=None, my_unique_id=None): - model: ModelPatcher | None = None - vae: VAE | None = None - clip: CLIP | None = None - clip_vision = None - - # resolution - if resolution != "自定义 x 自定义": - try: - width, height = map(int, resolution.split(' x ')) - empty_latent_width = width - empty_latent_height = height - except ValueError: - raise ValueError("Invalid base_resolution format.") - - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) - - output = clip_vision.encode_image(init_image) - pooled = output.image_embeds.unsqueeze(0) - pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, -1) - encode_pixels = pixels[:, :, :, :3] - if augmentation_level > 0: - encode_pixels += torch.randn_like(pixels) * augmentation_level - t = vae.encode(encode_pixels) - positive = [[pooled, - {"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level, - "concat_latent_image": t}]] - negative = [[torch.zeros_like(pooled), - {"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level, - "concat_latent_image": torch.zeros_like(t)}]] - if optional_positive is not None and optional_positive != '': - if clip_name == 'None': - raise Exception("You need choose a open_clip model when positive is not empty") - clip = easyCache.load_clip(clip_name) - if has_chinese(optional_positive): - optional_positive = zh_to_en([optional_positive])[0] - positive_embeddings_final, = CLIPTextEncode().encode(clip, optional_positive) - positive, = ConditioningConcat().concat(positive, positive_embeddings_final) - if optional_negative is not None and optional_negative != '': - if clip_name == 'None': - raise Exception("You need choose a open_clip model when negative is not empty") - if has_chinese(optional_negative): - optional_positive = zh_to_en([optional_negative])[0] - negative_embeddings_final, = CLIPTextEncode().encode(clip, optional_negative) - negative, = ConditioningConcat().concat(negative, negative_embeddings_final) - - latent = torch.zeros([video_frames, 4, empty_latent_height // 8, empty_latent_width // 8]) - samples = {"samples": latent} - - image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) - - pipe = {"model": model, - "positive": positive, - "negative": negative, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": image, - "seed": 0, - - "loader_settings": {"ckpt_name": ckpt_name, - "vae_name": vae_name, - - "positive": positive, - "negative": negative, - "resolution": resolution, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": 1, - "seed": 0, - } - } - - return (pipe, model, vae) - -#dynamiCrafter加载器 -from .dynamiCrafter import DynamiCrafter -class dynamiCrafterLoader(DynamiCrafter): - - def __init__(self): - super().__init__() - - @classmethod - def INPUT_TYPES(cls): - - return {"required": { - "model_name": (list(DYNAMICRAFTER_MODELS.keys()),), - "clip_skip": ("INT", {"default": -2, "min": -24, "max": 0, "step": 1}), - - "init_image": ("IMAGE",), - "resolution": (resolution_strings, {"default": "512 x 512"}), - "empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "multiline": True}), - "negative": ("STRING", {"default": "", "multiline": True}), - - "use_interpolate": ("BOOLEAN", {"default": False}), - "fps": ("INT", {"default": 15, "min": 1, "max": 30, "step": 1},), - "frames": ("INT", {"default": 16}), - "scale_latents": ("BOOLEAN", {"default": False}) - }, - "optional": { - "optional_vae": ("VAE",), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def get_clip_file(self, node_name): - clip_list = folder_paths.get_filename_list("clip") - pattern = 'sd2-1-open-clip|model.(safetensors|bin)$' - clip_files = [e for e in clip_list if re.search(pattern, e, re.IGNORECASE)] - - clip_name = clip_files[0] if len(clip_files)>0 else None - clip_file = folder_paths.get_full_path("clip", clip_name) if clip_name else None - if clip_name is not None: - log_node_info(node_name, f"Using {clip_name}") - - return clip_file, clip_name - - def get_clipvision_file(self, node_name): - clipvision_list = folder_paths.get_filename_list("clip_vision") - pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model|open_clip_pytorch_model.(bin|safetensors))' - clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)] - - clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None - clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None - if clipvision_name is not None: - log_node_info(node_name, f"Using {clipvision_name}") - - return clipvision_file, clipvision_name - - def get_vae_file(self, node_name): - vae_list = folder_paths.get_filename_list("vae") - pattern = 'vae-ft-mse-840000-ema-pruned.(pt|bin|safetensors)$' - vae_files = [e for e in vae_list if re.search(pattern, e, re.IGNORECASE)] - - vae_name = vae_files[0] if len(vae_files)>0 else None - vae_file = folder_paths.get_full_path("vae", vae_name) if vae_name else None - if vae_name is not None: - log_node_info(node_name, f"Using {vae_name}") - - return vae_file, vae_name - - def adv_pipeloader(self, model_name, clip_skip, init_image, resolution, empty_latent_width, empty_latent_height, positive, negative, use_interpolate, fps, frames, scale_latents, optional_vae=None, prompt=None, my_unique_id=None): - positive_embeddings_final, negative_embeddings_final = None, None - # resolution - if resolution != "自定义 x 自定义": - try: - width, height = map(int, resolution.split(' x ')) - empty_latent_width = width - empty_latent_height = height - except ValueError: - raise ValueError("Invalid base_resolution format.") - - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - models_0 = list(DYNAMICRAFTER_MODELS.keys())[0] - - if optional_vae: - vae = optional_vae - vae_name = None - else: - vae_file, vae_name = self.get_vae_file("easy dynamiCrafterLoader") - if vae_file is None: - vae_name = "vae-ft-mse-840000-ema-pruned.safetensors" - get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['vae_url'], os.path.join(folder_paths.models_dir, "vae"), - vae_name, '/stable-diffusion-cache/models/VAE') - vae = easyCache.load_vae(vae_name) - - clip_file, clip_name = self.get_clip_file("easy dynamiCrafterLoader") - if clip_file is None: - clip_name = 'sd2-1-open-clip.safetensors' - get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['clip_url'], os.path.join(folder_paths.models_dir, "clip"), - clip_name, cache_dir='/stable-diffusion-cache/models/clip') - - clip = easyCache.load_clip(clip_name) - # load clip vision - clip_vision_file, clip_vision_name = self.get_clipvision_file("easy dynamiCrafterLoader") - if clip_vision_file is None: - clip_vision_name = 'CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors' - clip_vision_file = get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['clip_vision_url'], os.path.join(folder_paths.models_dir, "clip_vision"), - clip_vision_name, cache_dir='/stable-diffusion-cache/models/annotator/clip_vision') - clip_vision = load_clip_vision(clip_vision_file) - # load unet model - model_path = get_local_filepath(DYNAMICRAFTER_MODELS[model_name]['model_url'], DYNAMICRAFTER_DIR, cache_dir='/stable-diffusion-cache/models/dynamiccraft') - model_patcher, image_proj_model = self.load_dynamicrafter(model_path) - - # apply - model, empty_latent, image_latent = self.process_image_conditioning(model_patcher, clip_vision, vae, image_proj_model, init_image, use_interpolate, fps, frames, scale_latents) - - clipped = clip.clone() - if clip_skip != 0: - clipped.clip_layer(clip_skip) - - if positive is not None and positive != '': - if has_chinese(positive): - positive = zh_to_en([positive])[0] - positive_embeddings_final, = CLIPTextEncode().encode(clipped, positive) - if negative is not None and negative != '': - if has_chinese(negative): - negative = zh_to_en([negative])[0] - negative_embeddings_final, = CLIPTextEncode().encode(clipped, negative) - - image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) - - pipe = {"model": model, - "positive": positive_embeddings_final, - "negative": negative_embeddings_final, - "vae": vae, - "clip": clip, - "clip_vision": clip_vision, - - "samples": empty_latent, - "images": image, - "seed": 0, - - "loader_settings": {"ckpt_name": model_name, - "vae_name": vae_name, - - "positive": positive, - "negative": negative, - "resolution": resolution, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": 1, - "seed": 0, - } - } - - return (pipe, model, vae) - -# kolors Loader -from .kolors.text_encode import chatglm3_adv_text_encode -class kolorsLoader: - - @classmethod - def INPUT_TYPES(cls): - return { - "required":{ - "unet_name": (folder_paths.get_filename_list("unet"),), - "vae_name": (folder_paths.get_filename_list("vae"),), - "chatglm3_name": (folder_paths.get_filename_list("llm"),), - "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "resolution": (resolution_strings, {"default": "1024 x 576"}), - "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), - }, - "optional": { - "model_override": ("MODEL",), - "vae_override": ("VAE",), - "optional_lora_stack": ("LORA_STACK",), - "auto_clean_gpu": ("BOOLEAN", {"default": False}), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "adv_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def adv_pipeloader(self, unet_name, vae_name, chatglm3_name, lora_name, lora_model_strength, lora_clip_strength, resolution, empty_latent_width, empty_latent_height, positive, negative, batch_size, model_override=None, optional_lora_stack=None, vae_override=None, auto_clean_gpu=False, prompt=None, my_unique_id=None): - # load unet - if model_override: - model = model_override - else: - model = easyCache.load_kolors_unet(unet_name) - # load vae - if vae_override: - vae = vae_override - else: - vae = easyCache.load_vae(vae_name) - # load chatglm3 - chatglm3_model = easyCache.load_chatglm3(chatglm3_name) - # load lora - lora_stack = [] - if optional_lora_stack is not None: - for lora in optional_lora_stack: - lora = {"lora_name": lora[0], "model": model, "clip": None, "model_strength": lora[1], - "clip_strength": lora[2]} - model, _ = easyCache.load_lora(lora) - lora['model'] = model - lora['clip'] = None - lora_stack.append(lora) - - if lora_name != "None": - lora = {"lora_name": lora_name, "model": model, "clip": None, "model_strength": lora_model_strength, - "clip_strength": lora_clip_strength} - model, _ = easyCache.load_lora(lora) - lora_stack.append(lora) - - - # text encode - log_node_warn("Positive encoding...") - positive_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, positive, auto_clean_gpu) - log_node_warn("Negative encoding...") - negative_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, negative, auto_clean_gpu) - - # empty latent - samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size) - - pipe = { - "model": model, - "chatglm3_model": chatglm3_model, - "positive": positive_embeddings_final, - "negative": negative_embeddings_final, - "vae": vae, - "clip": None, - - "samples": samples, - "images": None, - - "loader_settings": { - "unet_name": unet_name, - "vae_name": vae_name, - "chatglm3_name": chatglm3_name, - - "lora_name": lora_name, - "lora_model_strength": lora_model_strength, - "lora_clip_strength": lora_clip_strength, - - "positive": positive, - "negative": negative, - "resolution": resolution, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": batch_size, - "auto_clean_gpu": auto_clean_gpu, - } - } - - return {"ui": {}, - "result": (pipe, model, vae, chatglm3_model, positive_embeddings_final, negative_embeddings_final, samples)} - - - return (chatglm3_model, None, None) - -# Flux Loader -class fluxLoader(fullLoader): - @classmethod - def INPUT_TYPES(cls): - checkpoints = folder_paths.get_filename_list("checkpoints") - loras = ["None"] + folder_paths.get_filename_list("loras") - return { - "required": { - "ckpt_name": (checkpoints,), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), - "lora_name": (loras,), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - "resolution": (resolution_strings, {"default": "1024 x 1024"}), - "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), - }, - "optional": { - "model_override": ("MODEL",), - "clip_override": ("CLIP",), - "vae_override": ("VAE",), - "optional_lora_stack": ("LORA_STACK",), - "optional_controlnet_stack": ("CONTROL_NET_STACK",), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "fluxloader" - CATEGORY = "EasyUse/Loaders" - - def fluxloader(self, ckpt_name, vae_name, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, optional_controlnet_stack=None, - a1111_prompt_style=False, prompt=None, - my_unique_id=None): - - if positive == '': - positive = None - - return super().adv_pipeloader(ckpt_name, 'Default', vae_name, 0, - lora_name, lora_model_strength, lora_clip_strength, - resolution, empty_latent_width, empty_latent_height, - positive, 'none', 'comfy', - None, 'none', 'comfy', - batch_size, model_override, clip_override, vae_override, optional_lora_stack=optional_lora_stack, - optional_controlnet_stack=optional_controlnet_stack, - a1111_prompt_style=a1111_prompt_style, prompt=prompt, - my_unique_id=my_unique_id) - - -# Dit Loader -from .dit.pixArt.config import pixart_conf, pixart_res - -class pixArtLoader: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), - "model_name":(list(pixart_conf.keys()),), - "vae_name": (folder_paths.get_filename_list("vae"),), - "t5_type": (['sd3'],), - "clip_name": (folder_paths.get_filename_list("clip"),), - "padding": ("INT", {"default": 1, "min": 1, "max": 300}), - "t5_name": (folder_paths.get_filename_list("t5"),), - "device": (["auto", "cpu", "gpu"], {"default": "cpu"}), - "dtype": (["default", "auto (comfy)", "FP32", "FP16", "BF16"],), - - "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), - "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), - - "ratio": (["custom"] + list(pixart_res["PixArtMS_XL_2"].keys()), {"default":"1.00"}), - "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), - - "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), - }, - "optional":{ - "optional_lora_stack": ("LORA_STACK",), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - FUNCTION = "pixart_pipeloader" - CATEGORY = "EasyUse/Loaders" - - def pixart_pipeloader(self, ckpt_name, model_name, vae_name, t5_type, clip_name, padding, t5_name, device, dtype, lora_name, lora_model_strength, ratio, empty_latent_width, empty_latent_height, positive, negative, batch_size, optional_lora_stack=None, prompt=None, my_unique_id=None): - # Clean models from loaded_objects - easyCache.update_loaded_objects(prompt) - - # load checkpoint - model = easyCache.load_dit_ckpt(ckpt_name=ckpt_name, model_name=model_name, pixart_conf=pixart_conf, - model_type='PixArt') - # load vae - vae = easyCache.load_vae(vae_name) - - # load t5 - if t5_type == 'sd3': - clip = easyCache.load_clip(clip_name=clip_name,type='sd3') - clip = easyCache.load_t5_from_sd3_clip(sd3_clip=clip, padding=padding) - lora_stack = None - if optional_lora_stack is not None: - for lora in optional_lora_stack: - lora = {"lora_name": lora[0], "model": model, "clip": clip, "model_strength": lora[1], - "clip_strength": lora[2]} - model, _ = easyCache.load_lora(lora, type='PixArt') - lora['model'] = model - lora['clip'] = clip - lora_stack.append(lora) - - if lora_name != "None": - lora = {"lora_name": lora_name, "model": model, "clip": clip, "model_strength": lora_model_strength, - "clip_strength": 1} - model, _ = easyCache.load_lora(lora, type='PixArt') - lora_stack.append(lora) - - positive_embeddings_final, = CLIPTextEncode().encode(clip, positive) - negative_embeddings_final, = CLIPTextEncode().encode(clip, negative) - else: - # todo t5v11 - positive_embeddings_final, negative_embeddings_final = None, None - clip = None - pass - - # Create Empty Latent - if ratio != 'custom': - if model_name in ['ControlPixArtMSHalf','PixArtMS_Sigma_XL_2_900M']: - res_name = 'PixArtMS_XL_2' - elif model_name in ['ControlPixArtHalf']: - res_name = 'PixArt_XL_2' - else: - res_name = model_name - width, height = pixart_res[res_name][ratio] - empty_latent_width = width - empty_latent_height = height - - latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8], device=sampler.device) - samples = {"samples": latent} - - log_node_warn("加载完毕...") - pipe = { - "model": model, - "positive": positive_embeddings_final, - "negative": negative_embeddings_final, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": None, - - "loader_settings": { - "ckpt_name": ckpt_name, - "clip_name": clip_name, - "vae_name": vae_name, - "t5_name": t5_name, - - "positive": positive, - "negative": negative, - "ratio": ratio, - "empty_latent_width": empty_latent_width, - "empty_latent_height": empty_latent_height, - "batch_size": batch_size, - } - } - - return {"ui": {}, - "result": (pipe, model, vae, clip, positive_embeddings_final, negative_embeddings_final, samples)} - - -# Mochi加载器 -class mochiLoader(fullLoader): - @classmethod - def INPUT_TYPES(cls): - checkpoints = folder_paths.get_filename_list("checkpoints") - return { - "required": { - "ckpt_name": (checkpoints,), - "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"), {"default": "mochi_vae.safetensors"}), - - "positive": ("STRING", {"default":"", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default":"", "placeholder": "Negative", "multiline": True}), - - "resolution": (resolution_strings, {"default": "width x height (custom)"}), - "empty_latent_width": ("INT", {"default": 848, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "empty_latent_height": ("INT", {"default": 480, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "length": ("INT", {"default": 25, "min": 7, "max": MAX_RESOLUTION, "step": 6}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) - }, - "optional": { - "model_override": ("MODEL",), "clip_override": ("CLIP",), "vae_override": ("VAE",), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") - RETURN_NAMES = ("pipe", "model", "vae") - - FUNCTION = "mochiLoader" - CATEGORY = "EasyUse/Loaders" - - def mochiLoader(self, ckpt_name, vae_name, - positive, negative, - resolution, empty_latent_width, empty_latent_height, - length, batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, optional_controlnet_stack=None, a1111_prompt_style=False, prompt=None, - my_unique_id=None): - - return super().adv_pipeloader(ckpt_name, 'Default', vae_name, 0, - "None", 1.0, 1.0, - resolution, empty_latent_width, empty_latent_height, - positive, 'none', 'comfy', - negative,'none','comfy', - batch_size, model_override, clip_override, vae_override, a1111_prompt_style=False, video_length=length, prompt=prompt, - my_unique_id=my_unique_id - ) -# lora -class loraStack: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - max_lora_num = 10 - inputs = { - "required": { - "toggle": ("BOOLEAN", {"label_on": "enabled", "label_off": "disabled"}), - "mode": (["simple", "advanced"],), - "num_loras": ("INT", {"default": 1, "min": 1, "max": max_lora_num}), - }, - "optional": { - "optional_lora_stack": ("LORA_STACK",), - }, - } - - for i in range(1, max_lora_num+1): - inputs["optional"][f"lora_{i}_name"] = ( - ["None"] + folder_paths.get_filename_list("loras"), {"default": "None"}) - inputs["optional"][f"lora_{i}_strength"] = ( - "FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) - inputs["optional"][f"lora_{i}_model_strength"] = ( - "FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) - inputs["optional"][f"lora_{i}_clip_strength"] = ( - "FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) - - return inputs - - RETURN_TYPES = ("LORA_STACK",) - RETURN_NAMES = ("lora_stack",) - FUNCTION = "stack" - - CATEGORY = "EasyUse/Loaders" - - def stack(self, toggle, mode, num_loras, optional_lora_stack=None, **kwargs): - if (toggle in [False, None, "False"]) or not kwargs: - return (None,) - - loras = [] - - # Import Stack values - if optional_lora_stack is not None: - loras.extend([l for l in optional_lora_stack if l[0] != "None"]) - - # Import Lora values - for i in range(1, num_loras + 1): - lora_name = kwargs.get(f"lora_{i}_name") - - if not lora_name or lora_name == "None": - continue - - if mode == "simple": - lora_strength = float(kwargs.get(f"lora_{i}_strength")) - loras.append((lora_name, lora_strength, lora_strength)) - elif mode == "advanced": - model_strength = float(kwargs.get(f"lora_{i}_model_strength")) - clip_strength = float(kwargs.get(f"lora_{i}_clip_strength")) - loras.append((lora_name, model_strength, clip_strength)) - return (loras,) - -class controlnetStack: - - - @classmethod - def INPUT_TYPES(s): - max_cn_num = 3 - inputs = { - "required": { - "toggle": ("BOOLEAN", {"label_on": "enabled", "label_off": "disabled"}), - "mode": (["simple", "advanced"],), - "num_controlnet": ("INT", {"default": 1, "min": 1, "max": max_cn_num}), - }, - "optional": { - "optional_controlnet_stack": ("CONTROL_NET_STACK",), - } - } - - for i in range(1, max_cn_num+1): - inputs["optional"][f"controlnet_{i}"] = (["None"] + folder_paths.get_filename_list("controlnet"), {"default": "None"}) - inputs["optional"][f"controlnet_{i}_strength"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01},) - inputs["optional"][f"start_percent_{i}"] = ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},) - inputs["optional"][f"end_percent_{i}"] = ("FLOAT",{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},) - inputs["optional"][f"scale_soft_weight_{i}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},) - inputs["optional"][f"image_{i}"] = ("IMAGE",) - return inputs - - RETURN_TYPES = ("CONTROL_NET_STACK",) - RETURN_NAMES = ("controlnet_stack",) - FUNCTION = "stack" - CATEGORY = "EasyUse/Loaders" - - def stack(self, toggle, mode, num_controlnet, optional_controlnet_stack=None, **kwargs): - if (toggle in [False, None, "False"]) or not kwargs: - return (None,) - - controlnets = [] - - # Import Stack values - if optional_controlnet_stack is not None: - controlnets.extend([l for l in optional_controlnet_stack if l[0] != "None"]) - - # Import Controlnet values - for i in range(1, num_controlnet+1): - controlnet_name = kwargs.get(f"controlnet_{i}") - - if not controlnet_name or controlnet_name == "None": - continue - - controlnet_strength = float(kwargs.get(f"controlnet_{i}_strength")) - start_percent = float(kwargs.get(f"start_percent_{i}")) if mode == "advanced" else 0 - end_percent = float(kwargs.get(f"end_percent_{i}")) if mode == "advanced" else 1.0 - scale_soft_weights = float(kwargs.get(f"scale_soft_weight_{i}")) - image = kwargs.get(f"image_{i}") - - controlnets.append((controlnet_name, controlnet_strength, start_percent, end_percent, scale_soft_weights, image, True)) - - return (controlnets,) -# controlnet -class controlnetSimple: - @classmethod - def INPUT_TYPES(s): - - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "control_net_name": (folder_paths.get_filename_list("controlnet"),), - }, - "optional": { - "control_net": ("CONTROL_NET",), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), - } - } - - RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("pipe", "positive", "negative") - - FUNCTION = "controlnetApply" - CATEGORY = "EasyUse/Loaders" - - def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1, union_type=None): - - positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights, mask=None, easyCache=easyCache, model=pipe['model'], vae=pipe['vae']) - - new_pipe = { - "model": pipe['model'], - "positive": positive, - "negative": negative, - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": pipe["samples"], - "images": pipe["images"], - "seed": 0, - - "loader_settings": pipe["loader_settings"] - } - - del pipe - return (new_pipe, positive, negative) - -# controlnetADV -class controlnetAdvanced: - - @classmethod - def INPUT_TYPES(s): - - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "control_net_name": (folder_paths.get_filename_list("controlnet"),), - }, - "optional": { - "control_net": ("CONTROL_NET",), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), - } - } - - RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("pipe", "positive", "negative") - - FUNCTION = "controlnetApply" - CATEGORY = "EasyUse/Loaders" - - - def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1): - positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], - strength, start_percent, end_percent, control_net, scale_soft_weights, union_type=None, mask=None, easyCache=easyCache, model=pipe['model'], vae=pipe['vae']) - - new_pipe = { - "model": pipe['model'], - "positive": positive, - "negative": negative, - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": pipe["samples"], - "images": image, - "seed": 0, - - "loader_settings": pipe["loader_settings"] - } - - del pipe - - return (new_pipe, positive, negative) - -# controlnetPlusPlus -class controlnetPlusPlus: - - @classmethod - def INPUT_TYPES(s): - - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "control_net_name": (folder_paths.get_filename_list("controlnet"),), - }, - "optional": { - "control_net": ("CONTROL_NET",), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), - "union_type": (list(union_controlnet_types.keys()),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("pipe", "positive", "negative") - - FUNCTION = "controlnetApply" - CATEGORY = "EasyUse/Loaders" - - - def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1, union_type=None): - if scale_soft_weights < 1: - if "ScaledSoftControlNetWeights" in ALL_NODE_CLASS_MAPPINGS: - soft_weight_cls = ALL_NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights'] - (weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False) - cn_adv_cls = ALL_NODE_CLASS_MAPPINGS['ACN_ControlNet++LoaderSingle'] - if union_type == 'auto': - union_type = 'none' - elif union_type == 'canny/lineart/anime_lineart/mlsd': - union_type = 'canny/lineart/mlsd' - elif union_type == 'repaint': - union_type = 'inpaint/outpaint' - control_net, = cn_adv_cls().load_controlnet_plusplus(control_net_name, union_type) - apply_adv_cls = ALL_NODE_CLASS_MAPPINGS['ACN_AdvancedControlNetApply'] - positive, negative, _ = apply_adv_cls().apply_controlnet(pipe["positive"], pipe["negative"], control_net, image, strength, start_percent, end_percent, timestep_kf=timestep_keyframe,) - else: - raise Exception( - f"[Advanced-ControlNet Not Found] you need to install 'COMFYUI-Advanced-ControlNet'") - else: - positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], - strength, start_percent, end_percent, control_net, scale_soft_weights, union_type=union_type, mask=None, easyCache=easyCache, model=pipe['model']) - - new_pipe = { - "model": pipe['model'], - "positive": positive, - "negative": negative, - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": pipe["samples"], - "images": pipe["images"], - "seed": 0, - - "loader_settings": pipe["loader_settings"] - } - - del pipe - - return (new_pipe, positive, negative) - -# LLLiteLoader -from .libs.lllite import load_control_net_lllite_patch -class LLLiteLoader: - def __init__(self): - pass - @classmethod - def INPUT_TYPES(s): - def get_file_list(filenames): - return [file for file in filenames if file != "put_models_here.txt" and "lllite" in file] - - return { - "required": { - "model": ("MODEL",), - "model_name": (get_file_list(folder_paths.get_filename_list("controlnet")),), - "cond_image": ("IMAGE",), - "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "steps": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}), - "end_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}), - } - } - - RETURN_TYPES = ("MODEL",) - FUNCTION = "load_lllite" - CATEGORY = "EasyUse/Loaders" - - def load_lllite(self, model, model_name, cond_image, strength, steps, start_percent, end_percent): - # cond_image is b,h,w,3, 0-1 - - model_path = os.path.join(folder_paths.get_full_path("controlnet", model_name)) - - model_lllite = model.clone() - patch = load_control_net_lllite_patch(model_path, cond_image, strength, steps, start_percent, end_percent) - if patch is not None: - model_lllite.set_model_attn1_patch(patch) - model_lllite.set_model_attn2_patch(patch) - - return (model_lllite,) - -# ---------------------------------------------------------------加载器 结束----------------------------------------------------------------------# - -#---------------------------------------------------------------Inpaint 开始----------------------------------------------------------------------# -# FooocusInpaint -class applyFooocusInpaint: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "latent": ("LATENT",), - "head": (list(FOOOCUS_INPAINT_HEAD.keys()),), - "patch": (list(FOOOCUS_INPAINT_PATCH.keys()),), - }, - } - - RETURN_TYPES = ("MODEL",) - RETURN_NAMES = ("model",) - CATEGORY = "EasyUse/Inpaint" - FUNCTION = "apply" - - def apply(self, model, latent, head, patch): - from .fooocus import InpaintHead, InpaintWorker - head_file = get_local_filepath(FOOOCUS_INPAINT_HEAD[head]["model_url"], INPAINT_DIR, cache_dir='/stable-diffusion-cache/models/inpaint') - inpaint_head_model = InpaintHead() - sd = torch.load(head_file, map_location='cpu') - inpaint_head_model.load_state_dict(sd) - - patch_file = get_local_filepath(FOOOCUS_INPAINT_PATCH[patch]["model_url"], INPAINT_DIR, cache_dir='/stable-diffusion-cache/models/inpaint') - inpaint_lora = comfy.utils.load_torch_file(patch_file, safe_load=True) - - patch = (inpaint_head_model, inpaint_lora) - worker = InpaintWorker(node_name="easy kSamplerInpainting") - cloned = model.clone() - - m, = worker.patch(cloned, latent, patch) - return (m,) - -# brushnet -from .brushnet import BrushNet -class applyBrushNet: - - def get_files_with_extension(folder='inpaint', extensions='.safetensors'): - return [file for file in folder_paths.get_filename_list(folder) if file.endswith(extensions)] - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "mask": ("MASK",), - "brushnet": (s.get_files_with_extension(),), - "dtype": (['float16', 'bfloat16', 'float32', 'float64'], ), - "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), - "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), - "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), - }, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - CATEGORY = "EasyUse/Inpaint" - FUNCTION = "apply" - - def apply(self, pipe, image, mask, brushnet, dtype, scale, start_at, end_at): - - model = pipe['model'] - vae = pipe['vae'] - positive = pipe['positive'] - negative = pipe['negative'] - cls = BrushNet() - if brushnet in backend_cache.cache: - log_node_info("easy brushnetApply", f"Using {brushnet} Cached") - _, brushnet_model = backend_cache.cache[brushnet][1] - else: - brushnet_file = os.path.join(folder_paths.get_full_path("inpaint", brushnet)) - brushnet_model, = cls.load_brushnet_model(brushnet_file, dtype) - backend_cache.update_cache(brushnet, 'brushnet', (False, brushnet_model)) - m, positive, negative, latent = cls.brushnet_model_update(model=model, vae=vae, image=image, mask=mask, - brushnet=brushnet_model, positive=positive, - negative=negative, scale=scale, start_at=start_at, - end_at=end_at) - new_pipe = { - **pipe, - "model": m, - "positive": positive, - "negative": negative, - "samples": latent, - } - del pipe - return (new_pipe,) - -# #powerpaint -class applyPowerPaint: - def get_files_with_extension(folder='inpaint', extensions='.safetensors'): - return [file for file in folder_paths.get_filename_list(folder) if file.endswith(extensions)] - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "mask": ("MASK",), - "powerpaint_model": (s.get_files_with_extension(),), - "powerpaint_clip": (s.get_files_with_extension(extensions='.bin'),), - "dtype": (['float16', 'bfloat16', 'float32', 'float64'],), - "fitting": ("FLOAT", {"default": 1.0, "min": 0.3, "max": 1.0}), - "function": (['text guided', 'shape guided', 'object removal', 'context aware', 'image outpainting'],), - "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), - "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), - "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), - "save_memory": (['none', 'auto', 'max'],), - }, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - CATEGORY = "EasyUse/Inpaint" - FUNCTION = "apply" - - def apply(self, pipe, image, mask, powerpaint_model, powerpaint_clip, dtype, fitting, function, scale, start_at, end_at, save_memory='none'): - model = pipe['model'] - vae = pipe['vae'] - positive = pipe['positive'] - negative = pipe['negative'] - - cls = BrushNet() - # load powerpaint clip - if powerpaint_clip in backend_cache.cache: - log_node_info("easy powerpaintApply", f"Using {powerpaint_clip} Cached") - _, ppclip = backend_cache.cache[powerpaint_clip][1] - else: - model_url = POWERPAINT_MODELS['base_fp16']['model_url'] - base_clip = get_local_filepath(model_url, os.path.join(folder_paths.models_dir, 'clip'), cache_dir='/stable-diffusion-cache/models/clip') - ppclip, = cls.load_powerpaint_clip(base_clip, os.path.join(folder_paths.get_full_path("inpaint", powerpaint_clip))) - backend_cache.update_cache(powerpaint_clip, 'ppclip', (False, ppclip)) - # load powerpaint model - if powerpaint_model in backend_cache.cache: - log_node_info("easy powerpaintApply", f"Using {powerpaint_model} Cached") - _, powerpaint = backend_cache.cache[powerpaint_model][1] - else: - powerpaint_file = os.path.join(folder_paths.get_full_path("inpaint", powerpaint_model)) - powerpaint, = cls.load_brushnet_model(powerpaint_file, dtype) - backend_cache.update_cache(powerpaint_model, 'powerpaint', (False, powerpaint)) - m, positive, negative, latent = cls.powerpaint_model_update(model=model, vae=vae, image=image, mask=mask, powerpaint=powerpaint, - clip=ppclip, positive=positive, - negative=negative, fitting=fitting, function=function, - scale=scale, start_at=start_at, end_at=end_at, save_memory=save_memory) - new_pipe = { - **pipe, - "model": m, - "positive": positive, - "negative": negative, - "samples": latent, - } - del pipe - return (new_pipe,) - -class applyInpaint: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "mask": ("MASK",), - "inpaint_mode": (('normal', 'fooocus_inpaint', 'brushnet_random', 'brushnet_segmentation', 'powerpaint'),), - "encode": (('none', 'vae_encode_inpaint', 'inpaint_model_conditioning', 'different_diffusion'), {"default": "none"}), - "grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}), - "dtype": (['float16', 'bfloat16', 'float32', 'float64'],), - "fitting": ("FLOAT", {"default": 1.0, "min": 0.3, "max": 1.0}), - "function": (['text guided', 'shape guided', 'object removal', 'context aware', 'image outpainting'],), - "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), - "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), - "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), - }, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - CATEGORY = "EasyUse/Inpaint" - FUNCTION = "apply" - - def inpaint_model_conditioning(self, pipe, image, vae, mask, grow_mask_by): - if grow_mask_by >0: - mask, = GrowMask().expand_mask(mask, grow_mask_by, False) - positive, negative, latent = InpaintModelConditioning().encode(pipe['positive'], pipe['negative'], image, - vae, mask) - pipe['positive'] = positive - pipe['negative'] = negative - pipe['samples'] = latent - - return pipe - - def get_brushnet_model(self, type, model): - model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1' - if type == 'brushnet_random': - brush_model = BRUSHNET_MODELS['random_mask'][model_type]['model_url'] - if model_type == 'sdxl': - pattern = 'brushnet.random.mask.sdxl.*.(safetensors|bin)$' - else: - pattern = 'brushnet.random.mask.*.(safetensors|bin)$' - elif type == 'brushnet_segmentation': - brush_model = BRUSHNET_MODELS['segmentation_mask'][model_type]['model_url'] - if model_type == 'sdxl': - pattern = 'brushnet.segmentation.mask.sdxl.*.(safetensors|bin)$' - else: - pattern = 'brushnet.segmentation.mask.*.(safetensors|bin)$' - - - brushfile = [e for e in folder_paths.get_filename_list('inpaint') if re.search(pattern, e, re.IGNORECASE)] - brushname = brushfile[0] if brushfile else None - if not brushname: - from urllib.parse import urlparse - get_local_filepath(brush_model, INPAINT_DIR, cache_dir='/stable-diffusion-cache/models/inpaint') - parsed_url = urlparse(brush_model) - brushname = os.path.basename(parsed_url.path) - return brushname - - def get_powerpaint_model(self, model): - model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1' - if model_type == 'sdxl': - raise Exception("Powerpaint not supported for SDXL models") - - powerpaint_model = POWERPAINT_MODELS['v2.1']['model_url'] - powerpaint_clip = POWERPAINT_MODELS['v2.1']['clip_url'] - - from urllib.parse import urlparse - get_local_filepath(powerpaint_model, os.path.join(INPAINT_DIR, 'powerpaint')) - model_parsed_url = urlparse(powerpaint_model) - clip_parsed_url = urlparse(powerpaint_clip) - model_name = os.path.join("powerpaint",os.path.basename(model_parsed_url.path)) - clip_name = os.path.join("powerpaint",os.path.basename(clip_parsed_url.path)) - return model_name, clip_name - - def apply(self, pipe, image, mask, inpaint_mode, encode, grow_mask_by, dtype, fitting, function, scale, start_at, end_at): - new_pipe = { - **pipe, - } - del pipe - if inpaint_mode in ['brushnet_random', 'brushnet_segmentation']: - brushnet = self.get_brushnet_model(inpaint_mode, new_pipe['model']) - new_pipe, = applyBrushNet().apply(new_pipe, image, mask, brushnet, dtype, scale, start_at, end_at) - elif inpaint_mode == 'powerpaint': - powerpaint_model, powerpaint_clip = self.get_powerpaint_model(new_pipe['model']) - new_pipe, = applyPowerPaint().apply(new_pipe, image, mask, powerpaint_model, powerpaint_clip, dtype, fitting, function, scale, start_at, end_at) - - vae = new_pipe['vae'] - if encode == 'none': - if inpaint_mode == 'fooocus_inpaint': - model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], - list(FOOOCUS_INPAINT_HEAD.keys())[0], - list(FOOOCUS_INPAINT_PATCH.keys())[0]) - new_pipe['model'] = model - elif encode == 'vae_encode_inpaint': - latent, = VAEEncodeForInpaint().encode(vae, image, mask, grow_mask_by) - new_pipe['samples'] = latent - if inpaint_mode == 'fooocus_inpaint': - model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], - list(FOOOCUS_INPAINT_HEAD.keys())[0], - list(FOOOCUS_INPAINT_PATCH.keys())[0]) - new_pipe['model'] = model - elif encode == 'inpaint_model_conditioning': - if inpaint_mode == 'fooocus_inpaint': - latent, = VAEEncodeForInpaint().encode(vae, image, mask, grow_mask_by) - new_pipe['samples'] = latent - model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], - list(FOOOCUS_INPAINT_HEAD.keys())[0], - list(FOOOCUS_INPAINT_PATCH.keys())[0]) - new_pipe['model'] = model - new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, 0) - else: - new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, grow_mask_by) - elif encode == 'different_diffusion': - if inpaint_mode == 'fooocus_inpaint': - latent, = VAEEncodeForInpaint().encode(vae, image, mask, grow_mask_by) - new_pipe['samples'] = latent - model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], - list(FOOOCUS_INPAINT_HEAD.keys())[0], - list(FOOOCUS_INPAINT_PATCH.keys())[0]) - new_pipe['model'] = model - new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, 0) - else: - new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, grow_mask_by) - cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion'] - if cls is not None: - model, = cls().apply(new_pipe['model']) - new_pipe['model'] = model - else: - raise Exception("Differential Diffusion not found,please update comfyui") - - return (new_pipe,) -# ---------------------------------------------------------------Inpaint 结束----------------------------------------------------------------------# - -#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------# -class applyLoraStack: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "lora_stack": ("LORA_STACK",), - "model": ("MODEL",), - }, - "optional": { - "optional_clip": ("CLIP",), - } - } - - RETURN_TYPES = ("MODEL", "CLIP") - RETURN_NAMES = ("model", "clip") - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, lora_stack, model, optional_clip=None): - clip = None - if lora_stack is not None and len(lora_stack) > 0: - for lora in lora_stack: - lora = {"lora_name": lora[0], "model": model, "clip": optional_clip, "model_strength": lora[1], - "clip_strength": lora[2]} - model, clip = easyCache.load_lora(lora, model, optional_clip, use_cache=False) - return (model, clip) - -class applyControlnetStack: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "controlnet_stack": ("CONTROL_NET_STACK",), - "pipe": ("PIPE_LINE",), - }, - "optional": { - } - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, controlnet_stack, pipe): - - positive = pipe['positive'] - negative = pipe['negative'] - model = pipe['model'] - vae = pipe['vae'] - - if controlnet_stack is not None and len(controlnet_stack) >0: - for controlnet in controlnet_stack: - positive, negative = easyControlnet().apply(controlnet[0], controlnet[5], positive, negative, controlnet[1], start_percent=controlnet[2], end_percent=controlnet[3], control_net=None, scale_soft_weights=controlnet[4], mask=None, easyCache=easyCache, use_cache=False, model=model, vae=vae) - - new_pipe = { - **pipe, - "positive": positive, - "negetive": negative, - } - del pipe - - return (new_pipe,) - -# 风格对齐 -from .libs.styleAlign import styleAlignBatch, SHARE_NORM_OPTIONS, SHARE_ATTN_OPTIONS -class styleAlignedBatchAlign: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "model": ("MODEL",), - "share_norm": (SHARE_NORM_OPTIONS,), - "share_attn": (SHARE_ATTN_OPTIONS,), - "scale": ("FLOAT", {"default": 1, "min": 0, "max": 1.0, "step": 0.1}), - } - } - - RETURN_TYPES = ("MODEL",) - FUNCTION = "align" - CATEGORY = "EasyUse/Adapter" - - def align(self, model, share_norm, share_attn, scale): - return (styleAlignBatch(model, share_norm, share_attn, scale),) - -# 光照对齐 -from .ic_light.__init__ import ICLight, VAEEncodeArgMax -class icLightApply: - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "mode": (list(IC_LIGHT_MODELS.keys()),), - "model": ("MODEL",), - "image": ("IMAGE",), - "vae": ("VAE",), - "lighting": (['None', 'Left Light', 'Right Light', 'Top Light', 'Bottom Light', 'Circle Light'],{"default": "None"}), - "source": (['Use Background Image', 'Use Flipped Background Image', 'Left Light', 'Right Light', 'Top Light', 'Bottom Light', 'Ambient'],{"default": "Use Background Image"}), - "remove_bg": ("BOOLEAN", {"default": True}), - }, - } - - RETURN_TYPES = ("MODEL", "IMAGE") - RETURN_NAMES = ("model", "lighting_image") - FUNCTION = "apply" - CATEGORY = "EasyUse/Adapter" - - def batch(self, image1, image2): - if image1.shape[1:] != image2.shape[1:]: - image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", - "center").movedim(1, -1) - s = torch.cat((image1, image2), dim=0) - return s - - def removebg(self, image): - if "easy imageRemBg" not in ALL_NODE_CLASS_MAPPINGS: - raise Exception("Please re-install ComfyUI-Easy-Use") - cls = ALL_NODE_CLASS_MAPPINGS['easy imageRemBg'] - results = cls().remove('RMBG-1.4', image, 'Hide', 'ComfyUI') - if "result" in results: - image, _ = results['result'] - return image - - def apply(self, mode, model, image, vae, lighting, source, remove_bg): - model_type = get_sd_version(model) - if model_type == 'sdxl': - raise Exception("IC Light model is not supported for SDXL now") - - batch_size, height, width, channel = image.shape - if channel == 3: - # remove bg - if mode == 'Foreground' or batch_size == 1: - if remove_bg: - image = self.removebg(image) - else: - mask = torch.full((1, height, width), 1.0, dtype=torch.float32, device="cpu") - image, = JoinImageWithAlpha().join_image_with_alpha(image, mask) - - iclight = ICLight() - if mode == 'Foreground': - lighting_image = iclight.generate_lighting_image(image, lighting) - else: - lighting_image = iclight.generate_source_image(image, source) - if source not in ['Use Background Image', 'Use Flipped Background Image']: - _, height, width, _ = lighting_image.shape - mask = torch.full((1, height, width), 1.0, dtype=torch.float32, device="cpu") - lighting_image, = JoinImageWithAlpha().join_image_with_alpha(lighting_image, mask) - if batch_size < 2: - image = self.batch(image, lighting_image) - else: - original_image = [img.unsqueeze(0) for img in image] - original_image = self.removebg(original_image[0]) - image = self.batch(original_image, lighting_image) - - latent, = VAEEncodeArgMax().encode(vae, image) - key = 'iclight_' + mode + '_' + model_type - model_path = get_local_filepath(IC_LIGHT_MODELS[mode]['sd1']["model_url"], - os.path.join(folder_paths.models_dir, "unet"), cache_dir='/stable-diffusion-cache/models/unet') - ic_model = None - if key in backend_cache.cache: - log_node_info("easy icLightApply", f"Using icLightModel {mode+'_'+model_type} Cached") - _, ic_model = backend_cache.cache[key][1] - m, _ = iclight.apply(model_path, model, latent, ic_model) - else: - m, ic_model = iclight.apply(model_path, model, latent, ic_model) - backend_cache.update_cache(key, 'iclight', (False, ic_model)) - return (m, lighting_image) - - -def insightface_loader(provider, name='buffalo_l'): - try: - from insightface.app import FaceAnalysis - except ImportError as e: - raise Exception(e) - path = os.path.join(folder_paths.models_dir, "insightface") - model = FaceAnalysis(name=name, root=path, providers=[provider + 'ExecutionProvider', ]) - model.prepare(ctx_id=0, det_size=(640, 640)) - return model - -# Apply Ipadapter -class ipadapter: - - def __init__(self): - self.normal_presets = [ - 'LIGHT - SD1.5 only (low strength)', - 'STANDARD (medium strength)', - 'VIT-G (medium strength)', - 'PLUS (high strength)', - 'PLUS (kolors genernal)', - 'PLUS FACE (portraits)', - 'FULL FACE - SD1.5 only (portraits stronger)', - 'COMPOSITION' - ] - self.faceid_presets = [ - 'FACEID', - 'FACEID PLUS - SD1.5 only', - "FACEID PLUS KOLORS", - 'FACEID PLUS V2', - 'FACEID PORTRAIT (style transfer)', - 'FACEID PORTRAIT UNNORM - SDXL only (strong)' - ] - self.weight_types = ["linear", "ease in", "ease out", 'ease in-out', 'reverse in-out', 'weak input', 'weak output', 'weak middle', 'strong middle', 'style transfer', 'composition', 'strong style transfer', 'style and composition', 'style transfer precise'] - self.presets = self.normal_presets + self.faceid_presets - - - def error(self): - raise Exception(f"[ERROR] To use ipadapterApply, you need to install 'ComfyUI_IPAdapter_plus'") - - def get_clipvision_file(self, preset, node_name): - preset = preset.lower() - clipvision_list = folder_paths.get_filename_list("clip_vision") - - if preset.startswith("plus (kolors") or preset.startswith("faceid plus kolors"): - pattern = 'Vit.Large.patch14.336.(bin|safetensors)$' - elif preset.startswith("vit-g"): - pattern = '(ViT.bigG.14.*39B.b160k|ipadapter.*sdxl|sdxl.*model.(bin|safetensors))' - else: - pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model.(bin|safetensors))' - clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)] - - clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None - clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None - # if clipvision_name is not None: - # log_node_info(node_name, f"Using {clipvision_name}") - - return clipvision_file, clipvision_name - - def get_ipadapter_file(self, preset, is_sdxl, node_name): - preset = preset.lower() - ipadapter_list = folder_paths.get_filename_list("ipadapter") - is_insightface = False - lora_pattern = None - - if preset.startswith("light"): - if is_sdxl: - raise Exception("light model is not supported for SDXL") - pattern = 'sd15.light.v11.(safetensors|bin)$' - # if light model v11 is not found, try with the old version - if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]: - pattern = 'sd15.light.(safetensors|bin)$' - elif preset.startswith("standard"): - if is_sdxl: - pattern = 'ip.adapter.sdxl.vit.h.(safetensors|bin)$' - else: - pattern = 'ip.adapter.sd15.(safetensors|bin)$' - elif preset.startswith("vit-g"): - if is_sdxl: - pattern = 'ip.adapter.sdxl.(safetensors|bin)$' - else: - pattern = 'sd15.vit.g.(safetensors|bin)$' - elif preset.startswith("plus (high"): - if is_sdxl: - pattern = 'plus.sdxl.vit.h.(safetensors|bin)$' - else: - pattern = 'ip.adapter.plus.sd15.(safetensors|bin)$' - elif preset.startswith("plus (kolors"): - if is_sdxl: - pattern = 'plus.gener(nal|al).(safetensors|bin)$' - else: - raise Exception("kolors model is not supported for SD15") - elif preset.startswith("plus face"): - if is_sdxl: - pattern = 'plus.face.sdxl.vit.h.(safetensors|bin)$' - else: - pattern = 'plus.face.sd15.(safetensors|bin)$' - elif preset.startswith("full"): - if is_sdxl: - raise Exception("full face model is not supported for SDXL") - pattern = 'full.face.sd15.(safetensors|bin)$' - elif preset.startswith("composition"): - if is_sdxl: - pattern = 'plus.composition.sdxl.(safetensors|bin)$' - else: - pattern = 'plus.composition.sd15.(safetensors|bin)$' - elif preset.startswith("faceid portrait ("): - if is_sdxl: - pattern = 'portrait.sdxl.(safetensors|bin)$' - else: - pattern = 'portrait.v11.sd15.(safetensors|bin)$' - # if v11 is not found, try with the old version - if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]: - pattern = 'portrait.sd15.(safetensors|bin)$' - is_insightface = True - elif preset.startswith("faceid portrait unnorm"): - if is_sdxl: - pattern = r'portrait.sdxl.unnorm.(safetensors|bin)$' - else: - raise Exception("portrait unnorm model is not supported for SD1.5") - is_insightface = True - elif preset == "faceid": - if is_sdxl: - pattern = 'faceid.sdxl.(safetensors|bin)$' - lora_pattern = 'faceid.sdxl.lora.safetensors$' - else: - pattern = 'faceid.sd15.(safetensors|bin)$' - lora_pattern = 'faceid.sd15.lora.safetensors$' - is_insightface = True - elif preset.startswith("faceid plus kolors"): - if is_sdxl: - pattern = '(kolors.ip.adapter.faceid.plus|ipa.faceid.plus).(safetensors|bin)$' - else: - raise Exception("faceid plus kolors model is not supported for SD1.5") - is_insightface = True - elif preset.startswith("faceid plus -"): - if is_sdxl: - raise Exception("faceid plus model is not supported for SDXL") - pattern = 'faceid.plus.sd15.(safetensors|bin)$' - lora_pattern = 'faceid.plus.sd15.lora.safetensors$' - is_insightface = True - elif preset.startswith("faceid plus v2"): - if is_sdxl: - pattern = 'faceid.plusv2.sdxl.(safetensors|bin)$' - lora_pattern = 'faceid.plusv2.sdxl.lora.safetensors$' - else: - pattern = 'faceid.plusv2.sd15.(safetensors|bin)$' - lora_pattern = 'faceid.plusv2.sd15.lora.safetensors$' - is_insightface = True - else: - raise Exception(f"invalid type '{preset}'") - - ipadapter_files = [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)] - ipadapter_name = ipadapter_files[0] if len(ipadapter_files)>0 else None - ipadapter_file = folder_paths.get_full_path("ipadapter", ipadapter_name) if ipadapter_name else None - # if ipadapter_name is not None: - # log_node_info(node_name, f"Using {ipadapter_name}") - - return ipadapter_file, ipadapter_name, is_insightface, lora_pattern - - def get_lora_pattern(self, file): - basename = os.path.basename(file) - lora_pattern = None - if re.search(r'faceid.sdxl.(safetensors|bin)$', basename, re.IGNORECASE): - lora_pattern = 'faceid.sdxl.lora.safetensors$' - elif re.search(r'faceid.sd15.(safetensors|bin)$', basename, re.IGNORECASE): - lora_pattern = 'faceid.sd15.lora.safetensors$' - elif re.search(r'faceid.plus.sd15.(safetensors|bin)$', basename, re.IGNORECASE): - lora_pattern = 'faceid.plus.sd15.lora.safetensors$' - elif re.search(r'faceid.plusv2.sdxl.(safetensors|bin)$', basename, re.IGNORECASE): - lora_pattern = 'faceid.plusv2.sdxl.lora.safetensors$' - elif re.search(r'faceid.plusv2.sd15.(safetensors|bin)$', basename, re.IGNORECASE): - lora_pattern = 'faceid.plusv2.sd15.lora.safetensors$' - - return lora_pattern - - def get_lora_file(self, preset, pattern, model_type, model, model_strength, clip_strength, clip=None): - lora_list = folder_paths.get_filename_list("loras") - lora_files = [e for e in lora_list if re.search(pattern, e, re.IGNORECASE)] - lora_name = lora_files[0] if lora_files else None - if lora_name: - return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},) - else: - if "lora_url" in IPADAPTER_MODELS[preset][model_type]: - lora_name = get_local_filepath(IPADAPTER_MODELS[preset][model_type]["lora_url"], os.path.join(folder_paths.models_dir, "loras"), cache_dir='/stable-diffusion-cache/models/comfyui_loras') - return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},) - return (model, clip) - - def ipadapter_model_loader(self, file): - model = comfy.utils.load_torch_file(file, safe_load=True) - - if file.lower().endswith(".safetensors"): - st_model = {"image_proj": {}, "ip_adapter": {}} - for key in model.keys(): - if key.startswith("image_proj."): - st_model["image_proj"][key.replace("image_proj.", "")] = model[key] - elif key.startswith("ip_adapter."): - st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key] - model = st_model - del st_model - - model_keys = model.keys() - if "adapter_modules" in model_keys: - model["ip_adapter"] = model["adapter_modules"] - model["faceidplusv2"] = True - del model['adapter_modules'] - - if not "ip_adapter" in model_keys or not model["ip_adapter"]: - raise Exception("invalid IPAdapter model {}".format(file)) - - if 'plusv2' in file.lower(): - model["faceidplusv2"] = True - - if 'unnorm' in file.lower(): - model["portraitunnorm"] = True - - return model - - def load_model(self, model, preset, lora_model_strength, provider="CPU", clip_vision=None, optional_ipadapter=None, cache_mode='none', node_name='easy ipadapterApply'): - pipeline = {"clipvision": {'file': None, 'model': None}, "ipadapter": {'file': None, 'model': None}, - "insightface": {'provider': None, 'model': None}} - ipadapter, insightface, is_insightface, lora_pattern = None, None, None, None - if optional_ipadapter is not None: - pipeline = optional_ipadapter - if not clip_vision: - clip_vision = pipeline['clipvision']['model'] - ipadapter = pipeline['ipadapter']['model'] - if 'insightface' in pipeline: - insightface = pipeline['insightface']['model'] - lora_pattern = self.get_lora_pattern(pipeline['ipadapter']['file']) - - # 1. Load the clipvision model - if not clip_vision: - clipvision_file, clipvision_name = self.get_clipvision_file(preset, node_name) - if clipvision_file is None: - if preset.lower().startswith("plus (kolors"): - model_url = IPADAPTER_CLIPVISION_MODELS["clip-vit-large-patch14-336"]["model_url"] - clipvision_file = get_local_filepath(model_url, IPADAPTER_DIR, "clip-vit-large-patch14-336.bin", cache_dir='/stable-diffusion-cache/models/clip') - else: - model_url = IPADAPTER_CLIPVISION_MODELS["clip-vit-h-14-32b-b79k"]["model_url"] - clipvision_file = get_local_filepath(model_url, IPADAPTER_DIR, "clip-vit-h-14-32b-b79k.safetensors", cache_dir='/stable-diffusion-cache/models/clip') - clipvision_name = os.path.basename(model_url) - if clipvision_file == pipeline['clipvision']['file']: - clip_vision = pipeline['clipvision']['model'] - elif cache_mode in ["all", "clip_vision only"] and clipvision_name in backend_cache.cache: - log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name} Cached") - _, clip_vision = backend_cache.cache[clipvision_name][1] - else: - clip_vision = load_clip_vision(clipvision_file) - log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name}") - if cache_mode in ["all", "clip_vision only"]: - backend_cache.update_cache(clipvision_name, 'clip_vision', (False, clip_vision)) - pipeline['clipvision']['file'] = clipvision_file - pipeline['clipvision']['model'] = clip_vision - - # 2. Load the ipadapter model - is_sdxl = isinstance(model.model, comfy.model_base.SDXL) - if not ipadapter: - ipadapter_file, ipadapter_name, is_insightface, lora_pattern = self.get_ipadapter_file(preset, is_sdxl, node_name) - model_type = 'sdxl' if is_sdxl else 'sd15' - if ipadapter_file is None: - model_url = IPADAPTER_MODELS[preset][model_type]["model_url"] - ipadapter_file = get_local_filepath(model_url, IPADAPTER_DIR, cache_dir='/stable-diffusion-cache/models/ControlNet') - ipadapter_name = os.path.basename(model_url) - if ipadapter_file == pipeline['ipadapter']['file']: - ipadapter = pipeline['ipadapter']['model'] - elif cache_mode in ["all", "ipadapter only"] and ipadapter_name in backend_cache.cache: - log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name} Cached") - _, ipadapter = backend_cache.cache[ipadapter_name][1] - else: - ipadapter = self.ipadapter_model_loader(ipadapter_file) - pipeline['ipadapter']['file'] = ipadapter_file - log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name}") - if cache_mode in ["all", "ipadapter only"]: - backend_cache.update_cache(ipadapter_name, 'ipadapter', (False, ipadapter)) - - pipeline['ipadapter']['model'] = ipadapter - - # 3. Load the lora model if needed - if lora_pattern is not None: - if lora_model_strength > 0: - model, _ = self.get_lora_file(preset, lora_pattern, model_type, model, lora_model_strength, 1) - - # 4. Load the insightface model if needed - if is_insightface: - if not insightface: - icache_key = 'insightface-' + provider - if provider == pipeline['insightface']['provider']: - insightface = pipeline['insightface']['model'] - elif cache_mode in ["all", "insightface only"] and icache_key in backend_cache.cache: - log_node_info("easy ipadapterApply", f"Using InsightFaceModel {icache_key} Cached") - _, insightface = backend_cache.cache[icache_key][1] - else: - insightface = insightface_loader(provider, 'antelopev2' if preset == 'FACEID PLUS KOLORS' else 'buffalo_l') - if cache_mode in ["all", "insightface only"]: - backend_cache.update_cache(icache_key, 'insightface',(False, insightface)) - pipeline['insightface']['provider'] = provider - pipeline['insightface']['model'] = insightface - - return (model, pipeline,) - -class ipadapterApply(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - presets = cls().presets - return { - "required": { - "model": ("MODEL",), - "image": ("IMAGE",), - "preset": (presets,), - "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}), - "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],), - "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}), - "weight_faceidv2": ("FLOAT", { "default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "all"},), - "use_tiled": ("BOOLEAN", {"default": False},), - }, - - "optional": { - "attn_mask": ("MASK",), - "optional_ipadapter": ("IPADAPTER",), - } - } - - RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",) - RETURN_NAMES = ("model", "images", "masks", "ipadapter", ) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, start_at, end_at, cache_mode, use_tiled, attn_mask=None, optional_ipadapter=None, weight_kolors=None): - images, masks = image, [None] - model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) - if use_tiled and preset not in self.faceid_presets: - if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"] - model, images, masks = cls().apply_tiled(model, ipadapter, image, weight, "linear", start_at, end_at, sharpening=0.0, combine_embeds="concat", image_negative=None, attn_mask=attn_mask, clip_vision=None, embeds_scaling='V only') - else: - if preset in ['FACEID PLUS KOLORS', 'FACEID PLUS V2', 'FACEID PORTRAIT (style transfer)']: - if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] - if weight_kolors is None: - weight_kolors = weight - model, images = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight=weight, weight_type="linear", combine_embeds="concat", weight_faceidv2=weight_faceidv2, image=image, image_negative=None, clip_vision=None, attn_mask=attn_mask, insightface=None, embeds_scaling='V only', weight_kolors=weight_kolors) - else: - if "IPAdapter" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapter"] - model, images = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, weight_type='standard', attn_mask=attn_mask) - if images is None: - images = image - return (model, images, masks, ipadapter,) - -class ipadapterApplyAdvanced(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - presets = ipa_cls.presets - weight_types = ipa_cls.weight_types - return { - "required": { - "model": ("MODEL",), - "image": ("IMAGE",), - "preset": (presets,), - "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}), - "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],), - "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}), - "weight_faceidv2": ("FLOAT", {"default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }), - "weight_type": (weight_types,), - "combine_embeds": (["concat", "add", "subtract", "average", "norm average"],), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), - "cache_mode": (["insightface only", "clip_vision only","ipadapter only", "all", "none"], {"default": "all"},), - "use_tiled": ("BOOLEAN", {"default": False},), - "use_batch": ("BOOLEAN", {"default": False},), - "sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}), - }, - - "optional": { - "image_negative": ("IMAGE",), - "attn_mask": ("MASK",), - "clip_vision": ("CLIP_VISION",), - "optional_ipadapter": ("IPADAPTER",), - "layer_weights": ("STRING", {"default": "", "multiline": True, "placeholder": "Mad Scientist Layer Weights"}), - } - } - - RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",) - RETURN_NAMES = ("model", "images", "masks", "ipadapter", ) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, weight_type, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, use_tiled, use_batch, sharpening, weight_style=1.0, weight_composition=1.0, image_style=None, image_composition=None, expand_style=False, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None, layer_weights=None, weight_kolors=None): - images, masks = image, [None] - model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=clip_vision, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) - - if weight_kolors is None: - weight_kolors = weight - - if layer_weights: - if "IPAdapterMS" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] - model, images = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, weight_style=weight_style, weight_composition=weight_composition, image_style=image_style, image_composition=image_composition, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling, layer_weights=layer_weights, weight_kolors=weight_kolors) - elif use_tiled: - if use_batch: - if "IPAdapterTiledBatch" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiledBatch"] - else: - if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"] - model, images, masks = cls().apply_tiled(model, ipadapter, image=image, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, sharpening=sharpening, combine_embeds=combine_embeds, image_negative=image_negative, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling) - else: - if use_batch: - if "IPAdapterBatch" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterBatch"] - else: - if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] - model, images = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, weight_style=1.0, weight_composition=1.0, image_style=image_style, image_composition=image_composition, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling, weight_kolors=weight_kolors) - if images is None: - images = image - return (model, images, masks, ipadapter) - -class ipadapterApplyFaceIDKolors(ipadapterApplyAdvanced): - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - presets = ipa_cls.presets - weight_types = ipa_cls.weight_types - return { - "required": { - "model": ("MODEL",), - "image": ("IMAGE",), - "preset": (['FACEID PLUS KOLORS'], {"default":"FACEID PLUS KOLORS"}), - "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}), - "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"],), - "weight": ("FLOAT", {"default": 0.8, "min": -1, "max": 3, "step": 0.05}), - "weight_faceidv2": ("FLOAT", {"default": 1.0, "min": -1, "max": 5.0, "step": 0.05}), - "weight_kolors": ("FLOAT", {"default": 0.8, "min": -1, "max": 5.0, "step": 0.05}), - "weight_type": (weight_types,), - "combine_embeds": (["concat", "add", "subtract", "average", "norm average"],), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), - "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "all"},), - "use_tiled": ("BOOLEAN", {"default": False},), - "use_batch": ("BOOLEAN", {"default": False},), - "sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}), - }, - - "optional": { - "image_negative": ("IMAGE",), - "attn_mask": ("MASK",), - "clip_vision": ("CLIP_VISION",), - "optional_ipadapter": ("IPADAPTER",), - } - } - - -class ipadapterStyleComposition(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - normal_presets = ipa_cls.normal_presets - weight_types = ipa_cls.weight_types - return { - "required": { - "model": ("MODEL",), - "image_style": ("IMAGE",), - "preset": (normal_presets,), - "weight_style": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}), - "weight_composition": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}), - "expand_style": ("BOOLEAN", {"default": False}), - "combine_embeds": (["concat", "add", "subtract", "average", "norm average"], {"default": "average"}), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), - "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], - {"default": "all"},), - }, - "optional": { - "image_composition": ("IMAGE",), - "image_negative": ("IMAGE",), - "attn_mask": ("MASK",), - "clip_vision": ("CLIP_VISION",), - "optional_ipadapter": ("IPADAPTER",), - } - } - - CATEGORY = "EasyUse/Adapter" - - RETURN_TYPES = ("MODEL", "IPADAPTER",) - RETURN_NAMES = ("model", "ipadapter",) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, model, preset, weight_style, weight_composition, expand_style, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, image_style=None , image_composition=None, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None): - model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) - - if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] - - model, image = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight_style=weight_style, weight_composition=weight_composition, weight_type='linear', combine_embeds=combine_embeds, weight_faceidv2=weight_composition, image_style=image_style, image_composition=image_composition, image_negative=image_negative, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling) - return (model, ipadapter) - -class ipadapterApplyEncoder(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - normal_presets = ipa_cls.normal_presets - max_embeds_num = 4 - inputs = { - "required": { - "model": ("MODEL",), - "clip_vision": ("CLIP_VISION",), - "image1": ("IMAGE",), - "preset": (normal_presets,), - "num_embeds": ("INT", {"default": 2, "min": 1, "max": max_embeds_num}), - }, - "optional": {} - } - - for i in range(1, max_embeds_num + 1): - if i > 1: - inputs["optional"][f"image{i}"] = ("IMAGE",) - for i in range(1, max_embeds_num + 1): - inputs["optional"][f"mask{i}"] = ("MASK",) - inputs["optional"][f"weight{i}"] = ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}) - inputs["optional"]["combine_method"] = (["concat", "add", "subtract", "average", "norm average", "max", "min"],) - inputs["optional"]["optional_ipadapter"] = ("IPADAPTER",) - inputs["optional"]["pos_embeds"] = ("EMBEDS",) - inputs["optional"]["neg_embeds"] = ("EMBEDS",) - return inputs - - RETURN_TYPES = ("MODEL", "CLIP_VISION","IPADAPTER", "EMBEDS", "EMBEDS", ) - RETURN_NAMES = ("model", "clip_vision","ipadapter", "pos_embed", "neg_embed",) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def batch(self, embeds, method): - if method == 'concat' and len(embeds) == 1: - return (embeds[0],) - - embeds = [embed for embed in embeds if embed is not None] - embeds = torch.cat(embeds, dim=0) - - if method == "add": - embeds = torch.sum(embeds, dim=0).unsqueeze(0) - elif method == "subtract": - embeds = embeds[0] - torch.mean(embeds[1:], dim=0) - embeds = embeds.unsqueeze(0) - elif method == "average": - embeds = torch.mean(embeds, dim=0).unsqueeze(0) - elif method == "norm average": - embeds = torch.mean(embeds / torch.norm(embeds, dim=0, keepdim=True), dim=0).unsqueeze(0) - elif method == "max": - embeds = torch.max(embeds, dim=0).values.unsqueeze(0) - elif method == "min": - embeds = torch.min(embeds, dim=0).values.unsqueeze(0) - - return embeds - - def apply(self, **kwargs): - model = kwargs['model'] - clip_vision = kwargs['clip_vision'] - preset = kwargs['preset'] - if 'optional_ipadapter' in kwargs: - ipadapter = kwargs['optional_ipadapter'] - else: - model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=clip_vision, optional_ipadapter=None, cache_mode='none') - - if "IPAdapterEncoder" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - encoder_cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterEncoder"] - pos_embeds = kwargs["pos_embeds"] if "pos_embeds" in kwargs else [] - neg_embeds = kwargs["neg_embeds"] if "neg_embeds" in kwargs else [] - for i in range(1, kwargs['num_embeds'] + 1): - if f"image{i}" not in kwargs: - raise Exception(f"image{i} is required") - kwargs[f"mask{i}"] = kwargs[f"mask{i}"] if f"mask{i}" in kwargs else None - kwargs[f"weight{i}"] = kwargs[f"weight{i}"] if f"weight{i}" in kwargs else 1.0 - - pos, neg = encoder_cls().encode(ipadapter, kwargs[f"image{i}"], kwargs[f"weight{i}"], kwargs[f"mask{i}"], clip_vision=clip_vision) - pos_embeds.append(pos) - neg_embeds.append(neg) - - pos_embeds = self.batch(pos_embeds, kwargs['combine_method']) - neg_embeds = self.batch(neg_embeds, kwargs['combine_method']) - - return (model,clip_vision, ipadapter, pos_embeds, neg_embeds) - -class ipadapterApplyEmbeds(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - weight_types = ipa_cls.weight_types - return { - "required": { - "model": ("MODEL",), - "clip_vision": ("CLIP_VISION",), - "ipadapter": ("IPADAPTER",), - "pos_embed": ("EMBEDS",), - "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}), - "weight_type": (weight_types,), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), - }, - - "optional": { - "neg_embed": ("EMBEDS",), - "attn_mask": ("MASK",), - } - } - - RETURN_TYPES = ("MODEL", "IPADAPTER",) - RETURN_NAMES = ("model", "ipadapter", ) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, model, ipadapter, clip_vision, pos_embed, weight, weight_type, start_at, end_at, embeds_scaling, attn_mask=None, neg_embed=None,): - if "IPAdapterEmbeds" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterEmbeds"] - model, image = cls().apply_ipadapter(model, ipadapter, pos_embed, weight, weight_type, start_at, end_at, neg_embed=neg_embed, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling) - - return (model, ipadapter) - -class ipadapterApplyRegional(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - weight_types = ipa_cls.weight_types - return { - "required": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "positive": ("STRING", {"default": "", "placeholder": "positive", "multiline": True}), - "negative": ("STRING", {"default": "", "placeholder": "negative", "multiline": True}), - "image_weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 3.0, "step": 0.05}), - "prompt_weight": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.05}), - "weight_type": (weight_types,), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - }, - - "optional": { - "mask": ("MASK",), - "optional_ipadapter_params": ("IPADAPTER_PARAMS",), - }, - "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE", "IPADAPTER_PARAMS", "CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("pipe", "ipadapter_params", "positive", "negative") - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, pipe, image, positive, negative, image_weight, prompt_weight, weight_type, start_at, end_at, mask=None, optional_ipadapter_params=None, prompt=None, my_unique_id=None): - model = pipe['model'] - - if positive == '': - positive = pipe['loader_settings']['positive'] - if negative == '': - negative = pipe['loader_settings']['negative'] - - if "clip" not in pipe or not pipe['clip']: - if "chatglm3_model" in pipe: - chatglm3_model = pipe['chatglm3_model'] - # text encode - log_node_warn("Positive encoding...") - positive_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, positive, False) - log_node_warn("Negative encoding...") - negative_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, negative, False) - else: - clip = pipe['clip'] - clip_skip = pipe['loader_settings']['clip_skip'] - a1111_prompt_style = pipe['loader_settings']['a1111_prompt_style'] - pipe_lora_stack = pipe['loader_settings']['lora_stack'] - positive_token_normalization = pipe['loader_settings']['positive_token_normalization'] - positive_weight_interpretation = pipe['loader_settings']['positive_weight_interpretation'] - negative_token_normalization = pipe['loader_settings']['negative_token_normalization'] - negative_weight_interpretation = pipe['loader_settings']['negative_weight_interpretation'] - - positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, pipe_lora_stack, positive, positive_token_normalization, positive_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache) - negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, clip_skip, pipe_lora_stack, negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache) - - #ipadapter regional - if "IPAdapterRegionalConditioning" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterRegionalConditioning"] - ipadapter_params, new_positive_embeds, new_negative_embeds = cls().conditioning(image, image_weight, prompt_weight, weight_type, start_at, end_at, mask=mask, positive=positive_embeddings_final, negative=negative_embeddings_final) - - if optional_ipadapter_params is not None: - positive_embeds = pipe['positive'] + new_positive_embeds - negative_embeds = pipe['negative'] + new_negative_embeds - _ipadapter_params = { - "image": optional_ipadapter_params["image"] + ipadapter_params["image"], - "attn_mask": optional_ipadapter_params["attn_mask"] + ipadapter_params["attn_mask"], - "weight": optional_ipadapter_params["weight"] + ipadapter_params["weight"], - "weight_type": optional_ipadapter_params["weight_type"] + ipadapter_params["weight_type"], - "start_at": optional_ipadapter_params["start_at"] + ipadapter_params["start_at"], - "end_at": optional_ipadapter_params["end_at"] + ipadapter_params["end_at"], - } - ipadapter_params = _ipadapter_params - del _ipadapter_params - else: - positive_embeds = new_positive_embeds - negative_embeds = new_negative_embeds - - new_pipe = { - **pipe, - "positive": positive_embeds, - "negative": negative_embeds, - } - - del pipe - - return (new_pipe, ipadapter_params, positive_embeds, negative_embeds) - -class ipadapterApplyFromParams(ipadapter): - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - ipa_cls = cls() - normal_presets = ipa_cls.normal_presets - return { - "required": { - "model": ("MODEL",), - "preset": (normal_presets,), - "ipadapter_params": ("IPADAPTER_PARAMS",), - "combine_embeds": (["concat", "add", "subtract", "average", "norm average", "max", "min"],), - "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), - "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], - {"default": "insightface only"}), - }, - - "optional": { - "optional_ipadapter": ("IPADAPTER",), - "image_negative": ("IMAGE",), - } - } - - RETURN_TYPES = ("MODEL", "IPADAPTER",) - RETURN_NAMES = ("model", "ipadapter", ) - CATEGORY = "EasyUse/Adapter" - FUNCTION = "apply" - - def apply(self, model, preset, ipadapter_params, combine_embeds, embeds_scaling, cache_mode, optional_ipadapter=None, image_negative=None,): - model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) - if "IPAdapterFromParams" not in ALL_NODE_CLASS_MAPPINGS: - self.error() - cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterFromParams"] - model, image = cls().apply_ipadapter(model, ipadapter, clip_vision=None, combine_embeds=combine_embeds, embeds_scaling=embeds_scaling, image_negative=image_negative, ipadapter_params=ipadapter_params) - - return (model, ipadapter) - -#Apply InstantID -class instantID: - - def error(self): - raise Exception(f"[ERROR] To use instantIDApply, you need to install 'ComfyUI_InstantID'") - - def run(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - instantid_model, insightface_model, face_embeds = None, None, None - model = pipe['model'] - # Load InstantID - cache_key = 'instantID' - if cache_key in backend_cache.cache: - log_node_info("easy instantIDApply","Using InstantIDModel Cached") - _, instantid_model = backend_cache.cache[cache_key][1] - if "InstantIDModelLoader" in ALL_NODE_CLASS_MAPPINGS: - load_instant_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDModelLoader"] - instantid_model, = load_instant_cls().load_model(instantid_file) - backend_cache.update_cache(cache_key, 'instantid', (False, instantid_model)) - else: - self.error() - icache_key = 'insightface-' + insightface - if icache_key in backend_cache.cache: - log_node_info("easy instantIDApply", f"Using InsightFaceModel {insightface} Cached") - _, insightface_model = backend_cache.cache[icache_key][1] - elif "InstantIDFaceAnalysis" in ALL_NODE_CLASS_MAPPINGS: - load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDFaceAnalysis"] - insightface_model, = load_insightface_cls().load_insight_face(insightface) - backend_cache.update_cache(icache_key, 'insightface', (False, insightface_model)) - else: - self.error() - - # Apply InstantID - if "ApplyInstantID" in ALL_NODE_CLASS_MAPPINGS: - instantid_apply = ALL_NODE_CLASS_MAPPINGS['ApplyInstantID'] - if control_net is None: - control_net = easyCache.load_controlnet(control_net_name, cn_soft_weights) - model, positive, negative = instantid_apply().apply_instantid(instantid_model, insightface_model, control_net, image, model, positive, negative, start_at, end_at, weight=weight, ip_weight=None, cn_strength=cn_strength, noise=noise, image_kps=image_kps, mask=mask) - else: - self.error() - - new_pipe = { - "model": model, - "positive": positive, - "negative": negative, - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": pipe["samples"], - "images": pipe["images"], - "seed": 0, - - "loader_settings": pipe["loader_settings"] - } - - del pipe - - return (new_pipe, model, positive, negative) - -class instantIDApply(instantID): - - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required":{ - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "instantid_file": (folder_paths.get_filename_list("instantid"),), - "insightface": (["CPU", "CUDA", "ROCM"],), - "control_net_name": (folder_paths.get_filename_list("controlnet"),), - "cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), - "weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }), - "noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }), - }, - "optional": { - "image_kps": ("IMAGE",), - "mask": ("MASK",), - "control_net": ("CONTROL_NET",), - }, - "hidden": { - "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID" - }, - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("pipe", "model", "positive", "negative") - - FUNCTION = "apply" - CATEGORY = "EasyUse/Adapter" - - - def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - positive = pipe['positive'] - negative = pipe['negative'] - return self.run(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id) - -#Apply InstantID Advanced -class instantIDApplyAdvanced(instantID): - - def __init__(self): - super().__init__() - pass - - @classmethod - def INPUT_TYPES(cls): - return { - "required":{ - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "instantid_file": (folder_paths.get_filename_list("instantid"),), - "insightface": (["CPU", "CUDA", "ROCM"],), - "control_net_name": (folder_paths.get_filename_list("controlnet"),), - "cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), - "cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), - "weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }), - "noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }), - }, - "optional": { - "image_kps": ("IMAGE",), - "mask": ("MASK",), - "control_net": ("CONTROL_NET",), - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - }, - "hidden": { - "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID" - }, - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING") - RETURN_NAMES = ("pipe", "model", "positive", "negative") - - FUNCTION = "apply_advanced" - CATEGORY = "EasyUse/Adapter" - - def apply_advanced(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - - positive = positive if positive is not None else pipe['positive'] - negative = negative if negative is not None else pipe['negative'] - - return self.run(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id) - -class applyPulID: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "pulid_file": (folder_paths.get_filename_list("pulid"),), - "insightface": (["CPU", "CUDA", "ROCM"],), - "image": ("IMAGE",), - "method": (["fidelity", "style", "neutral"],), - "weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 5.0, "step": 0.05}), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - }, - "optional": { - "attn_mask": ("MASK",), - }, - } - - RETURN_TYPES = ("MODEL",) - RETURN_NAMES = ("model",) - - FUNCTION = "run" - CATEGORY = "EasyUse/Adapter" - - def error(self): - raise Exception(f"[ERROR] To use pulIDApply, you need to install 'ComfyUI_PulID'") - - def run(self, model, image, pulid_file, insightface, weight, start_at, end_at, method=None, noise=0.0, fidelity=None, projection=None, attn_mask=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - pulid_model, insightface_model, eva_clip = None, None, None - # Load PulID - cache_key = 'pulID' - if cache_key in backend_cache.cache: - log_node_info("easy pulIDApply","Using InstantIDModel Cached") - _, pulid_model = backend_cache.cache[cache_key][1] - if "PulidModelLoader" in ALL_NODE_CLASS_MAPPINGS: - load_pulid_cls = ALL_NODE_CLASS_MAPPINGS["PulidModelLoader"] - pulid_model, = load_pulid_cls().load_model(pulid_file) - backend_cache.update_cache(cache_key, 'pulid', (False, pulid_model)) - else: - self.error() - # Load Insightface - icache_key = 'insightface-' + insightface - if icache_key in backend_cache.cache: - log_node_info("easy pulIDApply", f"Using InsightFaceModel {insightface} Cached") - _, insightface_model = backend_cache.cache[icache_key][1] - elif "PulidInsightFaceLoader" in ALL_NODE_CLASS_MAPPINGS: - load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["PulidInsightFaceLoader"] - insightface_model, = load_insightface_cls().load_insightface(insightface) - backend_cache.update_cache(icache_key, 'insightface', (False, insightface_model)) - else: - self.error() - # Load Eva clip - ecache_key = 'eva_clip' - if ecache_key in backend_cache.cache: - log_node_info("easy pulIDApply", f"Using EVAClipModel Cached") - _, eva_clip = backend_cache.cache[ecache_key][1] - elif "PulidEvaClipLoader" in ALL_NODE_CLASS_MAPPINGS: - load_evaclip_cls = ALL_NODE_CLASS_MAPPINGS["PulidEvaClipLoader"] - eva_clip, = load_evaclip_cls().load_eva_clip() - backend_cache.update_cache(ecache_key, 'eva_clip', (False, eva_clip)) - else: - self.error() - - # Apply PulID - if method is not None: - if "ApplyPulid" in ALL_NODE_CLASS_MAPPINGS: - cls = ALL_NODE_CLASS_MAPPINGS['ApplyPulid'] - model, = cls().apply_pulid(model, pulid=pulid_model, eva_clip=eva_clip, face_analysis=insightface_model, image=image, weight=weight, method=method, start_at=start_at, end_at=end_at, attn_mask=attn_mask) - else: - self.error() - else: - if "ApplyPulidAdvanced" in ALL_NODE_CLASS_MAPPINGS: - cls = ALL_NODE_CLASS_MAPPINGS['ApplyPulidAdvanced'] - model, = cls().apply_pulid(model, pulid=pulid_model, eva_clip=eva_clip, face_analysis=insightface_model, image=image, weight=weight, projection=projection, fidelity=fidelity, noise=noise, start_at=start_at, end_at=end_at, attn_mask=attn_mask) - else: - self.error() - - return (model,) - -class applyPulIDADV(applyPulID): - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "pulid_file": (folder_paths.get_filename_list("pulid"),), - "insightface": (["CPU", "CUDA", "ROCM"],), - "image": ("IMAGE",), - "weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 5.0, "step": 0.05}), - "projection": (["ortho_v2", "ortho", "none"], {"default":"ortho_v2"}), - "fidelity": ("INT", {"default": 8, "min": 0, "max": 32, "step": 1}), - "noise": ("FLOAT", {"default": 0.0, "min": -1.0, "max": 1.0, "step": 0.1}), - "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - }, - "optional": { - "attn_mask": ("MASK",), - }, - } - -# ---------------------------------------------------------------适配器 结束----------------------------------------------------------------------# - -#---------------------------------------------------------------预采样 开始----------------------------------------------------------------------# - -# 预采样设置(基础) -class samplerSettings: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS + new_schedulers,), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "optional": { - "image_to_latent": ("IMAGE",), - "latent": ("LATENT",), - }, - "hidden": - {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE", ) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, steps, cfg, sampler_name, scheduler, denoise, seed, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - # 图生图转换 - vae = pipe["vae"] - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - if image_to_latent is not None: - _, height, width, _ = image_to_latent.shape - if height == 1 and width == 1: - samples = pipe["samples"] - images = pipe["images"] - else: - samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - images = image_to_latent - elif latent is not None: - samples = latent - images = pipe["images"] - else: - samples = pipe["samples"] - images = pipe["images"] - - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": samples, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "add_noise": "enabled" - } - } - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - -# 预采样设置(高级) -class samplerSettingsAdvanced: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS + new_schedulers,), - "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), - "end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}), - "add_noise": (["enable", "disable"],), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - "return_with_leftover_noise": (["disable", "enable"], ), - }, - "optional": { - "image_to_latent": ("IMAGE",), - "latent": ("LATENT",) - }, - "hidden": - {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE", ) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, steps, cfg, sampler_name, scheduler, start_at_step, end_at_step, add_noise, seed, return_with_leftover_noise, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - # 图生图转换 - vae = pipe["vae"] - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - if image_to_latent is not None: - _, height, width, _ = image_to_latent.shape - if height == 1 and width == 1: - samples = pipe["samples"] - images = pipe["images"] - else: - samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - images = image_to_latent - elif latent is not None: - samples = latent - images = pipe["images"] - else: - samples = pipe["samples"] - images = pipe["images"] - - force_full_denoise = True - if return_with_leftover_noise == "enable": - force_full_denoise = False - - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": samples, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "start_step": start_at_step, - "last_step": end_at_step, - "denoise": 1.0, - "add_noise": add_noise, - "force_full_denoise": force_full_denoise - } - } - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - -# 预采样设置(噪声注入) -class samplerSettingsNoiseIn: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "factor": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step":0.01, "round": 0.01}), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS+new_schedulers,), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "optional": { - "optional_noise_seed": ("INT",{"forceInput": True}), - "optional_latent": ("LATENT",), - }, - "hidden": - {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE", ) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def slerp(self, val, low, high): - dims = low.shape - - low = low.reshape(dims[0], -1) - high = high.reshape(dims[0], -1) - - low_norm = low / torch.norm(low, dim=1, keepdim=True) - high_norm = high / torch.norm(high, dim=1, keepdim=True) - - low_norm[low_norm != low_norm] = 0.0 - high_norm[high_norm != high_norm] = 0.0 - - omega = torch.acos((low_norm * high_norm).sum(1)) - so = torch.sin(omega) - res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze( - 1) * high - - return res.reshape(dims) - - def prepare_mask(self, mask, shape): - mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), - size=(shape[2], shape[3]), mode="bilinear") - mask = mask.expand((-1, shape[1], -1, -1)) - if mask.shape[0] < shape[0]: - mask = mask.repeat((shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:shape[0]] - return mask - - def expand_mask(self, mask, expand, tapered_corners): - try: - import scipy - - c = 0 if tapered_corners else 1 - kernel = np.array([[c, 1, c], - [1, 1, 1], - [c, 1, c]]) - mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1])) - out = [] - for m in mask: - output = m.numpy() - for _ in range(abs(expand)): - if expand < 0: - output = scipy.ndimage.grey_erosion(output, footprint=kernel) - else: - output = scipy.ndimage.grey_dilation(output, footprint=kernel) - output = torch.from_numpy(output) - out.append(output) - - return torch.stack(out, dim=0) - except: - return None - - def settings(self, pipe, factor, steps, cfg, sampler_name, scheduler, denoise, seed, optional_noise_seed=None, optional_latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - latent = optional_latent if optional_latent is not None else pipe["samples"] - model = pipe["model"] - - # generate base noise - batch_size, _, height, width = latent["samples"].shape - generator = torch.manual_seed(seed) - base_noise = torch.randn((1, 4, height, width), dtype=torch.float32, device="cpu", generator=generator).repeat(batch_size, 1, 1, 1).cpu() - - # generate variation noise - if optional_noise_seed is None or optional_noise_seed == seed: - optional_noise_seed = seed+1 - generator = torch.manual_seed(optional_noise_seed) - variation_noise = torch.randn((batch_size, 4, height, width), dtype=torch.float32, device="cpu", - generator=generator).cpu() - - slerp_noise = self.slerp(factor, base_noise, variation_noise) - - end_at_step = steps # min(steps, end_at_step) - start_at_step = round(end_at_step - end_at_step * denoise) - - device = comfy.model_management.get_torch_device() - comfy.model_management.load_model_gpu(model) - model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device()) - sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name, - scheduler=scheduler, denoise=1.0, model_options=model.model_options) - sigmas = sampler.sigmas - sigma = sigmas[start_at_step] - sigmas[end_at_step] - sigma /= model.model.latent_format.scale_factor - sigma = sigma.cpu().numpy() - - work_latent = latent.copy() - work_latent["samples"] = latent["samples"].clone() + slerp_noise * sigma - - if "noise_mask" in latent: - noise_mask = self.prepare_mask(latent["noise_mask"], latent['samples'].shape) - work_latent["samples"] = noise_mask * work_latent["samples"] + (1-noise_mask) * latent["samples"] - work_latent['noise_mask'] = self.expand_mask(latent["noise_mask"].clone(), 5, True) - - if pipe is None: - pipe = {} - - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": work_latent, - "images": pipe['images'], - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "add_noise": "disable" - } - } - - return (new_pipe,) - -# 预采样设置(自定义) -import comfy_extras.nodes_custom_sampler as custom_samplers -from tqdm import trange -class samplerCustomSettings: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "pipe": ("PIPE_LINE",), - "guider": (['CFG','DualCFG','IP2P+DualCFG','Basic'],{"default":"Basic"}), - "cfg": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0}), - "cfg_negative": ("FLOAT", {"default": 1.5, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS + ['inversed_euler'],), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['karrasADV','exponentialADV','polyExponential', 'sdturbo', 'vp', 'alignYourSteps', 'gits'],), - "coeff": ("FLOAT", {"default": 1.20, "min": 0.80, "max": 1.50, "step": 0.05}), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), - "sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), - "rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False}), - "beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), - "beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), - "eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step": 0.0001, "round": False}), - "flip_sigmas": ("BOOLEAN", {"default": False}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "add_noise": (["enable", "disable"], {"default": "enable"}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "optional": { - "image_to_latent": ("IMAGE",), - "latent": ("LATENT",), - "optional_sampler":("SAMPLER",), - "optional_sigmas":("SIGMAS",), - }, - "hidden": - {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE", ) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, guider, cfg, cfg_negative, sampler_name, scheduler, coeff, steps, sigma_max, sigma_min, rho, beta_d, beta_min, eps_s, flip_sigmas, denoise, add_noise, seed, image_to_latent=None, latent=None, optional_sampler=None, optional_sigmas=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - - # 图生图转换 - vae = pipe["vae"] - model = pipe["model"] - positive = pipe['positive'] - negative = pipe['negative'] - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - - if image_to_latent is not None: - _, height, width, _ = image_to_latent.shape - if height == 1 and width == 1: - samples = pipe["samples"] - images = pipe["images"] - else: - if guider == "IP2P+DualCFG": - positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent) - samples = latent - else: - samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - images = image_to_latent - elif latent is not None: - if guider == "IP2P+DualCFG": - positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], latent=latent) - samples = latent - else: - samples = latent - images = pipe["images"] - else: - samples = pipe["samples"] - images = pipe["images"] - - - new_pipe = { - "model": model, - "positive": positive, - "negative": negative, - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": samples, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "middle": pipe['negative'], - "steps": steps, - "cfg": cfg, - "cfg_negative": cfg_negative, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "add_noise": add_noise, - "custom": { - "guider": guider, - "coeff": coeff, - "sigma_max": sigma_max, - "sigma_min": sigma_min, - "rho": rho, - "beta_d": beta_d, - "beta_min": beta_min, - "eps_s": beta_min, - "flip_sigmas": flip_sigmas - }, - "optional_sampler": optional_sampler, - "optional_sigmas": optional_sigmas - } - } - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - -# 预采样设置(SDTurbo) -from .libs.gradual_latent_hires_fix import sample_dpmpp_2s_ancestral, sample_dpmpp_2m_sde, sample_lcm, sample_euler_ancestral -class sdTurboSettings: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": { - "pipe": ("PIPE_LINE",), - "steps": ("INT", {"default": 1, "min": 1, "max": 10}), - "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.SAMPLER_NAMES,), - "eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), - "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), - "upscale_ratio": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 16.0, "step": 0.01, "round": False}), - "start_step": ("INT", {"default": 5, "min": 0, "max": 1000, "step": 1}), - "end_step": ("INT", {"default": 15, "min": 0, "max": 1000, "step": 1}), - "upscale_n_step": ("INT", {"default": 3, "min": 0, "max": 1000, "step": 1}), - "unsharp_kernel_size": ("INT", {"default": 3, "min": 1, "max": 21, "step": 1}), - "unsharp_sigma": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), - "unsharp_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, steps, cfg, sampler_name, eta, s_noise, upscale_ratio, start_step, end_step, upscale_n_step, unsharp_kernel_size, unsharp_sigma, unsharp_strength, seed, prompt=None, extra_pnginfo=None, my_unique_id=None): - model = pipe['model'] - # sigma - timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[:steps] - sigmas = model.model.model_sampling.sigma(timesteps) - sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) - - #sampler - sample_function = None - extra_options = { - "eta": eta, - "s_noise": s_noise, - "upscale_ratio": upscale_ratio, - "start_step": start_step, - "end_step": end_step, - "upscale_n_step": upscale_n_step, - "unsharp_kernel_size": unsharp_kernel_size, - "unsharp_sigma": unsharp_sigma, - "unsharp_strength": unsharp_strength, - } - if sampler_name == "euler_ancestral": - sample_function = sample_euler_ancestral - elif sampler_name == "dpmpp_2s_ancestral": - sample_function = sample_dpmpp_2s_ancestral - elif sampler_name == "dpmpp_2m_sde": - sample_function = sample_dpmpp_2m_sde - elif sampler_name == "lcm": - sample_function = sample_lcm - - if sample_function is not None: - unsharp_kernel_size = unsharp_kernel_size if unsharp_kernel_size % 2 == 1 else unsharp_kernel_size + 1 - extra_options["unsharp_kernel_size"] = unsharp_kernel_size - _sampler = comfy.samplers.KSAMPLER(sample_function, extra_options) - else: - _sampler = comfy.samplers.sampler_object(sampler_name) - extra_options = None - - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": pipe["samples"], - "images": pipe["images"], - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "extra_options": extra_options, - "sampler": _sampler, - "sigmas": sigmas, - "steps": steps, - "cfg": cfg, - "add_noise": "enabled" - } - } - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - - -# cascade预采样参数 -class cascadeSettings: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), - "decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "optional": { - "image_to_latent_c": ("IMAGE",), - "latent_c": ("LATENT",), - }, - "hidden":{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, encode_vae_name, decode_vae_name, steps, cfg, sampler_name, scheduler, denoise, seed, model=None, image_to_latent_c=None, latent_c=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - images, samples_c = None, None - samples = pipe['samples'] - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - - encode_vae_name = encode_vae_name if encode_vae_name is not None else pipe['loader_settings']['encode_vae_name'] - decode_vae_name = decode_vae_name if decode_vae_name is not None else pipe['loader_settings']['decode_vae_name'] - - if image_to_latent_c is not None: - if encode_vae_name != 'None': - encode_vae = easyCache.load_vae(encode_vae_name) - else: - encode_vae = pipe['vae'][0] - if "compression" not in pipe["loader_settings"]: - raise Exception("compression is not found") - compression = pipe["loader_settings"]['compression'] - width = image_to_latent_c.shape[-2] - height = image_to_latent_c.shape[-3] - out_width = (width // compression) * encode_vae.downscale_ratio - out_height = (height // compression) * encode_vae.downscale_ratio - - s = comfy.utils.common_upscale(image_to_latent_c.movedim(-1, 1), out_width, out_height, "bicubic", - "center").movedim(1, - -1) - c_latent = encode_vae.encode(s[:, :, :, :3]) - b_latent = torch.zeros([c_latent.shape[0], 4, height // 4, width // 4]) - - samples_c = {"samples": c_latent} - samples_c = RepeatLatentBatch().repeat(samples_c, batch_size)[0] - - samples_b = {"samples": b_latent} - samples_b = RepeatLatentBatch().repeat(samples_b, batch_size)[0] - samples = (samples_c, samples_b) - images = image_to_latent_c - elif latent_c is not None: - samples_c = latent_c - samples = (samples_c, samples[1]) - images = pipe["images"] - if samples_c is not None: - samples = (samples_c, samples[1]) - - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": samples, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "encode_vae_name": encode_vae_name, - "decode_vae_name": decode_vae_name, - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "add_noise": "enabled" - } - } - - sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - -# layerDiffusion预采样参数 -class layerDiffusionSettings: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - { - "pipe": ("PIPE_LINE",), - "method": ([LayerMethod.FG_ONLY_ATTN.value, LayerMethod.FG_ONLY_CONV.value, LayerMethod.EVERYTHING.value, LayerMethod.FG_TO_BLEND.value, LayerMethod.BG_TO_BLEND.value],), - "weight": ("FLOAT",{"default": 1.0, "min": -1, "max": 3, "step": 0.05},), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler"}), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS+ new_schedulers, {"default": "normal"}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "optional": { - "image": ("IMAGE",), - "blended_image": ("IMAGE",), - "mask": ("MASK",), - # "latent": ("LATENT",), - # "blended_latent": ("LATENT",), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def get_layer_diffusion_method(self, method, has_blend_latent): - method = LayerMethod(method) - if has_blend_latent: - if method == LayerMethod.BG_TO_BLEND: - method = LayerMethod.BG_BLEND_TO_FG - elif method == LayerMethod.FG_TO_BLEND: - method = LayerMethod.FG_BLEND_TO_BG - return method - - def settings(self, pipe, method, weight, steps, cfg, sampler_name, scheduler, denoise, seed, image=None, blended_image=None, mask=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None - vae = pipe["vae"] - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - - method = self.get_layer_diffusion_method(method, blend_samples is not None or blended_image is not None) - - if image is not None or "image" in pipe: - image = image if image is not None else pipe['image'] - if mask is not None: - print('inpaint') - samples, = VAEEncodeForInpaint().encode(vae, image, mask) - else: - samples = {"samples": vae.encode(image[:,:,:,:3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - images = image - elif "samp_images" in pipe: - samples = {"samples": vae.encode(pipe["samp_images"][:,:,:,:3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - images = pipe["samp_images"] - else: - if method not in [LayerMethod.FG_ONLY_ATTN, LayerMethod.FG_ONLY_CONV, LayerMethod.EVERYTHING]: - raise Exception("image is missing") - - samples = pipe["samples"] - images = pipe["images"] - - if method in [LayerMethod.BG_BLEND_TO_FG, LayerMethod.FG_BLEND_TO_BG]: - if blended_image is None and blend_samples is None: - raise Exception("blended_image is missing") - elif blended_image is not None: - blend_samples = {"samples": vae.encode(blended_image[:,:,:,:3])} - blend_samples = RepeatLatentBatch().repeat(blend_samples, batch_size)[0] - - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": samples, - "blend_samples": blend_samples, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "add_noise": "enabled", - "layer_diffusion_method": method, - "layer_diffusion_weight": weight, - } - } - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - -# 预采样设置(layerDiffuse附加) -class layerDiffusionSettingsADDTL: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - { - "pipe": ("PIPE_LINE",), - "foreground_prompt": ("STRING", {"default": "", "placeholder": "Foreground Additional Prompt", "multiline": True}), - "background_prompt": ("STRING", {"default": "", "placeholder": "Background Additional Prompt", "multiline": True}), - "blended_prompt": ("STRING", {"default": "", "placeholder": "Blended Additional Prompt", "multiline": True}), - }, - "optional": { - "optional_fg_cond": ("CONDITIONING",), - "optional_bg_cond": ("CONDITIONING",), - "optional_blended_cond": ("CONDITIONING",), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, foreground_prompt, background_prompt, blended_prompt, optional_fg_cond=None, optional_bg_cond=None, optional_blended_cond=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - fg_cond, bg_cond, blended_cond = None, None, None - clip = pipe['clip'] - if optional_fg_cond is not None: - fg_cond = optional_fg_cond - elif foreground_prompt != "": - fg_cond, = CLIPTextEncode().encode(clip, foreground_prompt) - if optional_bg_cond is not None: - bg_cond = optional_bg_cond - elif background_prompt != "": - bg_cond, = CLIPTextEncode().encode(clip, background_prompt) - if optional_blended_cond is not None: - blended_cond = optional_blended_cond - elif blended_prompt != "": - blended_cond, = CLIPTextEncode().encode(clip, blended_prompt) - - new_pipe = { - **pipe, - "loader_settings": { - **pipe["loader_settings"], - "layer_diffusion_cond": (fg_cond, bg_cond, blended_cond) - } - } - - del pipe - - return (new_pipe,) - -# 预采样设置(动态CFG) -from .libs.dynthres_core import DynThresh -class dynamicCFGSettings: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "cfg_mode": (DynThresh.Modes,), - "cfg_scale_min": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.5}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS+new_schedulers,), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - "optional":{ - "image_to_latent": ("IMAGE",), - "latent": ("LATENT",) - }, - "hidden": - {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - - FUNCTION = "settings" - CATEGORY = "EasyUse/PreSampling" - - def settings(self, pipe, steps, cfg, cfg_mode, cfg_scale_min,sampler_name, scheduler, denoise, seed, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - - - dynamic_thresh = DynThresh(7.0, 1.0,"CONSTANT", 0, cfg_mode, cfg_scale_min, 0, 0, 999, False, - "MEAN", "AD", 1) - - def sampler_dyn_thresh(args): - input = args["input"] - cond = input - args["cond"] - uncond = input - args["uncond"] - cond_scale = args["cond_scale"] - time_step = args["timestep"] - dynamic_thresh.step = 999 - time_step[0] - - return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None) - - model = pipe['model'] - - m = model.clone() - m.set_model_sampler_cfg_function(sampler_dyn_thresh) - - # 图生图转换 - vae = pipe["vae"] - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - if image_to_latent is not None: - samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - images = image_to_latent - elif latent is not None: - samples = RepeatLatentBatch().repeat(latent, batch_size)[0] - images = pipe["images"] - else: - samples = pipe["samples"] - images = pipe["images"] - - new_pipe = { - "model": m, - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": pipe['vae'], - "clip": pipe['clip'], - - "samples": samples, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise - }, - } - - del pipe - - return {"ui": {"value": [seed]}, "result": (new_pipe,)} - -# 动态CFG -class dynamicThresholdingFull: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model": ("MODEL",), - "mimic_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.5}), - "threshold_percentile": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "mimic_mode": (DynThresh.Modes,), - "mimic_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), - "cfg_mode": (DynThresh.Modes,), - "cfg_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), - "sched_val": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), - "separate_feature_channels": (["enable", "disable"],), - "scaling_startpoint": (DynThresh.Startpoints,), - "variability_measure": (DynThresh.Variabilities,), - "interpolate_phi": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - } - } - - RETURN_TYPES = ("MODEL",) - FUNCTION = "patch" - CATEGORY = "EasyUse/PreSampling" - - def patch(self, model, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, - sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi): - dynamic_thresh = DynThresh(mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, - cfg_scale_min, sched_val, 0, 999, separate_feature_channels == "enable", - scaling_startpoint, variability_measure, interpolate_phi) - - def sampler_dyn_thresh(args): - input = args["input"] - cond = input - args["cond"] - uncond = input - args["uncond"] - cond_scale = args["cond_scale"] - time_step = args["timestep"] - dynamic_thresh.step = 999 - time_step[0] - - return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None) - - m = model.clone() - m.set_model_sampler_cfg_function(sampler_dyn_thresh) - return (m,) - -#---------------------------------------------------------------预采样参数 结束---------------------------------------------------------------------- - -#---------------------------------------------------------------采样器 开始---------------------------------------------------------------------- - -# 完整采样器 -from .libs.chooser import ChooserMessage, ChooserCancelled -class samplerFull: - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS+new_schedulers,), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - "model": ("MODEL",), - "positive": ("CONDITIONING",), - "negative": ("CONDITIONING",), - "latent": ("LATENT",), - "vae": ("VAE",), - "clip": ("CLIP",), - "xyPlot": ("XYPLOT",), - "image": ("IMAGE",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "INT",) - RETURN_NAMES = ("pipe", "image", "model", "positive", "negative", "latent", "vae", "clip", "seed",) - OUTPUT_NODE = True - FUNCTION = "run" - CATEGORY = "EasyUse/Sampler" - - def ip2p(self, positive, negative, vae=None, pixels=None, latent=None): - if latent is not None: - concat_latent = latent - else: - x = (pixels.shape[1] // 8) * 8 - y = (pixels.shape[2] // 8) * 8 - - if pixels.shape[1] != x or pixels.shape[2] != y: - x_offset = (pixels.shape[1] % 8) // 2 - y_offset = (pixels.shape[2] % 8) // 2 - pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] - - concat_latent = vae.encode(pixels) - - out_latent = {} - out_latent["samples"] = torch.zeros_like(concat_latent) - - out = [] - for conditioning in [positive, negative]: - c = [] - for t in conditioning: - d = t[1].copy() - d["concat_latent_image"] = concat_latent - n = [t[0], d] - c.append(n) - out.append(c) - return (out[0], out[1], out_latent) - - def get_inversed_euler_sampler(self): - @torch.no_grad() - def sample_inversed_euler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0.,s_tmax=float('inf'), s_noise=1.): - """Implements Algorithm 2 (Euler steps) from Karras et al. (2022).""" - extra_args = {} if extra_args is None else extra_args - s_in = x.new_ones([x.shape[0]]) - for i in trange(1, len(sigmas), disable=disable): - sigma_in = sigmas[i - 1] - - if i == 1: - sigma_t = sigmas[i] - else: - sigma_t = sigma_in - - denoised = model(x, sigma_t * s_in, **extra_args) - - if i == 1: - d = (x - denoised) / (2 * sigmas[i]) - else: - d = (x - denoised) / sigmas[i - 1] - - dt = sigmas[i] - sigmas[i - 1] - x = x + d * dt - if callback is not None: - callback( - {'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) - return x / sigmas[-1] - - ksampler = comfy.samplers.KSAMPLER(sample_inversed_euler) - return (ksampler,) - - def get_custom_cls(self, sampler_name): - try: - cls = custom_samplers.__dict__[sampler_name] - return cls() - except: - raise Exception(f"Custom sampler {sampler_name} not found, Please updated your ComfyUI") - - def add_model_patch_option(self, model): - if 'transformer_options' not in model.model_options: - model.model_options['transformer_options'] = {} - to = model.model_options['transformer_options'] - if "model_patch" not in to: - to["model_patch"] = {} - return to - - def get_sampler_custom(self, model, positive, negative, seed, loader_settings): - _guider = None - middle = loader_settings['middle'] if "middle" in loader_settings else negative - steps = loader_settings['steps'] if "steps" in loader_settings else 20 - cfg = loader_settings['cfg'] if "cfg" in loader_settings else 8.0 - cfg_negative = loader_settings['cfg_negative'] if "cfg_negative" in loader_settings else 8.0 - sampler_name = loader_settings['sampler_name'] if "sampler_name" in loader_settings else "euler" - scheduler = loader_settings['scheduler'] if "scheduler" in loader_settings else "normal" - guider = loader_settings['custom']['guider'] if "guider" in loader_settings['custom'] else "CFG" - beta_d = loader_settings['custom']['beta_d'] if "beta_d" in loader_settings['custom'] else 0.1 - beta_min = loader_settings['custom']['beta_min'] if "beta_min" in loader_settings['custom'] else 0.1 - eps_s = loader_settings['custom']['eps_s'] if "eps_s" in loader_settings['custom'] else 0.1 - sigma_max = loader_settings['custom']['sigma_max'] if "sigma_max" in loader_settings['custom'] else 14.61 - sigma_min = loader_settings['custom']['sigma_min'] if "sigma_min" in loader_settings['custom'] else 0.03 - rho = loader_settings['custom']['rho'] if "rho" in loader_settings['custom'] else 7.0 - coeff = loader_settings['custom']['coeff'] if "coeff" in loader_settings['custom'] else 1.2 - flip_sigmas = loader_settings['custom']['flip_sigmas'] if "flip_sigmas" in loader_settings['custom'] else False - denoise = loader_settings['denoise'] if "denoise" in loader_settings else 1.0 - add_noise = loader_settings['add_noise'] if "add_noise" in loader_settings else "enable" - optional_sigmas = loader_settings['optional_sigmas'] if "optional_sigmas" in loader_settings else None - optional_sampler = loader_settings['optional_sampler'] if "optional_sampler" in loader_settings else None - - # sigmas - if optional_sigmas is not None: - sigmas = optional_sigmas - else: - if scheduler == 'vp': - sigmas, = self.get_custom_cls('VPScheduler').get_sigmas(steps, beta_d, beta_min, eps_s) - elif scheduler == 'karrasADV': - sigmas, = self.get_custom_cls('KarrasScheduler').get_sigmas(steps, sigma_max, sigma_min, rho) - elif scheduler == 'exponentialADV': - sigmas, = self.get_custom_cls('ExponentialScheduler').get_sigmas(steps, sigma_max, sigma_min) - elif scheduler == 'polyExponential': - sigmas, = self.get_custom_cls('PolyexponentialScheduler').get_sigmas(steps, sigma_max, sigma_min, rho) - elif scheduler == 'sdturbo': - sigmas, = self.get_custom_cls('SDTurboScheduler').get_sigmas(model, steps, denoise) - elif scheduler == 'alignYourSteps': - model_type = get_sd_version(model) - if model_type == 'unknown': - model_type = 'sdxl' - sigmas, = alignYourStepsScheduler().get_sigmas(model_type.upper(), steps, denoise) - elif scheduler == 'gits': - sigmas, = gitsScheduler().get_sigmas(coeff, steps, denoise) - else: - sigmas, = self.get_custom_cls('BasicScheduler').get_sigmas(model, scheduler, steps, denoise) - - # filp_sigmas - if flip_sigmas: - sigmas, = self.get_custom_cls('FlipSigmas').get_sigmas(sigmas) - - ####################################################################################### - # brushnet - to = None - transformer_options = model.model_options['transformer_options'] if "transformer_options" in model.model_options else {} - if 'model_patch' in transformer_options and 'brushnet' in transformer_options['model_patch']: - to = self.add_model_patch_option(model) - mp = to['model_patch'] - if isinstance(model.model.model_config, comfy.supported_models.SD15): - mp['SDXL'] = False - elif isinstance(model.model.model_config, comfy.supported_models.SDXL): - mp['SDXL'] = True - else: - print('Base model type: ', type(model.model.model_config)) - raise Exception("Unsupported model type: ", type(model.model.model_config)) - - mp['all_sigmas'] = sigmas - mp['unet'] = model.model.diffusion_model - mp['step'] = 0 - mp['total_steps'] = 1 - ####################################################################################### - # guider - if cfg > 0 and get_sd_version(model) == 'flux': - c = [] - for t in positive: - n = [t[0], t[1]] - n[1]['guidance'] = cfg - c.append(n) - positive = c - - if guider == 'CFG': - _guider, = self.get_custom_cls('CFGGuider').get_guider(model, positive, negative, cfg) - elif guider in ['DualCFG', 'IP2P+DualCFG']: - _guider, = self.get_custom_cls('DualCFGGuider').get_guider(model, positive, middle, - negative, cfg, cfg_negative) - else: - _guider, = self.get_custom_cls('BasicGuider').get_guider(model, positive) - - # sampler - if optional_sampler: - _sampler = optional_sampler - else: - if sampler_name == 'inversed_euler': - _sampler, = self.get_inversed_euler_sampler() - else: - _sampler, = self.get_custom_cls('KSamplerSelect').get_sampler(sampler_name) - - # noise - if add_noise == 'disable': - noise, = self.get_custom_cls('DisableNoise').get_noise() - else: - noise, = self.get_custom_cls('RandomNoise').get_noise(seed) - - return (noise, _guider, _sampler, sigmas) - - def run(self, pipe, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed=None, model=None, positive=None, negative=None, latent=None, vae=None, clip=None, xyPlot=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False, downscale_options=None, image=None): - - samp_model = model if model is not None else pipe["model"] - samp_positive = positive if positive is not None else pipe["positive"] - samp_negative = negative if negative is not None else pipe["negative"] - samp_samples = latent if latent is not None else pipe["samples"] - samp_vae = vae if vae is not None else pipe["vae"] - samp_clip = clip if clip is not None else pipe["clip"] - - samp_seed = seed if seed is not None else pipe['seed'] - - samp_custom = pipe["loader_settings"] if "custom" in pipe["loader_settings"] else None - - steps = steps if steps is not None else pipe['loader_settings']['steps'] - start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0 - last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000 - cfg = cfg if cfg is not None else pipe['loader_settings']['cfg'] - sampler_name = sampler_name if sampler_name is not None else pipe['loader_settings']['sampler_name'] - scheduler = scheduler if scheduler is not None else pipe['loader_settings']['scheduler'] - denoise = denoise if denoise is not None else pipe['loader_settings']['denoise'] - add_noise = pipe['loader_settings']['add_noise'] if 'add_noise' in pipe['loader_settings'] else 'enabled' - force_full_denoise = pipe['loader_settings']['force_full_denoise'] if 'force_full_denoise' in pipe['loader_settings'] else True - noise_device = 'GPU' if 'a1111_prompt_style' in pipe['loader_settings'] and pipe['loader_settings']['a1111_prompt_style'] else 'CPU' - - if image is not None and latent is None: - samp_samples = {"samples": samp_vae.encode(image[:, :, :, :3])} - - disable_noise = False - if add_noise == "disable": - disable_noise = True - - def downscale_model_unet(samp_model): - # 获取Unet参数 - if "PatchModelAddDownscale" in ALL_NODE_CLASS_MAPPINGS: - cls = ALL_NODE_CLASS_MAPPINGS['PatchModelAddDownscale'] - # 自动收缩Unet - if downscale_options['downscale_factor'] is None: - unet_config = samp_model.model.model_config.unet_config - if unet_config is not None and "samples" in samp_samples: - height = samp_samples['samples'].shape[2] * 8 - width = samp_samples['samples'].shape[3] * 8 - context_dim = unet_config.get('context_dim') - longer_side = width if width > height else height - if context_dim is not None and longer_side > context_dim: - width_downscale_factor = float(width / context_dim) - height_downscale_factor = float(height / context_dim) - if width_downscale_factor > 1.75: - log_node_warn("Patch model unet add downscale...") - log_node_warn("Downscale factor:" + str(width_downscale_factor)) - (samp_model,) = cls().patch(samp_model, downscale_options['block_number'], width_downscale_factor, 0, 0.35, True, "bicubic", - "bicubic") - elif height_downscale_factor > 1.25: - log_node_warn("Patch model unet add downscale....") - log_node_warn("Downscale factor:" + str(height_downscale_factor)) - (samp_model,) = cls().patch(samp_model, downscale_options['block_number'], height_downscale_factor, 0, 0.35, True, "bicubic", - "bicubic") - else: - cls = ALL_NODE_CLASS_MAPPINGS['PatchModelAddDownscale'] - log_node_warn("Patch model unet add downscale....") - log_node_warn("Downscale factor:" + str(downscale_options['downscale_factor'])) - (samp_model,) = cls().patch(samp_model, downscale_options['block_number'], downscale_options['downscale_factor'], downscale_options['start_percent'], downscale_options['end_percent'], downscale_options['downscale_after_skip'], downscale_options['downscale_method'], downscale_options['upscale_method']) - return samp_model - - def process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, - samp_negative, - steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, - image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, - preview_latent, force_full_denoise=force_full_denoise, disable_noise=disable_noise, samp_custom=None, noise_device='cpu'): - - # LayerDiffusion - layerDiffuse = None - samp_blend_samples = None - layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None - if layer_diffusion_method is not None: - layerDiffuse = LayerDiffuse() - samp_blend_samples = pipe["blend_samples"] if "blend_samples" in pipe else None - additional_cond = pipe["loader_settings"]['layer_diffusion_cond'] if "layer_diffusion_cond" in pipe[ - 'loader_settings'] else (None, None, None) - method = layerDiffuse.get_layer_diffusion_method(pipe['loader_settings']['layer_diffusion_method'], - samp_blend_samples is not None) - - images = pipe["images"] if "images" in pipe else None - weight = pipe['loader_settings']['layer_diffusion_weight'] if 'layer_diffusion_weight' in pipe[ - 'loader_settings'] else 1.0 - samp_model, samp_positive, samp_negative = layerDiffuse.apply_layer_diffusion(samp_model, method, weight, - samp_samples, samp_blend_samples, - samp_positive, samp_negative, - images, additional_cond) - resolution = pipe['loader_settings']['resolution'] if 'resolution' in pipe['loader_settings'] else "自定义 X 自定义" - empty_latent_width = pipe['loader_settings']['empty_latent_width'] if 'empty_latent_width' in pipe['loader_settings'] else 512 - empty_latent_height = pipe['loader_settings']['empty_latent_height'] if 'empty_latent_height' in pipe['loader_settings'] else 512 - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - samp_samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size) - - # Downscale Model Unet - if samp_model is not None and downscale_options is not None: - samp_model = downscale_model_unet(samp_model) - # 推理初始时间 - start_time = int(time.time() * 1000) - # 开始推理 - if samp_custom is not None: - noise, _guider, _sampler, sigmas = self.get_sampler_custom(samp_model, samp_positive, samp_negative, samp_seed, samp_custom) - samp_samples, samp_blend_samples = sampler.custom_advanced_ksampler(noise, _guider, _sampler, sigmas, samp_samples, preview_latent=preview_latent) - elif scheduler == 'align_your_steps': - model_type = get_sd_version(samp_model) - if model_type == 'unknown': - model_type = 'sdxl' - sigmas, = alignYourStepsScheduler().get_sigmas(model_type.upper(), steps, denoise) - _sampler = comfy.samplers.sampler_object(sampler_name) - samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, _sampler, sigmas, samp_positive, samp_negative, samp_samples, disable_noise=disable_noise, preview_latent=preview_latent, noise_device=noise_device) - elif scheduler == 'gits': - sigmas, = gitsScheduler().get_sigmas(coeff=1.2, steps=steps, denoise=denoise) - _sampler = comfy.samplers.sampler_object(sampler_name) - samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, _sampler, sigmas, samp_positive, samp_negative, samp_samples, disable_noise=disable_noise, preview_latent=preview_latent, noise_device=noise_device) - else: - samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, samp_positive, samp_negative, samp_samples, denoise=denoise, preview_latent=preview_latent, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, disable_noise=disable_noise, noise_device=noise_device) - # 推理结束时间 - end_time = int(time.time() * 1000) - latent = samp_samples["samples"] - - # 解码图片 - if image_output == 'None': - samp_images, new_images, alpha, results = None, None, None, None - spent_time = 'Diffusion:' + str((end_time - start_time) / 1000) + '″' - else: - if tile_size is not None: - samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, ) - else: - samp_images = samp_vae.decode(latent).cpu() - if len(samp_images.shape) == 5: # Combine batches - samp_images = samp_images.reshape(-1, samp_images.shape[-3], samp_images.shape[-2], samp_images.shape[-1]) - # LayerDiffusion Decode - if layerDiffuse is not None: - new_images, samp_images, alpha = layerDiffuse.layer_diffusion_decode(layer_diffusion_method, latent, samp_blend_samples, samp_images, samp_model) - else: - new_images = samp_images - alpha = None - - # 推理总耗时(包含解码) - end_decode_time = int(time.time() * 1000) - spent_time = 'Diffusion:' + str((end_time-start_time)/1000)+'″, VAEDecode:' + str((end_decode_time-end_time)/1000)+'″ ' - - results = easySave(new_images, save_prefix, image_output, prompt, extra_pnginfo) - - new_pipe = { - **pipe, - "positive": samp_positive, - "negative": samp_negative, - "vae": samp_vae, - "clip": samp_clip, - - "samples": samp_samples, - "blend_samples": samp_blend_samples, - "images": new_images, - "samp_images": samp_images, - "alpha": alpha, - "seed": samp_seed, - - "loader_settings": { - **pipe["loader_settings"], - "spent_time": spent_time - } - } - - del pipe - - if image_output == 'Preview&Choose': - if my_unique_id not in ChooserMessage.stash: - ChooserMessage.stash[my_unique_id] = {} - my_stash = ChooserMessage.stash[my_unique_id] - - PromptServer.instance.send_sync("easyuse-image-choose", {"id": my_unique_id, "urls": results}) - # wait for selection - try: - selections = ChooserMessage.waitForMessage(my_unique_id, asList=True) - samples = samp_samples['samples'] - samples = [samples[x] for x in selections if x >= 0] if len(selections) > 1 else [samples[0]] - new_images = [new_images[x] for x in selections if x >= 0] if len(selections) > 1 else [new_images[0]] - samp_images = [samp_images[x] for x in selections if x >= 0] if len(selections) > 1 else [samp_images[0]] - new_images = torch.stack(new_images, dim=0) - samp_images = torch.stack(samp_images, dim=0) - samples = torch.stack(samples, dim=0) - samp_samples = {"samples": samples} - new_pipe['samples'] = samp_samples - new_pipe['loader_settings']['batch_size'] = len(new_images) - except ChooserCancelled: - raise comfy.model_management.InterruptProcessingException() - - new_pipe['images'] = new_images - new_pipe['samp_images'] = samp_images - - return {"ui": {"images": results}, - "result": sampler.get_output(new_pipe,)} - - if image_output in ("Hide", "Hide&Save", "None"): - return {"ui":{}, "result":sampler.get_output(new_pipe,)} - - if image_output in ("Sender", "Sender&Save"): - PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) - - return {"ui": {"images": results}, - "result": sampler.get_output(new_pipe,)} - - def process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, - steps, cfg, sampler_name, scheduler, denoise, - image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom, noise_device): - - sampleXYplot = easyXYPlot(xyPlot, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id, sampler, easyCache) - - if not sampleXYplot.validate_xy_plot(): - return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, - samp_negative, steps, 0, 10000, cfg, - sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, - extra_pnginfo, my_unique_id, preview_latent, samp_custom=samp_custom, noise_device=noise_device) - - # Downscale Model Unet - if samp_model is not None and downscale_options is not None: - samp_model = downscale_model_unet(samp_model) - - blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None - layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None - - plot_image_vars = { - "x_node_type": sampleXYplot.x_node_type, "y_node_type": sampleXYplot.y_node_type, - "lora_name": pipe["loader_settings"]["lora_name"] if "lora_name" in pipe["loader_settings"] else None, - "lora_model_strength": pipe["loader_settings"]["lora_model_strength"] if "model_strength" in pipe["loader_settings"] else None, - "lora_clip_strength": pipe["loader_settings"]["lora_clip_strength"] if "clip_strength" in pipe["loader_settings"] else None, - "lora_stack": pipe["loader_settings"]["lora_stack"] if "lora_stack" in pipe["loader_settings"] else None, - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "seed": samp_seed, - "images": pipe['images'], - - "model": samp_model, "vae": samp_vae, "clip": samp_clip, "positive_cond": samp_positive, - "negative_cond": samp_negative, - "noise_device":noise_device, - - "ckpt_name": pipe['loader_settings']['ckpt_name'] if "ckpt_name" in pipe["loader_settings"] else None, - "vae_name": pipe['loader_settings']['vae_name'] if "vae_name" in pipe["loader_settings"] else None, - "clip_skip": pipe['loader_settings']['clip_skip'] if "clip_skip" in pipe["loader_settings"] else None, - "positive": pipe['loader_settings']['positive'] if "positive" in pipe["loader_settings"] else None, - "positive_token_normalization": pipe['loader_settings']['positive_token_normalization'] if "positive_token_normalization" in pipe["loader_settings"] else None, - "positive_weight_interpretation": pipe['loader_settings']['positive_weight_interpretation'] if "positive_weight_interpretation" in pipe["loader_settings"] else None, - "negative": pipe['loader_settings']['negative'] if "negative" in pipe["loader_settings"] else None, - "negative_token_normalization": pipe['loader_settings']['negative_token_normalization'] if "negative_token_normalization" in pipe["loader_settings"] else None, - "negative_weight_interpretation": pipe['loader_settings']['negative_weight_interpretation'] if "negative_weight_interpretation" in pipe["loader_settings"] else None, - } - - if "models" in pipe["loader_settings"]: - plot_image_vars["models"] = pipe["loader_settings"]["models"] - if "vae_use" in pipe["loader_settings"]: - plot_image_vars["vae_use"] = pipe["loader_settings"]["vae_use"] - if "a1111_prompt_style" in pipe["loader_settings"]: - plot_image_vars["a1111_prompt_style"] = pipe["loader_settings"]["a1111_prompt_style"] - if "cnet_stack" in pipe["loader_settings"]: - plot_image_vars["cnet"] = pipe["loader_settings"]["cnet_stack"] - if "positive_cond_stack" in pipe["loader_settings"]: - plot_image_vars["positive_cond_stack"] = pipe["loader_settings"]["positive_cond_stack"] - if "negative_cond_stack" in pipe["loader_settings"]: - plot_image_vars["negative_cond_stack"] = pipe["loader_settings"]["negative_cond_stack"] - if layer_diffusion_method: - plot_image_vars["layer_diffusion_method"] = layer_diffusion_method - if "layer_diffusion_weight" in pipe["loader_settings"]: - plot_image_vars["layer_diffusion_weight"] = pipe['loader_settings']['layer_diffusion_weight'] - if "layer_diffusion_cond" in pipe["loader_settings"]: - plot_image_vars["layer_diffusion_cond"] = pipe['loader_settings']['layer_diffusion_cond'] - if "empty_samples" in pipe["loader_settings"]: - plot_image_vars["empty_samples"] = pipe["loader_settings"]['empty_samples'] - - latent_image = sampleXYplot.get_latent(pipe["samples"]) - latents_plot = sampleXYplot.get_labels_and_sample(plot_image_vars, latent_image, preview_latent, start_step, - last_step, force_full_denoise, disable_noise) - - samp_samples = {"samples": latents_plot} - - images, image_list = sampleXYplot.plot_images_and_labels() - - # Generate output_images - output_images = torch.stack([tensor.squeeze() for tensor in image_list]) - - if layer_diffusion_method is not None: - layerDiffuse = LayerDiffuse() - new_images, samp_images, alpha = layerDiffuse.layer_diffusion_decode(layer_diffusion_method, latents_plot, blend_samples, - output_images, samp_model) - else: - new_images = output_images - samp_images = output_images - alpha = None - - results = easySave(images, save_prefix, image_output, prompt, extra_pnginfo) - - new_pipe = { - **pipe, - "positive": samp_positive, - "negative": samp_negative, - "vae": samp_vae, - "clip": samp_clip, - - "samples": samp_samples, - "blend_samples": blend_samples, - "samp_images": samp_images, - "images": new_images, - "seed": samp_seed, - "alpha": alpha, - - "loader_settings": pipe["loader_settings"], - } - - del pipe - - if image_output in ("Hide", "Hide&Save", "None"): - return {"ui": {}, "result": sampler.get_output(new_pipe,)} - - return {"ui": {"images": results}, "result": sampler.get_output(new_pipe)} - - preview_latent = True - if image_output in ("Hide", "Hide&Save", "None"): - preview_latent = False - - xyplot_id = next((x for x in prompt if "XYPlot" in str(prompt[x]["class_type"])), None) - if xyplot_id is None: - xyPlot = None - else: - xyPlot = pipe["loader_settings"]["xyplot"] if "xyplot" in pipe["loader_settings"] else xyPlot - - # Fooocus model patch - model_options = samp_model.model_options if samp_model.model_options else samp_model.model.model_options - transformer_options = model_options["transformer_options"] if "transformer_options" in model_options else {} - if "fooocus" in transformer_options: - from .fooocus import applyFooocusInpaint - del transformer_options["fooocus"] - with applyFooocusInpaint(): - if xyPlot is not None: - return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, - samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, - link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, - preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom, noise_device) - else: - return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, - samp_positive, samp_negative, steps, start_step, last_step, cfg, - sampler_name, scheduler, denoise, image_output, link_id, save_prefix, - tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, - force_full_denoise, disable_noise, samp_custom, noise_device) - else: - if xyPlot is not None: - return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom, noise_device) - else: - return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, force_full_denoise, disable_noise, samp_custom, noise_device) - -# 简易采样器 -class samplerSimple(samplerFull): - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - - RETURN_TYPES = ("PIPE_LINE", "IMAGE",) - RETURN_NAMES = ("pipe", "image",) - OUTPUT_NODE = True - FUNCTION = "simple" - CATEGORY = "EasyUse/Sampler" - - def simple(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - - return super().run(pipe, None, None, None, None, None, image_output, link_id, save_prefix, - None, model, None, None, None, None, None, None, - None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) - -class samplerSimpleCustom(samplerFull): - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "None"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - - RETURN_TYPES = ("PIPE_LINE", "LATENT", "LATENT", "IMAGE") - RETURN_NAMES = ("pipe", "output", "denoised_output", "image") - OUTPUT_NODE = True - FUNCTION = "simple" - CATEGORY = "EasyUse/Sampler" - - def simple(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - - result = super().run(pipe, None, None, None, None, None, image_output, link_id, save_prefix, - None, model, None, None, None, None, None, None, - None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) - - pipe = result["result"][0] if "result" in result else None - - return ({"ui": result['ui'], "result": (pipe, pipe["samples"], pipe["blend_samples"], pipe["images"])}) - -# 简易采样器 (Tiled) -class samplerSimpleTiled(samplerFull): - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64}), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}) - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": { - "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE",) - RETURN_NAMES = ("pipe", "image",) - OUTPUT_NODE = True - FUNCTION = "tiled" - CATEGORY = "EasyUse/Sampler" - - def tiled(self, pipe, tile_size=512, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - - return super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, - None, model, None, None, None, None, None, None, - tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) - -# 简易采样器 (LayerDiffusion) -class samplerSimpleLayerDiffusion(samplerFull): - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"], {"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}) - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": { - "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "MASK") - RETURN_NAMES = ("pipe", "final_image", "original_image", "alpha") - OUTPUT_NODE = True - OUTPUT_IS_LIST = (False, False, False, True) - FUNCTION = "layerDiffusion" - CATEGORY = "EasyUse/Sampler" - - def layerDiffusion(self, pipe, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - - result = super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, - None, model, None, None, None, None, None, None, - None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) - pipe = result["result"][0] if "result" in result else None - return ({"ui":result['ui'], "result":(pipe, pipe["images"], pipe["samp_images"], pipe["alpha"])}) - -# 简易采样器(收缩Unet) -class samplerSimpleDownscaleUnet(samplerFull): - - upscale_methods = ["bicubic", "nearest-exact", "bilinear", "area", "bislerp"] - - @classmethod - def INPUT_TYPES(s): - return {"required": - {"pipe": ("PIPE_LINE",), - "downscale_mode": (["None", "Auto", "Custom"],{"default": "Auto"}), - "block_number": ("INT", {"default": 3, "min": 1, "max": 32, "step": 1}), - "downscale_factor": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 9.0, "step": 0.001}), - "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "end_percent": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.001}), - "downscale_after_skip": ("BOOLEAN", {"default": True}), - "downscale_method": (s.upscale_methods,), - "upscale_method": (s.upscale_methods,), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - - RETURN_TYPES = ("PIPE_LINE", "IMAGE",) - RETURN_NAMES = ("pipe", "image",) - OUTPUT_NODE = True - FUNCTION = "downscale_unet" - CATEGORY = "EasyUse/Sampler" - - def downscale_unet(self, pipe, downscale_mode, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - downscale_options = None - if downscale_mode == 'Auto': - downscale_options = { - "block_number": block_number, - "downscale_factor": None, - "start_percent": 0, - "end_percent":0.35, - "downscale_after_skip": True, - "downscale_method": "bicubic", - "upscale_method": "bicubic" - } - elif downscale_mode == 'Custom': - downscale_options = { - "block_number": block_number, - "downscale_factor": downscale_factor, - "start_percent": start_percent, - "end_percent": end_percent, - "downscale_after_skip": downscale_after_skip, - "downscale_method": downscale_method, - "upscale_method": upscale_method - } - - return super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, - None, model, None, None, None, None, None, None, - tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise, downscale_options) -# 简易采样器 (内补) -class samplerSimpleInpainting(samplerFull): - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - "additional": (["None", "InpaintModelCond", "Differential Diffusion", "Fooocus Inpaint", "Fooocus Inpaint + DD", "Brushnet Random", "Brushnet Random + DD", "Brushnet Segmentation", "Brushnet Segmentation + DD"],{"default": "None"}) - }, - "optional": { - "model": ("MODEL",), - "mask": ("MASK",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE", "VAE") - RETURN_NAMES = ("pipe", "image", "vae") - OUTPUT_NODE = True - FUNCTION = "inpainting" - CATEGORY = "EasyUse/Sampler" - - def dd(self, model, positive, negative, pixels, vae, mask): - positive, negative, latent = InpaintModelConditioning().encode(positive, negative, pixels, vae, mask) - cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion'] - if cls is not None: - model, = cls().apply(model) - else: - raise Exception("Differential Diffusion not found,please update comfyui") - return positive, negative, latent, model - - def get_brushnet_model(self, type, model): - model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1' - if type == 'random': - brush_model = BRUSHNET_MODELS['random_mask'][model_type]['model_url'] - if model_type == 'sdxl': - pattern = 'brushnet.random.mask.sdxl.*.(safetensors|bin)$' - else: - pattern = 'brushnet.random.mask.*.(safetensors|bin)$' - elif type == 'segmentation': - brush_model = BRUSHNET_MODELS['segmentation_mask'][model_type]['model_url'] - if model_type == 'sdxl': - pattern = 'brushnet.segmentation.mask.sdxl.*.(safetensors|bin)$' - else: - pattern = 'brushnet.segmentation.mask.*.(safetensors|bin)$' - - - brushfile = [e for e in folder_paths.get_filename_list('inpaint') if re.search(pattern, e, re.IGNORECASE)] - brushname = brushfile[0] if brushfile else None - if not brushname: - from urllib.parse import urlparse - get_local_filepath(brush_model, INPAINT_DIR, cache_dir='/stable-diffusion-cache/models/inpaint') - parsed_url = urlparse(brush_model) - brushname = os.path.basename(parsed_url.path) - return brushname - - def apply_brushnet(self, brushname, model, vae, image, mask, positive, negative, scale=1.0, start_at=0, end_at=10000): - if "BrushNetLoader" not in ALL_NODE_CLASS_MAPPINGS: - raise Exception("BrushNetLoader not found,please install ComfyUI-BrushNet") - cls = ALL_NODE_CLASS_MAPPINGS['BrushNetLoader'] - brushnet, = cls().brushnet_loading(brushname, 'float16') - cls = ALL_NODE_CLASS_MAPPINGS['BrushNet'] - m, positive, negative, latent = cls().model_update(model=model, vae=vae, image=image, mask=mask, brushnet=brushnet, positive=positive, negative=negative, scale=scale, start_at=start_at, end_at=end_at) - return m, positive, negative, latent - - def inpainting(self, pipe, grow_mask_by, image_output, link_id, save_prefix, additional, model=None, mask=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - _model = model if model is not None else pipe['model'] - latent = pipe['samples'] if 'samples' in pipe else None - positive = pipe['positive'] - negative = pipe['negative'] - images = pipe["images"] if pipe and "images" in pipe else None - vae = pipe["vae"] if pipe and "vae" in pipe else None - if 'noise_mask' in latent and mask is None: - mask = latent['noise_mask'] - elif mask is not None: - if images is None: - raise Exception("No Images found") - if vae is None: - raise Exception("No VAE found") - - if additional == 'Differential Diffusion': - positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) - elif additional == 'InpaintModelCond': - if mask is not None: - mask, = GrowMask().expand_mask(mask, grow_mask_by, False) - positive, negative, latent = InpaintModelConditioning().encode(positive, negative, images, vae, mask) - elif additional == 'Fooocus Inpaint': - head = list(FOOOCUS_INPAINT_HEAD.keys())[0] - patch = list(FOOOCUS_INPAINT_PATCH.keys())[0] - if mask is not None: - latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by) - _model, = applyFooocusInpaint().apply(_model, latent, head, patch) - elif additional == 'Fooocus Inpaint + DD': - head = list(FOOOCUS_INPAINT_HEAD.keys())[0] - patch = list(FOOOCUS_INPAINT_PATCH.keys())[0] - if mask is not None: - latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by) - _model, = applyFooocusInpaint().apply(_model, latent, head, patch) - positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) - elif additional == 'Brushnet Random': - mask, = GrowMask().expand_mask(mask, grow_mask_by, False) - brush_name = self.get_brushnet_model('random', _model) - _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, - negative) - elif additional == 'Brushnet Random + DD': - mask, = GrowMask().expand_mask(mask, grow_mask_by, False) - brush_name = self.get_brushnet_model('random', _model) - _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, - negative) - positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) - elif additional == 'Brushnet Segmentation': - mask, = GrowMask().expand_mask(mask, grow_mask_by, False) - brush_name = self.get_brushnet_model('segmentation', _model) - _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, - negative) - elif additional == 'Brushnet Segmentation + DD': - mask, = GrowMask().expand_mask(mask, grow_mask_by, False) - brush_name = self.get_brushnet_model('segmentation', _model) - _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, - negative) - positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) - else: - latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by) - - results = super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, - None, _model, positive, negative, latent, vae, None, None, - tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) - - result = results['result'] - - return {"ui":results['ui'],"result":(result[0], result[1], result[0]['vae'],)} - -# SDTurbo采样器 -class samplerSDTurbo: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", - "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE",) - RETURN_NAMES = ("pipe", "image",) - OUTPUT_NODE = True - FUNCTION = "run" - - CATEGORY = "EasyUse/Sampler" - - def run(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None,): - # Clean loaded_objects - easyCache.update_loaded_objects(prompt) - - my_unique_id = int(my_unique_id) - - samp_model = pipe["model"] if model is None else model - samp_positive = pipe["positive"] - samp_negative = pipe["negative"] - samp_samples = pipe["samples"] - samp_vae = pipe["vae"] - samp_clip = pipe["clip"] - - samp_seed = pipe['seed'] - - samp_sampler = pipe['loader_settings']['sampler'] - - sigmas = pipe['loader_settings']['sigmas'] - cfg = pipe['loader_settings']['cfg'] - steps = pipe['loader_settings']['steps'] - - disable_noise = False - - preview_latent = True - if image_output in ("Hide", "Hide&Save"): - preview_latent = False - - # 推理初始时间 - start_time = int(time.time() * 1000) - # 开始推理 - samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, samp_sampler, sigmas, samp_positive, samp_negative, samp_samples, - disable_noise, preview_latent) - # 推理结束时间 - end_time = int(time.time() * 1000) - - latent = samp_samples['samples'] - - # 解码图片 - if tile_size is not None: - samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, ) - else: - samp_images = samp_vae.decode(latent).cpu() - - # 推理总耗时(包含解码) - end_decode_time = int(time.time() * 1000) - spent_time = 'Diffusion:' + str((end_time - start_time) / 1000) + '″, VAEDecode:' + str( - (end_decode_time - end_time) / 1000) + '″ ' - - # Clean loaded_objects - easyCache.update_loaded_objects(prompt) - - results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo) - sampler.update_value_by_id("results", my_unique_id, results) - - new_pipe = { - "model": samp_model, - "positive": samp_positive, - "negative": samp_negative, - "vae": samp_vae, - "clip": samp_clip, - - "samples": samp_samples, - "images": samp_images, - "seed": samp_seed, - - "loader_settings": { - **pipe["loader_settings"], - "spent_time": spent_time - } - } - - sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) - - del pipe - - if image_output in ("Hide", "Hide&Save"): - return {"ui": {}, - "result": sampler.get_output(new_pipe, )} - - if image_output in ("Sender", "Sender&Save"): - PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) - - return {"ui": {"images": results}, - "result": sampler.get_output(new_pipe, )} - - -# Cascade完整采样器 -class samplerCascadeFull: - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), - "decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), - }, - - "optional": { - "image_to_latent_c": ("IMAGE",), - "latent_c": ("LATENT",), - "model_c": ("MODEL",), - }, - "hidden":{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "LATENT") - RETURN_NAMES = ("pipe", "model_b", "latent_b") - OUTPUT_NODE = True - - FUNCTION = "run" - CATEGORY = "EasyUse/Sampler" - - def run(self, pipe, encode_vae_name, decode_vae_name, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed, image_to_latent_c=None, latent_c=None, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - - encode_vae_name = encode_vae_name if encode_vae_name is not None else pipe['loader_settings']['encode_vae_name'] - decode_vae_name = decode_vae_name if decode_vae_name is not None else pipe['loader_settings']['decode_vae_name'] - - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - if image_to_latent_c is not None: - if encode_vae_name != 'None': - encode_vae = easyCache.load_vae(encode_vae_name) - else: - encode_vae = pipe['vae'][0] - if "compression" not in pipe["loader_settings"]: - raise Exception("compression is not found") - - compression = pipe["loader_settings"]['compression'] - width = image_to_latent_c.shape[-2] - height = image_to_latent_c.shape[-3] - out_width = (width // compression) * encode_vae.downscale_ratio - out_height = (height // compression) * encode_vae.downscale_ratio - - s = comfy.utils.common_upscale(image_to_latent_c.movedim(-1, 1), out_width, out_height, "bicubic", - "center").movedim(1, -1) - latent_c = encode_vae.encode(s[:, :, :, :3]) - latent_b = torch.zeros([latent_c.shape[0], 4, height // 4, width // 4]) - - samples_c = {"samples": latent_c} - samples_c = RepeatLatentBatch().repeat(samples_c, batch_size)[0] - - samples_b = {"samples": latent_b} - samples_b = RepeatLatentBatch().repeat(samples_b, batch_size)[0] - images = image_to_latent_c - elif latent_c is not None: - samples_c = latent_c - samples_b = pipe["samples"][1] - images = pipe["images"] - else: - samples_c = pipe["samples"][0] - samples_b = pipe["samples"][1] - images = pipe["images"] - - # Clean loaded_objects - easyCache.update_loaded_objects(prompt) - samp_model = model_c if model_c else pipe["model"][0] - samp_positive = pipe["positive"] - samp_negative = pipe["negative"] - samp_samples = samples_c - - samp_seed = seed if seed is not None else pipe['seed'] - - steps = steps if steps is not None else pipe['loader_settings']['steps'] - start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0 - last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000 - cfg = cfg if cfg is not None else pipe['loader_settings']['cfg'] - sampler_name = sampler_name if sampler_name is not None else pipe['loader_settings']['sampler_name'] - scheduler = scheduler if scheduler is not None else pipe['loader_settings']['scheduler'] - denoise = denoise if denoise is not None else pipe['loader_settings']['denoise'] - noise_device = 'gpu' if "a1111_prompt_style" in pipe['loader_settings'] and pipe['loader_settings']['a1111_prompt_style'] else 'cpu' - # 推理初始时间 - start_time = int(time.time() * 1000) - # 开始推理 - samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, - samp_positive, samp_negative, samp_samples, denoise=denoise, - preview_latent=False, start_step=start_step, - last_step=last_step, force_full_denoise=False, - disable_noise=False, noise_device=noise_device) - # 推理结束时间 - end_time = int(time.time() * 1000) - stage_c = samp_samples["samples"] - results = None - - if image_output not in ['Hide', 'Hide&Save']: - if decode_vae_name != 'None': - decode_vae = easyCache.load_vae(decode_vae_name) - else: - decode_vae = pipe['vae'][0] - samp_images = decode_vae.decode(stage_c).cpu() - - results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo) - sampler.update_value_by_id("results", my_unique_id, results) - - # 推理总耗时(包含解码) - end_decode_time = int(time.time() * 1000) - spent_time = 'Diffusion:' + str((end_time - start_time) / 1000) + '″, VAEDecode:' + str( - (end_decode_time - end_time) / 1000) + '″ ' - - # Clean loaded_objects - easyCache.update_loaded_objects(prompt) - # zero_out - c1 = [] - for t in samp_positive: - d = t[1].copy() - if "pooled_output" in d: - d["pooled_output"] = torch.zeros_like(d["pooled_output"]) - n = [torch.zeros_like(t[0]), d] - c1.append(n) - # stage_b_conditioning - c2 = [] - for t in c1: - d = t[1].copy() - d['stable_cascade_prior'] = stage_c - n = [t[0], d] - c2.append(n) - - - new_pipe = { - "model": pipe['model'][1], - "positive": c2, - "negative": c1, - "vae": pipe['vae'][1], - "clip": pipe['clip'], - - "samples": samples_b, - "images": images, - "seed": seed, - - "loader_settings": { - **pipe["loader_settings"], - "spent_time": spent_time - } - } - sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) - - del pipe - - if image_output in ("Hide", "Hide&Save"): - return {"ui": {}, - "result": sampler.get_output(new_pipe, )} - - if image_output in ("Sender", "Sender&Save") and results is not None: - PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) - - return {"ui": {"images": results}, "result": (new_pipe, new_pipe['model'], new_pipe['samples'])} - -# 简易采样器Cascade -class samplerCascadeSimple(samplerCascadeFull): - - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(cls): - return {"required": - {"pipe": ("PIPE_LINE",), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"], {"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "model_c": ("MODEL",), - }, - "hidden": - {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - "embeddingsList": (folder_paths.get_filename_list("embeddings"),) - } - } - - - RETURN_TYPES = ("PIPE_LINE", "IMAGE",) - RETURN_NAMES = ("pipe", "image",) - OUTPUT_NODE = True - FUNCTION = "simple" - CATEGORY = "EasyUse/Sampler" - - def simple(self, pipe, image_output, link_id, save_prefix, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): - - return super().run(pipe, None, None,None, None,None,None,None, image_output, link_id, save_prefix, - None, None, None, model_c, tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) - -class unsampler: - @classmethod - def INPUT_TYPES(s): - return {"required":{ - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "end_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), - "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS,), - "normalize": (["disable", "enable"],), - - }, - "optional": { - "pipe": ("PIPE_LINE",), - "optional_model": ("MODEL",), - "optional_positive": ("CONDITIONING",), - "optional_negative": ("CONDITIONING",), - "optional_latent": ("LATENT",), - } - } - - RETURN_TYPES = ("PIPE_LINE", "LATENT",) - RETURN_NAMES = ("pipe", "latent",) - FUNCTION = "unsampler" - - CATEGORY = "EasyUse/Sampler" - - def unsampler(self, cfg, sampler_name, steps, end_at_step, scheduler, normalize, pipe=None, optional_model=None, optional_positive=None, optional_negative=None, - optional_latent=None): - - model = optional_model if optional_model is not None else pipe["model"] - positive = optional_positive if optional_positive is not None else pipe["positive"] - negative = optional_negative if optional_negative is not None else pipe["negative"] - latent_image = optional_latent if optional_latent is not None else pipe["samples"] - - normalize = normalize == "enable" - device = comfy.model_management.get_torch_device() - latent = latent_image - latent_image = latent["samples"] - - end_at_step = min(end_at_step, steps - 1) - end_at_step = steps - end_at_step - - noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu") - noise_mask = None - if "noise_mask" in latent: - noise_mask = comfy.sampler_helpers.prepare_mask(latent["noise_mask"], noise.shape, device) - - noise = noise.to(device) - latent_image = latent_image.to(device) - - _positive = comfy.sampler_helpers.convert_cond(positive) - _negative = comfy.sampler_helpers.convert_cond(negative) - models, inference_memory = comfy.sampler_helpers.get_additional_models({"positive": _positive, "negative": _negative}, model.model_dtype()) - - - comfy.model_management.load_models_gpu([model] + models, model.memory_required(noise.shape) + inference_memory) - - model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device()) - - sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name, - scheduler=scheduler, denoise=1.0, model_options=model.model_options) - - sigmas = sampler.sigmas.flip(0) + 0.0001 - - pbar = comfy.utils.ProgressBar(steps) - - def callback(step, x0, x, total_steps): - pbar.update_absolute(step + 1, total_steps) - - samples = sampler.sample(noise, positive, negative, cfg=cfg, latent_image=latent_image, - force_full_denoise=False, denoise_mask=noise_mask, sigmas=sigmas, start_step=0, - last_step=end_at_step, callback=callback) - if normalize: - # technically doesn't normalize because unsampling is not guaranteed to end at a std given by the schedule - samples -= samples.mean() - samples /= samples.std() - samples = samples.cpu() - - comfy.sample.cleanup_additional_models(models) - - out = latent.copy() - out["samples"] = samples - - if pipe is None: - pipe = {} - - new_pipe = { - **pipe, - "samples": out - } - - return (new_pipe, out,) - -#---------------------------------------------------------------采样器 结束---------------------------------------------------------------------- - -#---------------------------------------------------------------修复 开始----------------------------------------------------------------------# - -# 高清修复 -class hiresFix: - upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos", "bislerp"] - crop_methods = ["disabled", "center"] - - @classmethod - def INPUT_TYPES(s): - return {"required": { - "model_name": (folder_paths.get_filename_list("upscale_models"),), - "rescale_after_model": ([False, True], {"default": True}), - "rescale_method": (s.upscale_methods,), - "rescale": (["by percentage", "to Width/Height", 'to longer side - maintain aspect'],), - "percent": ("INT", {"default": 50, "min": 0, "max": 1000, "step": 1}), - "width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "longer_side": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "crop": (s.crop_methods,), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "pipe": ("PIPE_LINE",), - "image": ("IMAGE",), - "vae": ("VAE",), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", - }, - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE", "LATENT", ) - RETURN_NAMES = ('pipe', 'image', "latent", ) - - FUNCTION = "upscale" - CATEGORY = "EasyUse/Fix" - OUTPUT_NODE = True - - def vae_encode_crop_pixels(self, pixels): - x = (pixels.shape[1] // 8) * 8 - y = (pixels.shape[2] // 8) * 8 - if pixels.shape[1] != x or pixels.shape[2] != y: - x_offset = (pixels.shape[1] % 8) // 2 - y_offset = (pixels.shape[2] % 8) // 2 - pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] - return pixels - - def upscale(self, model_name, rescale_after_model, rescale_method, rescale, percent, width, height, - longer_side, crop, image_output, link_id, save_prefix, pipe=None, image=None, vae=None, prompt=None, - extra_pnginfo=None, my_unique_id=None): - - new_pipe = {} - if pipe is not None: - image = image if image is not None else pipe["images"] - vae = vae if vae is not None else pipe.get("vae") - elif image is None or vae is None: - raise ValueError("pipe or image or vae missing.") - # Load Model - model_path = folder_paths.get_full_path("upscale_models", model_name) - sd = comfy.utils.load_torch_file(model_path, safe_load=True) - upscale_model = model_loading.load_state_dict(sd).eval() - - # Model upscale - device = comfy.model_management.get_torch_device() - upscale_model.to(device) - in_img = image.movedim(-1, -3).to(device) - - tile = 128 + 64 - overlap = 8 - steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile, - tile_y=tile, overlap=overlap) - pbar = comfy.utils.ProgressBar(steps) - s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap, - upscale_amount=upscale_model.scale, pbar=pbar) - upscale_model.cpu() - s = torch.clamp(s.movedim(-3, -1), min=0, max=1.0) - - # Post Model Rescale - if rescale_after_model == True: - samples = s.movedim(-1, 1) - orig_height = samples.shape[2] - orig_width = samples.shape[3] - if rescale == "by percentage" and percent != 0: - height = percent / 100 * orig_height - width = percent / 100 * orig_width - if (width > MAX_RESOLUTION): - width = MAX_RESOLUTION - if (height > MAX_RESOLUTION): - height = MAX_RESOLUTION - - width = easySampler.enforce_mul_of_64(width) - height = easySampler.enforce_mul_of_64(height) - elif rescale == "to longer side - maintain aspect": - longer_side = easySampler.enforce_mul_of_64(longer_side) - if orig_width > orig_height: - width, height = longer_side, easySampler.enforce_mul_of_64(longer_side * orig_height / orig_width) - else: - width, height = easySampler.enforce_mul_of_64(longer_side * orig_width / orig_height), longer_side - - s = comfy.utils.common_upscale(samples, width, height, rescale_method, crop) - s = s.movedim(1, -1) - - # vae encode - pixels = self.vae_encode_crop_pixels(s) - t = vae.encode(pixels[:, :, :, :3]) - - if pipe is not None: - new_pipe = { - "model": pipe['model'], - "positive": pipe['positive'], - "negative": pipe['negative'], - "vae": vae, - "clip": pipe['clip'], - - "samples": {"samples": t}, - "images": s, - "seed": pipe['seed'], - - "loader_settings": { - **pipe["loader_settings"], - } - } - del pipe - else: - new_pipe = {} - - results = easySave(s, save_prefix, image_output, prompt, extra_pnginfo) - - if image_output in ("Sender", "Sender&Save"): - PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) - - if image_output in ("Hide", "Hide&Save"): - return (new_pipe, s, {"samples": t},) - - return {"ui": {"images": results}, - "result": (new_pipe, s, {"samples": t},)} - -# 预细节修复 -class preDetailerFix: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "pipe": ("PIPE_LINE",), - "guide_size": ("FLOAT", {"default": 256, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}), - "max_size": ("FLOAT", {"default": 768, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['align_your_steps'],), - "denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}), - "feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}), - "noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), - "force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), - "drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}), - "wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}), - "cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}), - }, - "optional": { - "bbox_segm_pipe": ("PIPE_LINE",), - "sam_pipe": ("PIPE_LINE",), - "optional_image": ("IMAGE",), - }, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - OUTPUT_IS_LIST = (False,) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Fix" - - def doit(self, pipe, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, denoise, feather, noise_mask, force_inpaint, drop_size, wildcard, cycle, bbox_segm_pipe=None, sam_pipe=None, optional_image=None): - - model = pipe["model"] if "model" in pipe else None - if model is None: - raise Exception(f"[ERROR] pipe['model'] is missing") - clip = pipe["clip"] if"clip" in pipe else None - if clip is None: - raise Exception(f"[ERROR] pipe['clip'] is missing") - vae = pipe["vae"] if "vae" in pipe else None - if vae is None: - raise Exception(f"[ERROR] pipe['vae'] is missing") - if optional_image is not None: - images = optional_image - else: - images = pipe["images"] if "images" in pipe else None - if images is None: - raise Exception(f"[ERROR] pipe['image'] is missing") - positive = pipe["positive"] if "positive" in pipe else None - if positive is None: - raise Exception(f"[ERROR] pipe['positive'] is missing") - negative = pipe["negative"] if "negative" in pipe else None - if negative is None: - raise Exception(f"[ERROR] pipe['negative'] is missing") - bbox_segm_pipe = bbox_segm_pipe or (pipe["bbox_segm_pipe"] if pipe and "bbox_segm_pipe" in pipe else None) - if bbox_segm_pipe is None: - raise Exception(f"[ERROR] bbox_segm_pipe or pipe['bbox_segm_pipe'] is missing") - sam_pipe = sam_pipe or (pipe["sam_pipe"] if pipe and "sam_pipe" in pipe else None) - if sam_pipe is None: - raise Exception(f"[ERROR] sam_pipe or pipe['sam_pipe'] is missing") - - loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {} - - if(scheduler == 'align_your_steps'): - model_version = get_sd_version(model) - if model_version == 'sdxl': - scheduler = 'AYS SDXL' - elif model_version == 'svd': - scheduler = 'AYS SVD' - else: - scheduler = 'AYS SD1' - - new_pipe = { - "images": images, - "model": model, - "clip": clip, - "vae": vae, - "positive": positive, - "negative": negative, - "seed": seed, - - "bbox_segm_pipe": bbox_segm_pipe, - "sam_pipe": sam_pipe, - - "loader_settings": loader_settings, - - "detail_fix_settings": { - "guide_size": guide_size, - "guide_size_for": guide_size_for, - "max_size": max_size, - "seed": seed, - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "feather": feather, - "noise_mask": noise_mask, - "force_inpaint": force_inpaint, - "drop_size": drop_size, - "wildcard": wildcard, - "cycle": cycle - } - } - - - del bbox_segm_pipe - del sam_pipe - - return (new_pipe,) - -# 预遮罩细节修复 -class preMaskDetailerFix: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "pipe": ("PIPE_LINE",), - "mask": ("MASK",), - - "guide_size": ("FLOAT", {"default": 384, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}), - "max_size": ("FLOAT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), - "mask_mode": ("BOOLEAN", {"default": True, "label_on": "masked only", "label_off": "whole"}), - - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), - "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), - "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), - "scheduler": (comfy.samplers.KSampler.SCHEDULERS,), - "denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}), - - "feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}), - "crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}), - "drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}), - "refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}), - "batch_size": ("INT", {"default": 1, "min": 1, "max": 100}), - "cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}), - }, - "optional": { - # "patch": ("INPAINT_PATCH",), - "optional_image": ("IMAGE",), - "inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}), - "noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}), - }, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - OUTPUT_IS_LIST = (False,) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Fix" - - def doit(self, pipe, mask, guide_size, guide_size_for, max_size, mask_mode, seed, steps, cfg, sampler_name, scheduler, denoise, feather, crop_factor, drop_size,refiner_ratio, batch_size, cycle, optional_image=None, inpaint_model=False, noise_mask_feather=20): - - model = pipe["model"] if "model" in pipe else None - if model is None: - raise Exception(f"[ERROR] pipe['model'] is missing") - clip = pipe["clip"] if"clip" in pipe else None - if clip is None: - raise Exception(f"[ERROR] pipe['clip'] is missing") - vae = pipe["vae"] if "vae" in pipe else None - if vae is None: - raise Exception(f"[ERROR] pipe['vae'] is missing") - if optional_image is not None: - images = optional_image - else: - images = pipe["images"] if "images" in pipe else None - if images is None: - raise Exception(f"[ERROR] pipe['image'] is missing") - positive = pipe["positive"] if "positive" in pipe else None - if positive is None: - raise Exception(f"[ERROR] pipe['positive'] is missing") - negative = pipe["negative"] if "negative" in pipe else None - if negative is None: - raise Exception(f"[ERROR] pipe['negative'] is missing") - latent = pipe["samples"] if "samples" in pipe else None - if latent is None: - raise Exception(f"[ERROR] pipe['samples'] is missing") - - if 'noise_mask' not in latent: - if images is None: - raise Exception("No Images found") - if vae is None: - raise Exception("No VAE found") - x = (images.shape[1] // 8) * 8 - y = (images.shape[2] // 8) * 8 - mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), - size=(images.shape[1], images.shape[2]), mode="bilinear") - - pixels = images.clone() - if pixels.shape[1] != x or pixels.shape[2] != y: - x_offset = (pixels.shape[1] % 8) // 2 - y_offset = (pixels.shape[2] % 8) // 2 - pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] - mask = mask[:, :, x_offset:x + x_offset, y_offset:y + y_offset] - - mask_erosion = mask - - m = (1.0 - mask.round()).squeeze(1) - for i in range(3): - pixels[:, :, :, i] -= 0.5 - pixels[:, :, :, i] *= m - pixels[:, :, :, i] += 0.5 - t = vae.encode(pixels) - - latent = {"samples": t, "noise_mask": (mask_erosion[:, :, :x, :y].round())} - # when patch was linked - # if patch is not None: - # worker = InpaintWorker(node_name="easy kSamplerInpainting") - # model, = worker.patch(model, latent, patch) - - loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {} - - new_pipe = { - "images": images, - "model": model, - "clip": clip, - "vae": vae, - "positive": positive, - "negative": negative, - "seed": seed, - "mask": mask, - - "loader_settings": loader_settings, - - "detail_fix_settings": { - "guide_size": guide_size, - "guide_size_for": guide_size_for, - "max_size": max_size, - "seed": seed, - "steps": steps, - "cfg": cfg, - "sampler_name": sampler_name, - "scheduler": scheduler, - "denoise": denoise, - "feather": feather, - "crop_factor": crop_factor, - "drop_size": drop_size, - "refiner_ratio": refiner_ratio, - "batch_size": batch_size, - "cycle": cycle - }, - - "mask_settings": { - "mask_mode": mask_mode, - "inpaint_model": inpaint_model, - "noise_mask_feather": noise_mask_feather - } - } - - del pipe - - return (new_pipe,) - -# 细节修复 -class detailerFix: - @classmethod - def INPUT_TYPES(s): - return {"required": { - "pipe": ("PIPE_LINE",), - "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), - "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), - "save_prefix": ("STRING", {"default": "ComfyUI"}), - }, - "optional": { - "model": ("MODEL",), - }, - "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", } - } - - RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "IMAGE") - RETURN_NAMES = ("pipe", "image", "cropped_refined", "cropped_enhanced_alpha") - OUTPUT_NODE = True - OUTPUT_IS_LIST = (False, False, True, True) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Fix" - - - def doit(self, pipe, image_output, link_id, save_prefix, model=None, prompt=None, extra_pnginfo=None, my_unique_id=None): - - # Clean loaded_objects - easyCache.update_loaded_objects(prompt) - - my_unique_id = int(my_unique_id) - - model = model or (pipe["model"] if "model" in pipe else None) - if model is None: - raise Exception(f"[ERROR] model or pipe['model'] is missing") - - detail_fix_settings = pipe["detail_fix_settings"] if "detail_fix_settings" in pipe else None - if detail_fix_settings is None: - raise Exception(f"[ERROR] detail_fix_settings or pipe['detail_fix_settings'] is missing") - - mask = pipe["mask"] if "mask" in pipe else None - - image = pipe["images"] - clip = pipe["clip"] - vae = pipe["vae"] - seed = pipe["seed"] - positive = pipe["positive"] - negative = pipe["negative"] - loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {} - guide_size = pipe["detail_fix_settings"]["guide_size"] if "guide_size" in pipe["detail_fix_settings"] else 256 - guide_size_for = pipe["detail_fix_settings"]["guide_size_for"] if "guide_size_for" in pipe[ - "detail_fix_settings"] else True - max_size = pipe["detail_fix_settings"]["max_size"] if "max_size" in pipe["detail_fix_settings"] else 768 - steps = pipe["detail_fix_settings"]["steps"] if "steps" in pipe["detail_fix_settings"] else 20 - cfg = pipe["detail_fix_settings"]["cfg"] if "cfg" in pipe["detail_fix_settings"] else 1.0 - sampler_name = pipe["detail_fix_settings"]["sampler_name"] if "sampler_name" in pipe[ - "detail_fix_settings"] else None - scheduler = pipe["detail_fix_settings"]["scheduler"] if "scheduler" in pipe["detail_fix_settings"] else None - denoise = pipe["detail_fix_settings"]["denoise"] if "denoise" in pipe["detail_fix_settings"] else 0.5 - feather = pipe["detail_fix_settings"]["feather"] if "feather" in pipe["detail_fix_settings"] else 5 - crop_factor = pipe["detail_fix_settings"]["crop_factor"] if "crop_factor" in pipe["detail_fix_settings"] else 3.0 - drop_size = pipe["detail_fix_settings"]["drop_size"] if "drop_size" in pipe["detail_fix_settings"] else 10 - refiner_ratio = pipe["detail_fix_settings"]["refiner_ratio"] if "refiner_ratio" in pipe else 0.2 - batch_size = pipe["detail_fix_settings"]["batch_size"] if "batch_size" in pipe["detail_fix_settings"] else 1 - noise_mask = pipe["detail_fix_settings"]["noise_mask"] if "noise_mask" in pipe["detail_fix_settings"] else None - force_inpaint = pipe["detail_fix_settings"]["force_inpaint"] if "force_inpaint" in pipe["detail_fix_settings"] else False - wildcard = pipe["detail_fix_settings"]["wildcard"] if "wildcard" in pipe["detail_fix_settings"] else "" - cycle = pipe["detail_fix_settings"]["cycle"] if "cycle" in pipe["detail_fix_settings"] else 1 - - bbox_segm_pipe = pipe["bbox_segm_pipe"] if pipe and "bbox_segm_pipe" in pipe else None - sam_pipe = pipe["sam_pipe"] if "sam_pipe" in pipe else None - - # 细节修复初始时间 - start_time = int(time.time() * 1000) - if "mask_settings" in pipe: - mask_mode = pipe['mask_settings']["mask_mode"] if "inpaint_model" in pipe['mask_settings'] else True - inpaint_model = pipe['mask_settings']["inpaint_model"] if "inpaint_model" in pipe['mask_settings'] else False - noise_mask_feather = pipe['mask_settings']["noise_mask_feather"] if "noise_mask_feather" in pipe['mask_settings'] else 20 - cls = ALL_NODE_CLASS_MAPPINGS["MaskDetailerPipe"] - if "MaskDetailerPipe" not in ALL_NODE_CLASS_MAPPINGS: - raise Exception(f"[ERROR] To use MaskDetailerPipe, you need to install 'Impact Pack'") - basic_pipe = (model, clip, vae, positive, negative) - result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, basic_pipe, refiner_basic_pipe_opt = cls().doit(image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode, - seed, steps, cfg, sampler_name, scheduler, denoise, - feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1, - refiner_basic_pipe_opt=None, detailer_hook=None, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather) - result_mask = mask - result_cnet_images = () - else: - if bbox_segm_pipe is None: - raise Exception(f"[ERROR] bbox_segm_pipe or pipe['bbox_segm_pipe'] is missing") - if sam_pipe is None: - raise Exception(f"[ERROR] sam_pipe or pipe['sam_pipe'] is missing") - bbox_detector_opt, bbox_threshold, bbox_dilation, bbox_crop_factor, segm_detector_opt = bbox_segm_pipe - sam_model_opt, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative = sam_pipe - if "FaceDetailer" not in ALL_NODE_CLASS_MAPPINGS: - raise Exception(f"[ERROR] To use FaceDetailer, you need to install 'Impact Pack'") - cls = ALL_NODE_CLASS_MAPPINGS["FaceDetailer"] - - result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, pipe, result_cnet_images = cls().doit( - image, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, - scheduler, - positive, negative, denoise, feather, noise_mask, force_inpaint, - bbox_threshold, bbox_dilation, bbox_crop_factor, - sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, - sam_mask_hint_use_negative, drop_size, bbox_detector_opt, wildcard, cycle, sam_model_opt, - segm_detector_opt, - detailer_hook=None) - - # 细节修复结束时间 - end_time = int(time.time() * 1000) - - spent_time = 'Fix:' + str((end_time - start_time) / 1000) + '"' - - results = easySave(result_img, save_prefix, image_output, prompt, extra_pnginfo) - sampler.update_value_by_id("results", my_unique_id, results) - - # Clean loaded_objects - easyCache.update_loaded_objects(prompt) - - new_pipe = { - "samples": None, - "images": result_img, - "model": model, - "clip": clip, - "vae": vae, - "seed": seed, - "positive": positive, - "negative": negative, - "wildcard": wildcard, - "bbox_segm_pipe": bbox_segm_pipe, - "sam_pipe": sam_pipe, - - "loader_settings": { - **loader_settings, - "spent_time": spent_time - }, - "detail_fix_settings": detail_fix_settings - } - if "mask_settings" in pipe: - new_pipe["mask_settings"] = pipe["mask_settings"] - - sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) - - del bbox_segm_pipe - del sam_pipe - del pipe - - if image_output in ("Hide", "Hide&Save"): - return (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images) - - if image_output in ("Sender", "Sender&Save"): - PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) - - return {"ui": {"images": results}, "result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )} - -class ultralyticsDetectorForDetailerFix: - @classmethod - def INPUT_TYPES(s): - bboxs = ["bbox/" + x for x in folder_paths.get_filename_list("ultralytics_bbox")] - segms = ["segm/" + x for x in folder_paths.get_filename_list("ultralytics_segm")] - return {"required": - {"model_name": (bboxs + segms,), - "bbox_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), - "bbox_dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}), - "bbox_crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}), - } - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("bbox_segm_pipe",) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Fix" - - def doit(self, model_name, bbox_threshold, bbox_dilation, bbox_crop_factor): - if 'UltralyticsDetectorProvider' not in ALL_NODE_CLASS_MAPPINGS: - raise Exception(f"[ERROR] To use UltralyticsDetectorProvider, you need to install 'Impact Pack'") - cls = ALL_NODE_CLASS_MAPPINGS['UltralyticsDetectorProvider'] - bbox_detector, segm_detector = cls().doit(model_name) - pipe = (bbox_detector, bbox_threshold, bbox_dilation, bbox_crop_factor, segm_detector) - return (pipe,) - -class samLoaderForDetailerFix: - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "model_name": (folder_paths.get_filename_list("sams"),), - "device_mode": (["AUTO", "Prefer GPU", "CPU"],{"default": "AUTO"}), - "sam_detection_hint": ( - ["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area", "mask-points", - "mask-point-bbox", "none"],), - "sam_dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), - "sam_threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}), - "sam_bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}), - "sam_mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}), - "sam_mask_hint_use_negative": (["False", "Small", "Outter"],), - } - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("sam_pipe",) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Fix" - - def doit(self, model_name, device_mode, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative): - if 'SAMLoader' not in ALL_NODE_CLASS_MAPPINGS: - raise Exception(f"[ERROR] To use SAMLoader, you need to install 'Impact Pack'") - cls = ALL_NODE_CLASS_MAPPINGS['SAMLoader'] - (sam_model,) = cls().load_model(model_name, device_mode) - pipe = (sam_model, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative) - return (pipe,) - -#---------------------------------------------------------------修复 结束---------------------------------------------------------------------- - -#---------------------------------------------------------------节点束 开始----------------------------------------------------------------------# -# 节点束输入 -class pipeIn: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": {}, - "optional": { - "pipe": ("PIPE_LINE",), - "model": ("MODEL",), - "pos": ("CONDITIONING",), - "neg": ("CONDITIONING",), - "latent": ("LATENT",), - "vae": ("VAE",), - "clip": ("CLIP",), - "image": ("IMAGE",), - "xyPlot": ("XYPLOT",), - }, - "hidden": {"my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - FUNCTION = "flush" - - CATEGORY = "EasyUse/Pipe" - - def flush(self, pipe=None, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, image=None, xyplot=None, my_unique_id=None): - - model = model if model is not None else pipe.get("model") - if model is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Model missing from pipeLine") - pos = pos if pos is not None else pipe.get("positive") - if pos is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Pos Conditioning missing from pipeLine") - neg = neg if neg is not None else pipe.get("negative") - if neg is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine") - vae = vae if vae is not None else pipe.get("vae") - if vae is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine") - clip = clip if clip is not None else pipe.get("clip") if pipe is not None and "clip" in pipe else None - # if clip is None: - # log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine") - if latent is not None: - samples = latent - elif image is None: - samples = pipe.get("samples") if pipe is not None else None - image = pipe.get("images") if pipe is not None else None - elif image is not None: - if pipe is None: - batch_size = 1 - else: - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - samples = {"samples": vae.encode(image[:, :, :, :3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - - if pipe is None: - pipe = {"loader_settings": {"positive": "", "negative": "", "xyplot": None}} - - xyplot = xyplot if xyplot is not None else pipe['loader_settings']['xyplot'] if xyplot in pipe['loader_settings'] else None - - new_pipe = { - **pipe, - "model": model, - "positive": pos, - "negative": neg, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": image, - "seed": pipe.get('seed') if pipe is not None and "seed" in pipe else None, - - "loader_settings": { - **pipe["loader_settings"], - "xyplot": xyplot - } - } - del pipe - - return (new_pipe,) - -# 节点束输出 -class pipeOut: - def __init__(self): - pass - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - }, - "hidden": {"my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "IMAGE", "INT",) - RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "image", "seed",) - FUNCTION = "flush" - - CATEGORY = "EasyUse/Pipe" - - def flush(self, pipe, my_unique_id=None): - model = pipe.get("model") - pos = pipe.get("positive") - neg = pipe.get("negative") - latent = pipe.get("samples") - vae = pipe.get("vae") - clip = pipe.get("clip") - image = pipe.get("images") - seed = pipe.get("seed") - - return pipe, model, pos, neg, latent, vae, clip, image, seed - -# 编辑节点束 -class pipeEdit: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), - - "optional_positive": ("STRING", {"default": "", "multiline": True}), - "positive_token_normalization": (["none", "mean", "length", "length+mean"],), - "positive_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), - - "optional_negative": ("STRING", {"default": "", "multiline": True}), - "negative_token_normalization": (["none", "mean", "length", "length+mean"],), - "negative_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), - - "a1111_prompt_style": ("BOOLEAN", {"default": False}), - "conditioning_mode": (['replace', 'concat', 'combine', 'average', 'timestep'], {"default": "replace"}), - "average_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), - "old_cond_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "old_cond_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "new_cond_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), - "new_cond_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), - }, - "optional": { - "pipe": ("PIPE_LINE",), - "model": ("MODEL",), - "pos": ("CONDITIONING",), - "neg": ("CONDITIONING",), - "latent": ("LATENT",), - "vae": ("VAE",), - "clip": ("CLIP",), - "image": ("IMAGE",), - }, - "hidden": {"my_unique_id": "UNIQUE_ID", "prompt":"PROMPT"}, - } - - RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "IMAGE") - RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "image") - FUNCTION = "edit" - - CATEGORY = "EasyUse/Pipe" - - def edit(self, clip_skip, optional_positive, positive_token_normalization, positive_weight_interpretation, optional_negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end, pipe=None, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, image=None, my_unique_id=None, prompt=None): - - model = model if model is not None else pipe.get("model") - if model is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Model missing from pipeLine") - vae = vae if vae is not None else pipe.get("vae") - if vae is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine") - clip = clip if clip is not None else pipe.get("clip") - if clip is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine") - if image is None: - image = pipe.get("images") if pipe is not None else None - samples = latent if latent is not None else pipe.get("samples") - if samples is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Latent missing from pipeLine") - else: - batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 - samples = {"samples": vae.encode(image[:, :, :, :3])} - samples = RepeatLatentBatch().repeat(samples, batch_size)[0] - - pipe_lora_stack = pipe.get("lora_stack") if pipe is not None and "lora_stack" in pipe else [] - - steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 1 - if pos is None and optional_positive != '': - pos, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, - pipe_lora_stack, optional_positive, positive_token_normalization,positive_weight_interpretation, - a1111_prompt_style, my_unique_id, prompt, easyCache, True, steps) - pos = set_cond(pipe['positive'], pos, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end) - pipe['loader_settings']['positive'] = positive_wildcard_prompt - pipe['loader_settings']['positive_token_normalization'] = positive_token_normalization - pipe['loader_settings']['positive_weight_interpretation'] = positive_weight_interpretation - if a1111_prompt_style: - pipe['loader_settings']['a1111_prompt_style'] = True - else: - pos = pipe.get("positive") - if pos is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Pos Conditioning missing from pipeLine") - - if neg is None and optional_negative != '': - neg, negative_wildcard_prompt, model, clip = prompt_to_cond("negative", model, clip, clip_skip, pipe_lora_stack, optional_negative, - negative_token_normalization, negative_weight_interpretation, - a1111_prompt_style, my_unique_id, prompt, easyCache, True, steps) - neg = set_cond(pipe['negative'], neg, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end) - pipe['loader_settings']['negative'] = negative_wildcard_prompt - pipe['loader_settings']['negative_token_normalization'] = negative_token_normalization - pipe['loader_settings']['negative_weight_interpretation'] = negative_weight_interpretation - if a1111_prompt_style: - pipe['loader_settings']['a1111_prompt_style'] = True - else: - neg = pipe.get("negative") - if neg is None: - log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine") - if pipe is None: - pipe = {"loader_settings": {"positive": "", "negative": "", "xyplot": None}} - - new_pipe = { - **pipe, - "model": model, - "positive": pos, - "negative": neg, - "vae": vae, - "clip": clip, - - "samples": samples, - "images": image, - "seed": pipe.get('seed') if pipe is not None and "seed" in pipe else None, - "loader_settings":{ - **pipe["loader_settings"] - } - } - del pipe - - return (new_pipe, model,pos, neg, latent, vae, clip, image) - -# 编辑节点束提示词 -class pipeEditPrompt: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "positive": ("STRING", {"default": "", "multiline": True}), - "negative": ("STRING", {"default": "", "multiline": True}), - }, - "hidden": {"my_unique_id": "UNIQUE_ID", "prompt": "PROMPT"}, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - FUNCTION = "edit" - - CATEGORY = "EasyUse/Pipe" - - def edit(self, pipe, positive, negative, my_unique_id=None, prompt=None): - model = pipe.get("model") - if model is None: - log_node_warn(f'pipeEdit[{my_unique_id}]', "Model missing from pipeLine") - - from .kolors.loader import is_kolors_model - model_type = get_sd_version(model) - if model_type == 'sdxl' and is_kolors_model(model): - auto_clean_gpu = pipe["loader_settings"]["auto_clean_gpu"] if "auto_clean_gpu" in pipe["loader_settings"] else False - chatglm3_model = pipe["chatglm3_model"] if "chatglm3_model" in pipe else None - # text encode - log_node_warn("Positive encoding...") - positive_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, positive, auto_clean_gpu) - log_node_warn("Negative encoding...") - negative_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, negative, auto_clean_gpu) - else: - clip_skip = pipe["loader_settings"]["clip_skip"] if "clip_skip" in pipe["loader_settings"] else -1 - lora_stack = pipe.get("lora_stack") if pipe is not None and "lora_stack" in pipe else [] - clip = pipe.get("clip") if pipe is not None and "clip" in pipe else None - positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"] if "positive_token_normalization" in pipe["loader_settings"] else "none" - positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"] if "positive_weight_interpretation" in pipe["loader_settings"] else "comfy" - negative_token_normalization = pipe["loader_settings"]["negative_token_normalization"] if "negative_token_normalization" in pipe["loader_settings"] else "none" - negative_weight_interpretation = pipe["loader_settings"]["negative_weight_interpretation"] if "negative_weight_interpretation" in pipe["loader_settings"] else "comfy" - a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"] if "a1111_prompt_style" in pipe["loader_settings"] else False - # Prompt to Conditioning - positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, - clip_skip, lora_stack, - positive, - positive_token_normalization, - positive_weight_interpretation, - a1111_prompt_style, - my_unique_id, prompt, - easyCache, - model_type=model_type) - negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, - clip_skip, lora_stack, - negative, - negative_token_normalization, - negative_weight_interpretation, - a1111_prompt_style, - my_unique_id, prompt, - easyCache, - model_type=model_type) - new_pipe = { - **pipe, - "model": model, - "positive": positive_embeddings_final, - "negative": negative_embeddings_final, - } - del pipe - - return (new_pipe,) - - -# 节点束到基础节点束(pipe to ComfyUI-Impack-pack's basic_pipe) -class pipeToBasicPipe: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - }, - "hidden": {"my_unique_id": "UNIQUE_ID"}, - } - - RETURN_TYPES = ("BASIC_PIPE",) - RETURN_NAMES = ("basic_pipe",) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Pipe" - - def doit(self, pipe, my_unique_id=None): - new_pipe = (pipe.get('model'), pipe.get('clip'), pipe.get('vae'), pipe.get('positive'), pipe.get('negative')) - del pipe - return (new_pipe,) - -# 批次索引 -class pipeBatchIndex: - @classmethod - def INPUT_TYPES(s): - return {"required": {"pipe": ("PIPE_LINE",), - "batch_index": ("INT", {"default": 0, "min": 0, "max": 63}), - "length": ("INT", {"default": 1, "min": 1, "max": 64}), - }, - "hidden": {"my_unique_id": "UNIQUE_ID"},} - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - FUNCTION = "doit" - - CATEGORY = "EasyUse/Pipe" - - def doit(self, pipe, batch_index, length, my_unique_id=None): - samples = pipe["samples"] - new_samples, = LatentFromBatch().frombatch(samples, batch_index, length) - new_pipe = { - **pipe, - "samples": new_samples - } - del pipe - return (new_pipe,) - -# pipeXYPlot -class pipeXYPlot: - lora_list = ["None"] + folder_paths.get_filename_list("loras") - lora_strengths = {"min": -4.0, "max": 4.0, "step": 0.01} - token_normalization = ["none", "mean", "length", "length+mean"] - weight_interpretation = ["comfy", "A1111", "compel", "comfy++"] - - loader_dict = { - "ckpt_name": folder_paths.get_filename_list("checkpoints"), - "vae_name": ["Baked-VAE"] + folder_paths.get_filename_list("vae"), - "clip_skip": {"min": -24, "max": -1, "step": 1}, - "lora_name": lora_list, - "lora_model_strength": lora_strengths, - "lora_clip_strength": lora_strengths, - "positive": [], - "negative": [], - } - - sampler_dict = { - "steps": {"min": 1, "max": 100, "step": 1}, - "cfg": {"min": 0.0, "max": 100.0, "step": 1.0}, - "sampler_name": comfy.samplers.KSampler.SAMPLERS, - "scheduler": comfy.samplers.KSampler.SCHEDULERS, - "denoise": {"min": 0.0, "max": 1.0, "step": 0.01}, - "seed": {"min": 0, "max": MAX_SEED_NUM}, - } - - plot_dict = {**sampler_dict, **loader_dict} - - plot_values = ["None", ] - plot_values.append("---------------------") - for k in sampler_dict: - plot_values.append(f'preSampling: {k}') - plot_values.append("---------------------") - for k in loader_dict: - plot_values.append(f'loader: {k}') - - def __init__(self): - pass - - rejected = ["None", "---------------------", "Nothing"] - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "grid_spacing": ("INT", {"min": 0, "max": 500, "step": 5, "default": 0, }), - "output_individuals": (["False", "True"], {"default": "False"}), - "flip_xy": (["False", "True"], {"default": "False"}), - "x_axis": (pipeXYPlot.plot_values, {"default": 'None'}), - "x_values": ( - "STRING", {"default": '', "multiline": True, "placeholder": 'insert values seperated by "; "'}), - "y_axis": (pipeXYPlot.plot_values, {"default": 'None'}), - "y_values": ( - "STRING", {"default": '', "multiline": True, "placeholder": 'insert values seperated by "; "'}), - }, - "optional": { - "pipe": ("PIPE_LINE",) - }, - "hidden": { - "plot_dict": (pipeXYPlot.plot_dict,), - }, - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - FUNCTION = "plot" - - CATEGORY = "EasyUse/Pipe" - - def plot(self, grid_spacing, output_individuals, flip_xy, x_axis, x_values, y_axis, y_values, pipe=None, font_path=None): - def clean_values(values): - original_values = values.split("; ") - cleaned_values = [] - - for value in original_values: - # Strip the semi-colon - cleaned_value = value.strip(';').strip() - - if cleaned_value == "": - continue - - # Try to convert the cleaned_value back to int or float if possible - try: - cleaned_value = int(cleaned_value) - except ValueError: - try: - cleaned_value = float(cleaned_value) - except ValueError: - pass - - # Append the cleaned_value to the list - cleaned_values.append(cleaned_value) - - return cleaned_values - - if x_axis in self.rejected: - x_axis = "None" - x_values = [] - else: - x_values = clean_values(x_values) - - if y_axis in self.rejected: - y_axis = "None" - y_values = [] - else: - y_values = clean_values(y_values) - - if flip_xy == "True": - x_axis, y_axis = y_axis, x_axis - x_values, y_values = y_values, x_values - - - xy_plot = {"x_axis": x_axis, - "x_vals": x_values, - "y_axis": y_axis, - "y_vals": y_values, - "custom_font": font_path, - "grid_spacing": grid_spacing, - "output_individuals": output_individuals} - - if pipe is not None: - new_pipe = pipe - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "xyplot": xy_plot - } - del pipe - return (new_pipe, xy_plot,) - -# pipeXYPlotAdvanced -import platform -class pipeXYPlotAdvanced: - if platform.system() == "Windows": - system_root = os.environ.get("SystemRoot") - user_root = os.environ.get("USERPROFILE") - font_dir = os.path.join(system_root, "Fonts") if system_root else None - user_font_dir = os.path.join(user_root, "AppData","Local","Microsoft","Windows", "Fonts") if user_root else None - - # Default debian-based Linux & MacOS font dirs - elif platform.system() == "Linux": - font_dir = "/usr/share/fonts/truetype" - user_font_dir = None - elif platform.system() == "Darwin": - font_dir = "/System/Library/Fonts" - user_font_dir = None - else: - font_dir = None - user_font_dir = None - - @classmethod - def INPUT_TYPES(s): - files_list = [] - if s.font_dir and os.path.exists(s.font_dir): - font_dir = s.font_dir - files_list = files_list + [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")] - - if s.user_font_dir and os.path.exists(s.user_font_dir): - files_list = files_list + [f for f in os.listdir(s.user_font_dir) if os.path.isfile(os.path.join(s.user_font_dir, f)) and f.lower().endswith(".ttf")] - - return { - "required": { - "pipe": ("PIPE_LINE",), - "grid_spacing": ("INT", {"min": 0, "max": 500, "step": 5, "default": 0, }), - "output_individuals": (["False", "True"], {"default": "False"}), - "flip_xy": (["False", "True"], {"default": "False"}), - }, - "optional": { - "X": ("X_Y",), - "Y": ("X_Y",), - "font": (["None"] + files_list,) - }, - "hidden": {"my_unique_id": "UNIQUE_ID"} - } - - RETURN_TYPES = ("PIPE_LINE",) - RETURN_NAMES = ("pipe",) - FUNCTION = "plot" - - CATEGORY = "EasyUse/Pipe" - - def plot(self, pipe, grid_spacing, output_individuals, flip_xy, X=None, Y=None, font=None, my_unique_id=None): - font_path = os.path.join(self.font_dir, font) if font != "None" else None - if font_path and not os.path.exists(font_path): - font_path = os.path.join(self.user_font_dir, font) - - if X != None: - x_axis = X.get('axis') - x_values = X.get('values') - else: - x_axis = "Nothing" - x_values = [""] - if Y != None: - y_axis = Y.get('axis') - y_values = Y.get('values') - else: - y_axis = "Nothing" - y_values = [""] - - if pipe is not None: - new_pipe = pipe - positive = pipe["loader_settings"]["positive"] if "positive" in pipe["loader_settings"] else "" - negative = pipe["loader_settings"]["negative"] if "negative" in pipe["loader_settings"] else "" - - if x_axis == 'advanced: ModelMergeBlocks': - models = X.get('models') - vae_use = X.get('vae_use') - if models is None: - raise Exception("models is not found") - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "models": models, - "vae_use": vae_use - } - if y_axis == 'advanced: ModelMergeBlocks': - models = Y.get('models') - vae_use = Y.get('vae_use') - if models is None: - raise Exception("models is not found") - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "models": models, - "vae_use": vae_use - } - - if x_axis in ['advanced: Lora', 'advanced: Checkpoint']: - lora_stack = X.get('lora_stack') - _lora_stack = [] - if lora_stack is not None: - for lora in lora_stack: - _lora_stack.append( - {"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "model_strength": lora[1], - "clip_strength": lora[2]}) - del lora_stack - x_values = "; ".join(x_values) - lora_stack = pipe['lora_stack'] + _lora_stack if 'lora_stack' in pipe else _lora_stack - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "lora_stack": lora_stack, - } - - if y_axis in ['advanced: Lora', 'advanced: Checkpoint']: - lora_stack = Y.get('lora_stack') - _lora_stack = [] - if lora_stack is not None: - for lora in lora_stack: - _lora_stack.append( - {"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "model_strength": lora[1], - "clip_strength": lora[2]}) - del lora_stack - y_values = "; ".join(y_values) - lora_stack = pipe['lora_stack'] + _lora_stack if 'lora_stack' in pipe else _lora_stack - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "lora_stack": lora_stack, - } - - if x_axis == 'advanced: Seeds++ Batch': - if new_pipe['seed']: - value = x_values - x_values = [] - for index in range(value): - x_values.append(str(new_pipe['seed'] + index)) - x_values = "; ".join(x_values) - if y_axis == 'advanced: Seeds++ Batch': - if new_pipe['seed']: - value = y_values - y_values = [] - for index in range(value): - y_values.append(str(new_pipe['seed'] + index)) - y_values = "; ".join(y_values) - - if x_axis == 'advanced: Positive Prompt S/R': - if positive: - x_value = x_values - x_values = [] - for index, value in enumerate(x_value): - search_txt, replace_txt, replace_all = value - if replace_all: - txt = replace_txt if replace_txt is not None else positive - x_values.append(txt) - else: - txt = positive.replace(search_txt, replace_txt, 1) if replace_txt is not None else positive - x_values.append(txt) - x_values = "; ".join(x_values) - if y_axis == 'advanced: Positive Prompt S/R': - if positive: - y_value = y_values - y_values = [] - for index, value in enumerate(y_value): - search_txt, replace_txt, replace_all = value - if replace_all: - txt = replace_txt if replace_txt is not None else positive - y_values.append(txt) - else: - txt = positive.replace(search_txt, replace_txt, 1) if replace_txt is not None else positive - y_values.append(txt) - y_values = "; ".join(y_values) - - if x_axis == 'advanced: Negative Prompt S/R': - if negative: - x_value = x_values - x_values = [] - for index, value in enumerate(x_value): - search_txt, replace_txt, replace_all = value - if replace_all: - txt = replace_txt if replace_txt is not None else negative - x_values.append(txt) - else: - txt = negative.replace(search_txt, replace_txt, 1) if replace_txt is not None else negative - x_values.append(txt) - x_values = "; ".join(x_values) - if y_axis == 'advanced: Negative Prompt S/R': - if negative: - y_value = y_values - y_values = [] - for index, value in enumerate(y_value): - search_txt, replace_txt, replace_all = value - if replace_all: - txt = replace_txt if replace_txt is not None else negative - y_values.append(txt) - else: - txt = negative.replace(search_txt, replace_txt, 1) if replace_txt is not None else negative - y_values.append(txt) - y_values = "; ".join(y_values) - - if "advanced: ControlNet" in x_axis: - x_value = x_values - x_values = [] - cnet = [] - for index, value in enumerate(x_value): - cnet.append(value) - x_values.append(str(index)) - x_values = "; ".join(x_values) - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "cnet_stack": cnet, - } - - if "advanced: ControlNet" in y_axis: - y_value = y_values - y_values = [] - cnet = [] - for index, value in enumerate(y_value): - cnet.append(value) - y_values.append(str(index)) - y_values = "; ".join(y_values) - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "cnet_stack": cnet, - } - - if "advanced: Pos Condition" in x_axis: - x_values = "; ".join(x_values) - cond = X.get('cond') - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "positive_cond_stack": cond, - } - if "advanced: Pos Condition" in y_axis: - y_values = "; ".join(y_values) - cond = Y.get('cond') - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "positive_cond_stack": cond, - } - - if "advanced: Neg Condition" in x_axis: - x_values = "; ".join(x_values) - cond = X.get('cond') - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "negative_cond_stack": cond, - } - if "advanced: Neg Condition" in y_axis: - y_values = "; ".join(y_values) - cond = Y.get('cond') - new_pipe['loader_settings'] = { - **pipe['loader_settings'], - "negative_cond_stack": cond, - } - - del pipe - - return pipeXYPlot().plot(grid_spacing, output_individuals, flip_xy, x_axis, x_values, y_axis, y_values, new_pipe, font_path) - -#---------------------------------------------------------------节点束 结束---------------------------------------------------------------------- - -# 显示推理时间 -class showSpentTime: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "spent_time": ("INFO", {"default": 'Time will be displayed when reasoning is complete', "forceInput": False}), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - FUNCTION = "notify" - OUTPUT_NODE = True - RETURN_TYPES = () - RETURN_NAMES = () - - CATEGORY = "EasyUse/Util" - - def notify(self, pipe, spent_time=None, unique_id=None, extra_pnginfo=None): - if unique_id and extra_pnginfo and "workflow" in extra_pnginfo: - workflow = extra_pnginfo["workflow"] - node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None) - if node: - spent_time = pipe['loader_settings']['spent_time'] if 'spent_time' in pipe['loader_settings'] else '' - node["widgets_values"] = [spent_time] - - return {"ui": {"text": spent_time}, "result": {}} - -# 显示加载器参数中的各种名称 -class showLoaderSettingsNames: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "pipe": ("PIPE_LINE",), - "names": ("INFO", {"default": '', "forceInput": False}), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - RETURN_TYPES = ("STRING", "STRING", "STRING",) - RETURN_NAMES = ("ckpt_name", "vae_name", "lora_name") - - FUNCTION = "notify" - OUTPUT_NODE = True - - CATEGORY = "EasyUse/Util" - - def notify(self, pipe, names=None, unique_id=None, extra_pnginfo=None): - if unique_id and extra_pnginfo and "workflow" in extra_pnginfo: - workflow = extra_pnginfo["workflow"] - node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None) - if node: - ckpt_name = pipe['loader_settings']['ckpt_name'] if 'ckpt_name' in pipe['loader_settings'] else '' - vae_name = pipe['loader_settings']['vae_name'] if 'vae_name' in pipe['loader_settings'] else '' - lora_name = pipe['loader_settings']['lora_name'] if 'lora_name' in pipe['loader_settings'] else '' - - if ckpt_name: - ckpt_name = os.path.basename(os.path.splitext(ckpt_name)[0]) - if vae_name: - vae_name = os.path.basename(os.path.splitext(vae_name)[0]) - if lora_name: - lora_name = os.path.basename(os.path.splitext(lora_name)[0]) - - names = "ckpt_name: " + ckpt_name + '\n' + "vae_name: " + vae_name + '\n' + "lora_name: " + lora_name - node["widgets_values"] = names - - return {"ui": {"text": names}, "result": (ckpt_name, vae_name, lora_name)} - - -class sliderControl: - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "mode": (['ipadapter layer weights'],), - "model_type": (['sdxl', 'sd1'],), - }, - "hidden": { - "prompt": "PROMPT", - "my_unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("layer_weights",) - - FUNCTION = "control" - - CATEGORY = "EasyUse/Util" - - def control(self, mode, model_type, prompt=None, my_unique_id=None, extra_pnginfo=None): - values = '' - if my_unique_id in prompt: - if 'values' in prompt[my_unique_id]["inputs"]: - values = prompt[my_unique_id]["inputs"]['values'] - - return (values,) - -#---------------------------------------------------------------API 开始----------------------------------------------------------------------# -from .libs.stability import stableAPI -class stableDiffusion3API: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), - "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), - "model": (["sd3", "sd3-turbo"],), - "aspect_ratio": (['16:9', '1:1', '21:9', '2:3', '3:2', '4:5', '5:4', '9:16', '9:21'],), - "seed": ("INT", {"default": 0, "min": 0, "max": 4294967294}), - "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}), - }, - "optional": { - "optional_image": ("IMAGE",), - }, - "hidden": { - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) - - FUNCTION = "generate" - OUTPUT_NODE = False - - CATEGORY = "EasyUse/API" - - def generate(self, positive, negative, model, aspect_ratio, seed, denoise, optional_image=None, unique_id=None, extra_pnginfo=None): - mode = 'text-to-image' - if optional_image is not None: - mode = 'image-to-image' - output_image = stableAPI.generate_sd3_image(positive, negative, aspect_ratio, seed=seed, mode=mode, model=model, strength=denoise, image=optional_image) - return (output_image,) - -from .libs.fluxai import fluxaiAPI - -class fluxPromptGenAPI: - - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "describe": ("STRING", {"default": "", "placeholder": "Describe your image idea (you can use any language)", "multiline": True}), - }, - "optional": { - "cookie_override": ("STRING", {"default": "", "forceInput": True}), - }, - "hidden": { - "prompt": "PROMPT", - "unique_id": "UNIQUE_ID", - "extra_pnginfo": "EXTRA_PNGINFO", - }, - } - - RETURN_TYPES = ("STRING",) - RETURN_NAMES = ("prompt",) - - FUNCTION = "generate" - OUTPUT_NODE = False - - CATEGORY = "EasyUse/API" - - def generate(self, describe, cookie_override=None, prompt=None, unique_id=None, extra_pnginfo=None): - prompt = fluxaiAPI.promptGenerate(describe, cookie_override) - return (prompt,) - - -#---------------------------------------------------------------API 结束---------------------------------------------------------------------- - -NODE_CLASS_MAPPINGS = { - # seed 随机种 - "easy seed": easySeed, - "easy globalSeed": globalSeed, - # prompt 提示词 - "easy positive": positivePrompt, - "easy negative": negativePrompt, - "easy wildcards": wildcardsPrompt, - "easy prompt": prompt, - "easy promptList": promptList, - "easy promptLine": promptLine, - "easy promptConcat": promptConcat, - "easy promptReplace": promptReplace, - "easy stylesSelector": stylesPromptSelector, - "easy portraitMaster": portraitMaster, - # loaders 加载器 - "easy fullLoader": fullLoader, - "easy a1111Loader": a1111Loader, - "easy comfyLoader": comfyLoader, - "easy hunyuanDiTLoader": hunyuanDiTLoader, - "easy svdLoader": svdLoader, - "easy sv3dLoader": sv3DLoader, - "easy zero123Loader": zero123Loader, - "easy dynamiCrafterLoader": dynamiCrafterLoader, - "easy cascadeLoader": cascadeLoader, - "easy kolorsLoader": kolorsLoader, - "easy fluxLoader": fluxLoader, - "easy pixArtLoader": pixArtLoader, - "easy mochiLoader": mochiLoader, - "easy loraStack": loraStack, - "easy controlnetStack": controlnetStack, - "easy controlnetLoader": controlnetSimple, - "easy controlnetLoaderADV": controlnetAdvanced, - "easy controlnetLoader++": controlnetPlusPlus, - "easy LLLiteLoader": LLLiteLoader, - # Adapter 适配器 - "easy loraStackApply": applyLoraStack, - "easy controlnetStackApply": applyControlnetStack, - "easy ipadapterApply": ipadapterApply, - "easy ipadapterApplyADV": ipadapterApplyAdvanced, - "easy ipadapterApplyFaceIDKolors": ipadapterApplyFaceIDKolors, - "easy ipadapterApplyEncoder": ipadapterApplyEncoder, - "easy ipadapterApplyEmbeds": ipadapterApplyEmbeds, - "easy ipadapterApplyRegional": ipadapterApplyRegional, - "easy ipadapterApplyFromParams": ipadapterApplyFromParams, - "easy ipadapterStyleComposition": ipadapterStyleComposition, - "easy instantIDApply": instantIDApply, - "easy instantIDApplyADV": instantIDApplyAdvanced, - "easy pulIDApply": applyPulID, - "easy pulIDApplyADV": applyPulIDADV, - "easy styleAlignedBatchAlign": styleAlignedBatchAlign, - "easy icLightApply": icLightApply, - # Inpaint 内补 - "easy applyFooocusInpaint": applyFooocusInpaint, - "easy applyBrushNet": applyBrushNet, - "easy applyPowerPaint": applyPowerPaint, - "easy applyInpaint": applyInpaint, - # latent 潜空间 - "easy latentNoisy": latentNoisy, - "easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond, - "easy injectNoiseToLatent": injectNoiseToLatent, - # preSampling 预采样处理 - "easy preSampling": samplerSettings, - "easy preSamplingAdvanced": samplerSettingsAdvanced, - "easy preSamplingNoiseIn": samplerSettingsNoiseIn, - "easy preSamplingCustom": samplerCustomSettings, - "easy preSamplingSdTurbo": sdTurboSettings, - "easy preSamplingDynamicCFG": dynamicCFGSettings, - "easy preSamplingCascade": cascadeSettings, - "easy preSamplingLayerDiffusion": layerDiffusionSettings, - "easy preSamplingLayerDiffusionADDTL": layerDiffusionSettingsADDTL, - # kSampler k采样器 - "easy fullkSampler": samplerFull, - "easy kSampler": samplerSimple, - "easy kSamplerCustom": samplerSimpleCustom, - "easy kSamplerTiled": samplerSimpleTiled, - "easy kSamplerLayerDiffusion": samplerSimpleLayerDiffusion, - "easy kSamplerInpainting": samplerSimpleInpainting, - "easy kSamplerDownscaleUnet": samplerSimpleDownscaleUnet, - "easy kSamplerSDTurbo": samplerSDTurbo, - "easy fullCascadeKSampler": samplerCascadeFull, - "easy cascadeKSampler": samplerCascadeSimple, - "easy unSampler": unsampler, - # fix 修复相关 - "easy hiresFix": hiresFix, - "easy preDetailerFix": preDetailerFix, - "easy preMaskDetailerFix": preMaskDetailerFix, - "easy ultralyticsDetectorPipe": ultralyticsDetectorForDetailerFix, - "easy samLoaderPipe": samLoaderForDetailerFix, - "easy detailerFix": detailerFix, - # pipe 管道(节点束) - "easy pipeIn": pipeIn, - "easy pipeOut": pipeOut, - "easy pipeEdit": pipeEdit, - "easy pipeEditPrompt": pipeEditPrompt, - "easy pipeToBasicPipe": pipeToBasicPipe, - "easy pipeBatchIndex": pipeBatchIndex, - "easy XYPlot": pipeXYPlot, - "easy XYPlotAdvanced": pipeXYPlotAdvanced, - # XY Inputs - "easy XYInputs: Seeds++ Batch": XYplot_SeedsBatch, - "easy XYInputs: Steps": XYplot_Steps, - "easy XYInputs: CFG Scale": XYplot_CFG, - "easy XYInputs: FluxGuidance": XYplot_FluxGuidance, - "easy XYInputs: Sampler/Scheduler": XYplot_Sampler_Scheduler, - "easy XYInputs: Denoise": XYplot_Denoise, - "easy XYInputs: Checkpoint": XYplot_Checkpoint, - "easy XYInputs: Lora": XYplot_Lora, - "easy XYInputs: ModelMergeBlocks": XYplot_ModelMergeBlocks, - "easy XYInputs: PromptSR": XYplot_PromptSR, - "easy XYInputs: ControlNet": XYplot_Control_Net, - "easy XYInputs: PositiveCond": XYplot_Positive_Cond, - "easy XYInputs: PositiveCondList": XYplot_Positive_Cond_List, - "easy XYInputs: NegativeCond": XYplot_Negative_Cond, - "easy XYInputs: NegativeCondList": XYplot_Negative_Cond_List, - # others 其他 - "easy showSpentTime": showSpentTime, - "easy showLoaderSettingsNames": showLoaderSettingsNames, - "easy sliderControl": sliderControl, - "dynamicThresholdingFull": dynamicThresholdingFull, - # api 相关 - "easy stableDiffusion3API": stableDiffusion3API, - "easy fluxPromptGenAPI": fluxPromptGenAPI, - # utils - "easy ckptNames": setCkptName, - "easy controlnetNames": setControlName, -} - -NODE_DISPLAY_NAME_MAPPINGS = { - # seed 随机种 - "easy seed": "EasySeed", - "easy globalSeed": "EasyGlobalSeed", - # prompt 提示词 - "easy positive": "Positive", - "easy negative": "Negative", - "easy wildcards": "Wildcards", - "easy prompt": "Prompt", - "easy promptList": "PromptList", - "easy promptLine": "PromptLine", - "easy promptConcat": "PromptConcat", - "easy promptReplace": "PromptReplace", - "easy stylesSelector": "Styles Selector", - "easy portraitMaster": "Portrait Master", - # loaders 加载器 - "easy fullLoader": "EasyLoader (Full)", - "easy a1111Loader": "EasyLoader (A1111)", - "easy comfyLoader": "EasyLoader (Comfy)", - "easy svdLoader": "EasyLoader (SVD)", - "easy sv3dLoader": "EasyLoader (SV3D)", - "easy zero123Loader": "EasyLoader (Zero123)", - "easy dynamiCrafterLoader": "EasyLoader (DynamiCrafter)", - "easy cascadeLoader": "EasyCascadeLoader", - "easy kolorsLoader": "EasyLoader (Kolors)", - "easy fluxLoader": "EasyLoader (Flux)", - "easy hunyuanDiTLoader": "EasyLoader (HunyuanDiT)", - "easy pixArtLoader": "EasyLoader (PixArt)", - "easy mochiLoader": "EasyLoader (Mochi)", - "easy loraStack": "EasyLoraStack", - "easy controlnetStack": "EasyControlnetStack", - "easy controlnetLoader": "EasyControlnet", - "easy controlnetLoaderADV": "EasyControlnet (Advanced)", - "easy controlnetLoader++": "EasyControlnet++", - "easy LLLiteLoader": "EasyLLLite", - # Adapter 适配器 - "easy loraStackApply": "Easy Apply LoraStack", - "easy controlnetStackApply": "Easy Apply CnetStack", - "easy ipadapterApply": "Easy Apply IPAdapter", - "easy ipadapterApplyADV": "Easy Apply IPAdapter (Advanced)", - "easy ipadapterApplyFaceIDKolors": "Easy Apply IPAdapter (FaceID Kolors)", - "easy ipadapterStyleComposition": "Easy Apply IPAdapter (StyleComposition)", - "easy ipadapterApplyEncoder": "Easy Apply IPAdapter (Encoder)", - "easy ipadapterApplyRegional": "Easy Apply IPAdapter (Regional)", - "easy ipadapterApplyEmbeds": "Easy Apply IPAdapter (Embeds)", - "easy ipadapterApplyFromParams": "Easy Apply IPAdapter (From Params)", - "easy instantIDApply": "Easy Apply InstantID", - "easy instantIDApplyADV": "Easy Apply InstantID (Advanced)", - "easy pulIDApply": "Easy Apply PuLID", - "easy pulIDApplyADV": "Easy Apply PuLID (Advanced)", - "easy styleAlignedBatchAlign": "Easy Apply StyleAlign", - "easy icLightApply": "Easy Apply ICLight", - # Inpaint 内补 - "easy applyFooocusInpaint": "Easy Apply Fooocus Inpaint", - "easy applyBrushNet": "Easy Apply BrushNet", - "easy applyPowerPaint": "Easy Apply PowerPaint", - "easy applyInpaint": "Easy Apply Inpaint", - # latent 潜空间 - "easy latentNoisy": "LatentNoisy", - "easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond", - "easy injectNoiseToLatent": "InjectNoiseToLatent", - # preSampling 预采样处理 - "easy preSampling": "PreSampling", - "easy preSamplingAdvanced": "PreSampling (Advanced)", - "easy preSamplingNoiseIn": "PreSampling (NoiseIn)", - "easy preSamplingCustom": "PreSampling (Custom)", - "easy preSamplingSdTurbo": "PreSampling (SDTurbo)", - "easy preSamplingDynamicCFG": "PreSampling (DynamicCFG)", - "easy preSamplingCascade": "PreSampling (Cascade)", - "easy preSamplingLayerDiffusion": "PreSampling (LayerDiffuse)", - "easy preSamplingLayerDiffusionADDTL": "PreSampling (LayerDiffuse ADDTL)", - # kSampler k采样器 - "easy kSampler": "EasyKSampler", - "easy kSamplerCustom": "EasyKSampler (Custom)", - "easy fullkSampler": "EasyKSampler (Full)", - "easy kSamplerTiled": "EasyKSampler (Tiled Decode)", - "easy kSamplerLayerDiffusion": "EasyKSampler (LayerDiffuse)", - "easy kSamplerInpainting": "EasyKSampler (Inpainting)", - "easy kSamplerDownscaleUnet": "EasyKsampler (Downscale Unet)", - "easy kSamplerSDTurbo": "EasyKSampler (SDTurbo)", - "easy cascadeKSampler": "EasyCascadeKsampler", - "easy fullCascadeKSampler": "EasyCascadeKsampler (Full)", - "easy unSampler": "EasyUnSampler", - # fix 修复相关 - "easy hiresFix": "HiresFix", - "easy preDetailerFix": "PreDetailerFix", - "easy preMaskDetailerFix": "preMaskDetailerFix", - "easy ultralyticsDetectorPipe": "UltralyticsDetector (Pipe)", - "easy samLoaderPipe": "SAMLoader (Pipe)", - "easy detailerFix": "DetailerFix", - # pipe 管道(节点束) - "easy pipeIn": "Pipe In", - "easy pipeOut": "Pipe Out", - "easy pipeEdit": "Pipe Edit", - "easy pipeEditPrompt": "Pipe Edit Prompt", - "easy pipeBatchIndex": "Pipe Batch Index", - "easy pipeToBasicPipe": "Pipe -> BasicPipe", - "easy XYPlot": "XY Plot", - "easy XYPlotAdvanced": "XY Plot Advanced", - # XY Inputs - "easy XYInputs: Seeds++ Batch": "XY Inputs: Seeds++ Batch //EasyUse", - "easy XYInputs: Steps": "XY Inputs: Steps //EasyUse", - "easy XYInputs: CFG Scale": "XY Inputs: CFG Scale //EasyUse", - "easy XYInputs: FluxGuidance": "XY Inputs: Flux Guidance //EasyUse", - "easy XYInputs: Sampler/Scheduler": "XY Inputs: Sampler/Scheduler //EasyUse", - "easy XYInputs: Denoise": "XY Inputs: Denoise //EasyUse", - "easy XYInputs: Checkpoint": "XY Inputs: Checkpoint //EasyUse", - "easy XYInputs: Lora": "XY Inputs: Lora //EasyUse", - "easy XYInputs: ModelMergeBlocks": "XY Inputs: ModelMergeBlocks //EasyUse", - "easy XYInputs: PromptSR": "XY Inputs: PromptSR //EasyUse", - "easy XYInputs: ControlNet": "XY Inputs: Controlnet //EasyUse", - "easy XYInputs: PositiveCond": "XY Inputs: PosCond //EasyUse", - "easy XYInputs: PositiveCondList": "XY Inputs: PosCondList //EasyUse", - "easy XYInputs: NegativeCond": "XY Inputs: NegCond //EasyUse", - "easy XYInputs: NegativeCondList": "XY Inputs: NegCondList //EasyUse", - # others 其他 - "easy showSpentTime": "Show Spent Time", - "easy showLoaderSettingsNames": "Show Loader Settings Names", - "easy sliderControl": "Easy Slider Control", - "dynamicThresholdingFull": "DynamicThresholdingFull", - # api 相关 - "easy stableDiffusion3API": "Stable Diffusion 3 (API)", - "easy fluxPromptGenAPI": "Flux Prompt Gen (API)", - # utils - "easy ckptNames": "Ckpt Names", - "easy controlnetNames": "ControlNet Names", +import sys, os, re, json, time +import torch +import folder_paths +import numpy as np +import comfy.utils, comfy.sample, comfy.samplers, comfy.controlnet, comfy.model_base, comfy.model_management, comfy.sampler_helpers, comfy.supported_models +from comfy.sd import CLIP, VAE +from comfy.model_patcher import ModelPatcher +from comfy_extras.chainner_models import model_loading +from comfy_extras.nodes_mask import LatentCompositeMasked, GrowMask +from comfy_extras.nodes_compositing import JoinImageWithAlpha +from comfy.clip_vision import load as load_clip_vision +from urllib.request import urlopen +from PIL import Image + +from server import PromptServer +from nodes import MAX_RESOLUTION, LatentFromBatch, RepeatLatentBatch, NODE_CLASS_MAPPINGS as ALL_NODE_CLASS_MAPPINGS, ConditioningSetMask, ConditioningConcat, CLIPTextEncode, VAEEncodeForInpaint, InpaintModelConditioning, ConditioningZeroOut +from .layer_diffuse import LayerDiffuse, LayerMethod +from .xyplot import * +from .config import * + +from .libs.log import log_node_info, log_node_error, log_node_warn +from .libs.adv_encode import advanced_encode +from .libs.wildcards import process_with_loras, get_wildcard_list, process +from .libs.utils import find_wildcards_seed, is_linked_styles_selector, easySave, get_local_filepath, AlwaysEqualProxy, get_sd_version +from .libs.loader import easyLoader +from .libs.sampler import easySampler, alignYourStepsScheduler, gitsScheduler +from .libs.xyplot import easyXYPlot +from .libs.controlnet import easyControlnet, union_controlnet_types +from .libs.conditioning import prompt_to_cond, set_cond +from .libs.easing import EasingBase +from .libs.translate import has_chinese, zh_to_en +from .libs import cache as backend_cache + +sampler = easySampler() +easyCache = easyLoader() + +new_schedulers = ['align_your_steps', 'gits'] +# ---------------------------------------------------------------提示词 开始----------------------------------------------------------------------# + +# 正面提示词 +class positivePrompt: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return {"required": { + "positive": ("STRING", {"default": "", "multiline": True, "placeholder": "Positive"}),} + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("positive",) + FUNCTION = "main" + + CATEGORY = "EasyUse/Prompt" + + @staticmethod + def main(positive): + return positive, + +# 通配符提示词 +class wildcardsPrompt: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + wildcard_list = get_wildcard_list() + return {"required": { + "text": ("STRING", {"default": "", "multiline": True, "dynamicPrompts": False, "placeholder": "(Support Lora Block Weight and wildcard)"}), + "Select to add LoRA": (["Select the LoRA to add to the text"] + folder_paths.get_filename_list("loras"),), + "Select to add Wildcard": (["Select the Wildcard to add to the text"] + wildcard_list,), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + "multiline_mode": ("BOOLEAN", {"default": False}), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("STRING", "STRING") + RETURN_NAMES = ("text", "populated_text") + OUTPUT_IS_LIST = (True, True) + FUNCTION = "main" + + CATEGORY = "EasyUse/Prompt" + + def translate(self, text): + return text + + def main(self, *args, **kwargs): + prompt = kwargs["prompt"] if "prompt" in kwargs else None + seed = kwargs["seed"] + + # Clean loaded_objects + if prompt: + easyCache.update_loaded_objects(prompt) + + text = kwargs['text'] + if "multiline_mode" in kwargs and kwargs["multiline_mode"]: + populated_text = [] + _text = [] + text = text.split("\n") + for t in text: + t = self.translate(t) + _text.append(t) + populated_text.append(process(t, seed)) + text = _text + else: + text = self.translate(text) + populated_text = [process(text, seed)] + text = [text] + return {"ui": {"value": [seed]}, "result": (text, populated_text)} + +# 负面提示词 +class negativePrompt: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return {"required": { + "negative": ("STRING", {"default": "", "multiline": True, "placeholder": "Negative"}),} + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("negative",) + FUNCTION = "main" + + CATEGORY = "EasyUse/Prompt" + + @staticmethod + def main(negative): + return negative, + +# 风格提示词选择器 +class stylesPromptSelector: + + @classmethod + def INPUT_TYPES(s): + styles = ["fooocus_styles"] + styles_dir = FOOOCUS_STYLES_DIR + for file_name in os.listdir(styles_dir): + file = os.path.join(styles_dir, file_name) + if os.path.isfile(file) and file_name.endswith(".json"): + styles.append(file_name.split(".")[0]) + return { + "required": { + "styles": (styles, {"default": "fooocus_styles"}), + }, + "optional": { + "positive": ("STRING", {"forceInput": True}), + "negative": ("STRING", {"forceInput": True}), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("STRING", "STRING",) + RETURN_NAMES = ("positive", "negative",) + + CATEGORY = 'EasyUse/Prompt' + FUNCTION = 'run' + + def run(self, styles, positive='', negative='', prompt=None, extra_pnginfo=None, my_unique_id=None): + values = [] + all_styles = {} + positive_prompt, negative_prompt = '', negative + if styles == "fooocus_styles": + file = os.path.join(RESOURCES_DIR, styles + '.json') + else: + file = os.path.join(FOOOCUS_STYLES_DIR, styles + '.json') + f = open(file, 'r', encoding='utf-8') + data = json.load(f) + f.close() + for d in data: + all_styles[d['name']] = d + if my_unique_id in prompt: + if prompt[my_unique_id]["inputs"]['select_styles']: + values = prompt[my_unique_id]["inputs"]['select_styles'].split(',') + + has_prompt = False + if len(values) == 0: + return (positive, negative) + + for index, val in enumerate(values): + if 'prompt' in all_styles[val]: + if "{prompt}" in all_styles[val]['prompt'] and has_prompt == False: + positive_prompt = all_styles[val]['prompt'].replace('{prompt}', positive) + has_prompt = True + else: + positive_prompt += ', ' + all_styles[val]['prompt'].replace(', {prompt}', '').replace('{prompt}', '') + if 'negative_prompt' in all_styles[val]: + negative_prompt += ', ' + all_styles[val]['negative_prompt'] if negative_prompt else all_styles[val]['negative_prompt'] + + if has_prompt == False and positive: + positive_prompt = positive + ', ' + + return (positive_prompt, negative_prompt) + +#prompt +class prompt: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "prompt": ("STRING", {"default": "", "multiline": True, "placeholder": "Prompt"}), + "main": ([ + 'none', + 'beautiful woman, detailed face', + 'handsome man, detailed face', + 'pretty girl', + 'handsome boy', + 'dog', + 'cat', + 'Buddha', + 'toy' + ], {"default": "none"}), + "lighting": ([ + 'none', + 'sunshine from window', + 'neon light, city', + 'sunset over sea', + 'golden time', + 'sci-fi RGB glowing, cyberpunk', + 'natural lighting', + 'warm atmosphere, at home, bedroom', + 'magic lit', + 'evil, gothic, Yharnam', + 'light and shadow', + 'shadow from window', + 'soft studio lighting', + 'home atmosphere, cozy bedroom illumination', + 'neon, Wong Kar-wai, warm', + 'cinemative lighting', + 'neo punk lighting, cyberpunk', + ],{"default":'none'}) + }} + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("prompt",) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Prompt" + + def doit(self, prompt, main, lighting): + if lighting != 'none' and main != 'none': + prompt = main + ',' + lighting + ',' + prompt + elif lighting != 'none' and main == 'none': + prompt = prompt + ',' + lighting + elif main != 'none': + prompt = main + ',' + prompt + + return prompt, +#promptList +class promptList: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "prompt_1": ("STRING", {"multiline": True, "default": ""}), + "prompt_2": ("STRING", {"multiline": True, "default": ""}), + "prompt_3": ("STRING", {"multiline": True, "default": ""}), + "prompt_4": ("STRING", {"multiline": True, "default": ""}), + "prompt_5": ("STRING", {"multiline": True, "default": ""}), + }, + "optional": { + "optional_prompt_list": ("LIST",) + } + } + + RETURN_TYPES = ("LIST", "STRING") + RETURN_NAMES = ("prompt_list", "prompt_strings") + OUTPUT_IS_LIST = (False, True) + FUNCTION = "run" + CATEGORY = "EasyUse/Prompt" + + def run(self, **kwargs): + prompts = [] + + if "optional_prompt_list" in kwargs: + for l in kwargs["optional_prompt_list"]: + prompts.append(l) + + # Iterate over the received inputs in sorted order. + for k in sorted(kwargs.keys()): + v = kwargs[k] + + # Only process string input ports. + if isinstance(v, str) and v != '': + prompts.append(v) + + return (prompts, prompts) + +#promptLine +class promptLine: + + @classmethod + def INPUT_TYPES(s): + return {"required": { + "prompt": ("STRING", {"multiline": True, "default": "text"}), + "start_index": ("INT", {"default": 0, "min": 0, "max": 9999}), + "max_rows": ("INT", {"default": 1000, "min": 1, "max": 9999}), + }, + "hidden":{ + "workflow_prompt": "PROMPT", "my_unique_id": "UNIQUE_ID" + } + } + + RETURN_TYPES = ("STRING", AlwaysEqualProxy('*')) + RETURN_NAMES = ("STRING", "COMBO") + OUTPUT_IS_LIST = (True, True) + FUNCTION = "generate_strings" + CATEGORY = "EasyUse/Prompt" + + def generate_strings(self, prompt, start_index, max_rows, workflow_prompt=None, my_unique_id=None): + lines = prompt.split('\n') + # lines = [zh_to_en([v])[0] if has_chinese(v) else v for v in lines if v] + + start_index = max(0, min(start_index, len(lines) - 1)) + + end_index = min(start_index + max_rows, len(lines)) + + rows = lines[start_index:end_index] + + return (rows, rows) + +class promptConcat: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + }, + "optional": { + "prompt1": ("STRING", {"multiline": False, "default": "", "forceInput": True}), + "prompt2": ("STRING", {"multiline": False, "default": "", "forceInput": True}), + "separator": ("STRING", {"multiline": False, "default": ""}), + }, + } + RETURN_TYPES = ("STRING", ) + RETURN_NAMES = ("prompt", ) + FUNCTION = "concat_text" + CATEGORY = "EasyUse/Prompt" + + def concat_text(self, prompt1="", prompt2="", separator=""): + + return (prompt1 + separator + prompt2,) + +class promptReplace: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"multiline": True, "default": "", "forceInput": True}), + }, + "optional": { + "find1": ("STRING", {"multiline": False, "default": ""}), + "replace1": ("STRING", {"multiline": False, "default": ""}), + "find2": ("STRING", {"multiline": False, "default": ""}), + "replace2": ("STRING", {"multiline": False, "default": ""}), + "find3": ("STRING", {"multiline": False, "default": ""}), + "replace3": ("STRING", {"multiline": False, "default": ""}), + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("prompt",) + FUNCTION = "replace_text" + CATEGORY = "EasyUse/Prompt" + + def replace_text(self, prompt, find1="", replace1="", find2="", replace2="", find3="", replace3=""): + + prompt = prompt.replace(find1, replace1) + prompt = prompt.replace(find2, replace2) + prompt = prompt.replace(find3, replace3) + + return (prompt,) + + +# 肖像大师 +# Created by AI Wiz Art (Stefano Flore) +# Version: 2.2 +# https://stefanoflore.it +# https://ai-wiz.art +class portraitMaster: + + @classmethod + def INPUT_TYPES(s): + max_float_value = 1.95 + prompt_path = os.path.join(RESOURCES_DIR, 'portrait_prompt.json') + if not os.path.exists(prompt_path): + response = urlopen('https://raw.githubusercontent.com/yolain/ComfyUI-Easy-Use/main/resources/portrait_prompt.json') + temp_prompt = json.loads(response.read()) + prompt_serialized = json.dumps(temp_prompt, indent=4) + with open(prompt_path, "w") as f: + f.write(prompt_serialized) + del response, temp_prompt + # Load local + with open(prompt_path, 'r') as f: + list = json.load(f) + keys = [ + ['shot', 'COMBO', {"key": "shot_list"}], ['shot_weight', 'FLOAT'], + ['gender', 'COMBO', {"default": "Woman", "key": "gender_list"}], ['age', 'INT', {"default": 30, "min": 18, "max": 90, "step": 1, "display": "slider"}], + ['nationality_1', 'COMBO', {"default": "Chinese", "key": "nationality_list"}], ['nationality_2', 'COMBO', {"key": "nationality_list"}], ['nationality_mix', 'FLOAT'], + ['body_type', 'COMBO', {"key": "body_type_list"}], ['body_type_weight', 'FLOAT'], ['model_pose', 'COMBO', {"key": "model_pose_list"}], ['eyes_color', 'COMBO', {"key": "eyes_color_list"}], + ['facial_expression', 'COMBO', {"key": "face_expression_list"}], ['facial_expression_weight', 'FLOAT'], ['face_shape', 'COMBO', {"key": "face_shape_list"}], ['face_shape_weight', 'FLOAT'], ['facial_asymmetry', 'FLOAT'], + ['hair_style', 'COMBO', {"key": "hair_style_list"}], ['hair_color', 'COMBO', {"key": "hair_color_list"}], ['disheveled', 'FLOAT'], ['beard', 'COMBO', {"key": "beard_list"}], + ['skin_details', 'FLOAT'], ['skin_pores', 'FLOAT'], ['dimples', 'FLOAT'], ['freckles', 'FLOAT'], + ['moles', 'FLOAT'], ['skin_imperfections', 'FLOAT'], ['skin_acne', 'FLOAT'], ['tanned_skin', 'FLOAT'], + ['eyes_details', 'FLOAT'], ['iris_details', 'FLOAT'], ['circular_iris', 'FLOAT'], ['circular_pupil', 'FLOAT'], + ['light_type', 'COMBO', {"key": "light_type_list"}], ['light_direction', 'COMBO', {"key": "light_direction_list"}], ['light_weight', 'FLOAT'] + ] + widgets = {} + for i, obj in enumerate(keys): + if obj[1] == 'COMBO': + key = obj[2]['key'] if obj[2] and 'key' in obj[2] else obj[0] + _list = list[key].copy() + _list.insert(0, '-') + widgets[obj[0]] = (_list, {**obj[2]}) + elif obj[1] == 'FLOAT': + widgets[obj[0]] = ("FLOAT", {"default": 0, "step": 0.05, "min": 0, "max": max_float_value, "display": "slider",}) + elif obj[1] == 'INT': + widgets[obj[0]] = (obj[1], obj[2]) + del list + return { + "required": { + **widgets, + "photorealism_improvement": (["enable", "disable"],), + "prompt_start": ("STRING", {"multiline": True, "default": "raw photo, (realistic:1.5)"}), + "prompt_additional": ("STRING", {"multiline": True, "default": ""}), + "prompt_end": ("STRING", {"multiline": True, "default": ""}), + "negative_prompt": ("STRING", {"multiline": True, "default": ""}), + } + } + + RETURN_TYPES = ("STRING", "STRING",) + RETURN_NAMES = ("positive", "negative",) + + FUNCTION = "pm" + + CATEGORY = "EasyUse/Prompt" + + def pm(self, shot="-", shot_weight=1, gender="-", body_type="-", body_type_weight=0, eyes_color="-", + facial_expression="-", facial_expression_weight=0, face_shape="-", face_shape_weight=0, + nationality_1="-", nationality_2="-", nationality_mix=0.5, age=30, hair_style="-", hair_color="-", + disheveled=0, dimples=0, freckles=0, skin_pores=0, skin_details=0, moles=0, skin_imperfections=0, + wrinkles=0, tanned_skin=0, eyes_details=1, iris_details=1, circular_iris=1, circular_pupil=1, + facial_asymmetry=0, prompt_additional="", prompt_start="", prompt_end="", light_type="-", + light_direction="-", light_weight=0, negative_prompt="", photorealism_improvement="disable", beard="-", + model_pose="-", skin_acne=0): + + prompt = [] + + if gender == "-": + gender = "" + else: + if age <= 25 and gender == 'Woman': + gender = 'girl' + if age <= 25 and gender == 'Man': + gender = 'boy' + gender = " " + gender + " " + + if nationality_1 != '-' and nationality_2 != '-': + nationality = f"[{nationality_1}:{nationality_2}:{round(nationality_mix, 2)}]" + elif nationality_1 != '-': + nationality = nationality_1 + " " + elif nationality_2 != '-': + nationality = nationality_2 + " " + else: + nationality = "" + + if prompt_start != "": + prompt.append(f"{prompt_start}") + + if shot != "-" and shot_weight > 0: + prompt.append(f"({shot}:{round(shot_weight, 2)})") + + prompt.append(f"({nationality}{gender}{round(age)}-years-old:1.5)") + + if body_type != "-" and body_type_weight > 0: + prompt.append(f"({body_type}, {body_type} body:{round(body_type_weight, 2)})") + + if model_pose != "-": + prompt.append(f"({model_pose}:1.5)") + + if eyes_color != "-": + prompt.append(f"({eyes_color} eyes:1.25)") + + if facial_expression != "-" and facial_expression_weight > 0: + prompt.append( + f"({facial_expression}, {facial_expression} expression:{round(facial_expression_weight, 2)})") + + if face_shape != "-" and face_shape_weight > 0: + prompt.append(f"({face_shape} shape face:{round(face_shape_weight, 2)})") + + if hair_style != "-": + prompt.append(f"({hair_style} hairstyle:1.25)") + + if hair_color != "-": + prompt.append(f"({hair_color} hair:1.25)") + + if beard != "-": + prompt.append(f"({beard}:1.15)") + + if disheveled != "-" and disheveled > 0: + prompt.append(f"(disheveled:{round(disheveled, 2)})") + + if prompt_additional != "": + prompt.append(f"{prompt_additional}") + + if skin_details > 0: + prompt.append(f"(skin details, skin texture:{round(skin_details, 2)})") + + if skin_pores > 0: + prompt.append(f"(skin pores:{round(skin_pores, 2)})") + + if skin_imperfections > 0: + prompt.append(f"(skin imperfections:{round(skin_imperfections, 2)})") + + if skin_acne > 0: + prompt.append(f"(acne, skin with acne:{round(skin_acne, 2)})") + + if wrinkles > 0: + prompt.append(f"(skin imperfections:{round(wrinkles, 2)})") + + if tanned_skin > 0: + prompt.append(f"(tanned skin:{round(tanned_skin, 2)})") + + if dimples > 0: + prompt.append(f"(dimples:{round(dimples, 2)})") + + if freckles > 0: + prompt.append(f"(freckles:{round(freckles, 2)})") + + if moles > 0: + prompt.append(f"(skin pores:{round(moles, 2)})") + + if eyes_details > 0: + prompt.append(f"(eyes details:{round(eyes_details, 2)})") + + if iris_details > 0: + prompt.append(f"(iris details:{round(iris_details, 2)})") + + if circular_iris > 0: + prompt.append(f"(circular iris:{round(circular_iris, 2)})") + + if circular_pupil > 0: + prompt.append(f"(circular pupil:{round(circular_pupil, 2)})") + + if facial_asymmetry > 0: + prompt.append(f"(facial asymmetry, face asymmetry:{round(facial_asymmetry, 2)})") + + if light_type != '-' and light_weight > 0: + if light_direction != '-': + prompt.append(f"({light_type} {light_direction}:{round(light_weight, 2)})") + else: + prompt.append(f"({light_type}:{round(light_weight, 2)})") + + if prompt_end != "": + prompt.append(f"{prompt_end}") + + prompt = ", ".join(prompt) + prompt = prompt.lower() + + if photorealism_improvement == "enable": + prompt = prompt + ", (professional photo, balanced photo, balanced exposure:1.2), (film grain:1.15)" + + if photorealism_improvement == "enable": + negative_prompt = negative_prompt + ", (shinny skin, reflections on the skin, skin reflections:1.25)" + + log_node_info("Portrait Master as generate the prompt:", prompt) + + return (prompt, negative_prompt,) + +# ---------------------------------------------------------------提示词 结束----------------------------------------------------------------------# + +# ---------------------------------------------------------------潜空间 开始----------------------------------------------------------------------# +# 潜空间sigma相乘 +class latentNoisy: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS,), + "steps": ("INT", {"default": 10000, "min": 0, "max": 10000}), + "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at_step": ("INT", {"default": 10000, "min": 1, "max": 10000}), + "source": (["CPU", "GPU"],), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + }, + "optional": { + "pipe": ("PIPE_LINE",), + "optional_model": ("MODEL",), + "optional_latent": ("LATENT",) + }} + + RETURN_TYPES = ("PIPE_LINE", "LATENT", "FLOAT",) + RETURN_NAMES = ("pipe", "latent", "sigma",) + FUNCTION = "run" + + CATEGORY = "EasyUse/Latent" + + def run(self, sampler_name, scheduler, steps, start_at_step, end_at_step, source, seed, pipe=None, optional_model=None, optional_latent=None): + model = optional_model if optional_model is not None else pipe["model"] + batch_size = pipe["loader_settings"]["batch_size"] + empty_latent_height = pipe["loader_settings"]["empty_latent_height"] + empty_latent_width = pipe["loader_settings"]["empty_latent_width"] + + if optional_latent is not None: + samples = optional_latent + else: + torch.manual_seed(seed) + if source == "CPU": + device = "cpu" + else: + device = comfy.model_management.get_torch_device() + noise = torch.randn((batch_size, 4, empty_latent_height // 8, empty_latent_width // 8), dtype=torch.float32, + device=device).cpu() + + samples = {"samples": noise} + + device = comfy.model_management.get_torch_device() + end_at_step = min(steps, end_at_step) + start_at_step = min(start_at_step, end_at_step) + comfy.model_management.load_model_gpu(model) + model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device()) + sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name, + scheduler=scheduler, denoise=1.0, model_options=model.model_options) + sigmas = sampler.sigmas + sigma = sigmas[start_at_step] - sigmas[end_at_step] + sigma /= model.model.latent_format.scale_factor + sigma = sigma.cpu().numpy() + + samples_out = samples.copy() + + s1 = samples["samples"] + samples_out["samples"] = s1 * sigma + + if pipe is None: + pipe = {} + new_pipe = { + **pipe, + "samples": samples_out + } + del pipe + + return (new_pipe, samples_out, sigma) + +# Latent遮罩复合 +class latentCompositeMaskedWithCond: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "text_combine": ("LIST",), + "source_latent": ("LATENT",), + "source_mask": ("MASK",), + "destination_mask": ("MASK",), + "text_combine_mode": (["add", "replace", "cover"], {"default": "add"}), + "replace_text": ("STRING", {"default": ""}) + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + OUTPUT_IS_LIST = (False, False, True) + RETURN_TYPES = ("PIPE_LINE", "LATENT", "CONDITIONING") + RETURN_NAMES = ("pipe", "latent", "conditioning",) + FUNCTION = "run" + + CATEGORY = "EasyUse/Latent" + + def run(self, pipe, text_combine, source_latent, source_mask, destination_mask, text_combine_mode, replace_text, prompt=None, extra_pnginfo=None, my_unique_id=None): + positive = None + clip = pipe["clip"] + destination_latent = pipe["samples"] + + conds = [] + + for text in text_combine: + if text_combine_mode == 'cover': + positive = text + elif text_combine_mode == 'replace' and replace_text != '': + positive = pipe["loader_settings"]["positive"].replace(replace_text, text) + else: + positive = pipe["loader_settings"]["positive"] + ',' + text + positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"] + positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"] + a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"] + positive_cond = pipe["positive"] + + log_node_warn("Positive encoding...") + steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 1 + positive_embeddings_final = advanced_encode(clip, positive, + positive_token_normalization, + positive_weight_interpretation, w_max=1.0, + apply_to_pooled='enable', a1111_prompt_style=a1111_prompt_style, steps=steps) + + # source cond + (cond_1,) = ConditioningSetMask().append(positive_cond, source_mask, "default", 1) + (cond_2,) = ConditioningSetMask().append(positive_embeddings_final, destination_mask, "default", 1) + positive_cond = cond_1 + cond_2 + + conds.append(positive_cond) + # latent composite masked + (samples,) = LatentCompositeMasked().composite(destination_latent, source_latent, 0, 0, False) + + new_pipe = { + **pipe, + "samples": samples, + "loader_settings": { + **pipe["loader_settings"], + "positive": positive, + } + } + + del pipe + + return (new_pipe, samples, conds) + +# 噪声注入到潜空间 +class injectNoiseToLatent: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "strength": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 200.0, "step": 0.0001}), + "normalize": ("BOOLEAN", {"default": False}), + "average": ("BOOLEAN", {"default": False}), + }, + "optional": { + "pipe_to_noise": ("PIPE_LINE",), + "image_to_latent": ("IMAGE",), + "latent": ("LATENT",), + "noise": ("LATENT",), + "mask": ("MASK",), + "mix_randn_amount": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1000.0, "step": 0.001}), + "seed": ("INT", {"default": 123, "min": 0, "max": 0xffffffffffffffff, "step": 1}), + } + } + + RETURN_TYPES = ("LATENT",) + FUNCTION = "inject" + CATEGORY = "EasyUse/Latent" + + def inject(self,strength, normalize, average, pipe_to_noise=None, noise=None, image_to_latent=None, latent=None, mix_randn_amount=0, mask=None, seed=None): + + vae = pipe_to_noise["vae"] if pipe_to_noise is not None else pipe_to_noise["vae"] + batch_size = pipe_to_noise["loader_settings"]["batch_size"] if pipe_to_noise is not None and "batch_size" in pipe_to_noise["loader_settings"] else 1 + if noise is None and pipe_to_noise is not None: + noise = pipe_to_noise["samples"] + elif noise is None: + raise Exception("InjectNoiseToLatent: No noise provided") + + if image_to_latent is not None and vae is not None: + samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} + latents = RepeatLatentBatch().repeat(samples, batch_size)[0] + elif latent is not None: + latents = latent + else: + latents = {"samples": noise["samples"].clone()} + + samples = latents.copy() + if latents["samples"].shape != noise["samples"].shape: + raise ValueError("InjectNoiseToLatent: Latent and noise must have the same shape") + if average: + noised = (samples["samples"].clone() + noise["samples"].clone()) / 2 + else: + noised = samples["samples"].clone() + noise["samples"].clone() * strength + if normalize: + noised = noised / noised.std() + if mask is not None: + mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), + size=(noised.shape[2], noised.shape[3]), mode="bilinear") + mask = mask.expand((-1, noised.shape[1], -1, -1)) + if mask.shape[0] < noised.shape[0]: + mask = mask.repeat((noised.shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:noised.shape[0]] + noised = mask * noised + (1 - mask) * latents["samples"] + if mix_randn_amount > 0: + if seed is not None: + torch.manual_seed(seed) + rand_noise = torch.randn_like(noised) + noised = ((1 - mix_randn_amount) * noised + mix_randn_amount * + rand_noise) / ((mix_randn_amount ** 2 + (1 - mix_randn_amount) ** 2) ** 0.5) + samples["samples"] = noised + return (samples,) + +# ---------------------------------------------------------------潜空间 结束----------------------------------------------------------------------# + +# ---------------------------------------------------------------随机种 开始----------------------------------------------------------------------# +# 随机种 +class easySeed: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("INT",) + RETURN_NAMES = ("seed",) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Seed" + + def doit(self, seed=0, prompt=None, extra_pnginfo=None, my_unique_id=None): + return seed, + +# 全局随机种 +class globalSeed: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "value": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + "mode": ("BOOLEAN", {"default": True, "label_on": "control_before_generate", "label_off": "control_after_generate"}), + "action": (["fixed", "increment", "decrement", "randomize", + "increment for each node", "decrement for each node", "randomize for each node"], ), + "last_seed": ("STRING", {"default": ""}), + } + } + + RETURN_TYPES = () + FUNCTION = "doit" + + CATEGORY = "EasyUse/Seed" + + OUTPUT_NODE = True + + def doit(self, **kwargs): + return {} + +# ---------------------------------------------------------------随机种 结束----------------------------------------------------------------------# + +# ---------------------------------------------------------------加载器 开始----------------------------------------------------------------------# +class setCkptName: + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), + } + } + + RETURN_TYPES = (AlwaysEqualProxy('*'),) + RETURN_NAMES = ("ckpt_name",) + FUNCTION = "set_name" + CATEGORY = "EasyUse/Util" + + def set_name(self, ckpt_name): + return (ckpt_name,) + +class setControlName: + + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "controlnet_name": (folder_paths.get_filename_list("controlnet"),), + } + } + + RETURN_TYPES = (AlwaysEqualProxy('*'),) + RETURN_NAMES = ("controlnet_name",) + FUNCTION = "set_name" + CATEGORY = "EasyUse/Util" + + def set_name(self, controlnet_name): + return (controlnet_name,) + +# 简易加载器完整 +resolution_strings = [f"{width} x {height} (custom)" if width == 'width' and height == 'height' else f"{width} x {height}" for width, height in BASE_RESOLUTIONS] +class fullLoader: + + @classmethod + def INPUT_TYPES(cls): + a1111_prompt_style_default = False + + return {"required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), + "config_name": (["Default", ] + folder_paths.get_filename_list("configs"), {"default": "Default"}), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), + + "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + + "resolution": (resolution_strings,), + "empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + "positive_token_normalization": (["none", "mean", "length", "length+mean"],), + "positive_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), + + "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), + "negative_token_normalization": (["none", "mean", "length", "length+mean"],), + "negative_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), + + "batch_size": ( + "INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) + }, + "optional": {"model_override": ("MODEL",), "clip_override": ("CLIP",), "vae_override": ("VAE",), "optional_lora_stack": ("LORA_STACK",), "optional_controlnet_stack": ("CONTROL_NET_STACK",), "a1111_prompt_style": ("BOOLEAN", {"default": a1111_prompt_style_default})}, + "hidden": {"video_length": "INT", "prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE", "CLIP", "CONDITIONING", "CONDITIONING", "LATENT") + RETURN_NAMES = ("pipe", "model", "vae", "clip", "positive", "negative", "latent") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def adv_pipeloader(self, ckpt_name, config_name, vae_name, clip_skip, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, positive_token_normalization, positive_weight_interpretation, + negative, negative_token_normalization, negative_weight_interpretation, + batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, optional_controlnet_stack=None, a1111_prompt_style=False, video_length=25, prompt=None, + my_unique_id=None + ): + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + # Load models + log_node_warn("Loading models...") + model, clip, vae, clip_vision, lora_stack = easyCache.load_main(ckpt_name, config_name, vae_name, lora_name, lora_model_strength, lora_clip_strength, optional_lora_stack, model_override, clip_override, vae_override, prompt) + + # Create Empty Latent + model_type = get_sd_version(model) + samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size, model_type=model_type, video_length=video_length) + + # Prompt to Conditioning + positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, lora_stack, positive, positive_token_normalization, positive_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache, model_type=model_type) + negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, clip_skip, lora_stack, negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache, model_type=model_type) + + if negative_embeddings_final is None: + negative_embeddings_final, = ConditioningZeroOut().zero_out(positive_embeddings_final) + + # Conditioning add controlnet + if optional_controlnet_stack is not None and len(optional_controlnet_stack) > 0: + for controlnet in optional_controlnet_stack: + positive_embeddings_final, negative_embeddings_final = easyControlnet().apply(controlnet[0], controlnet[5], positive_embeddings_final, negative_embeddings_final, controlnet[1], start_percent=controlnet[2], end_percent=controlnet[3], control_net=None, scale_soft_weights=controlnet[4], mask=None, easyCache=easyCache, use_cache=True, model=model, vae=vae) + + pipe = { + "model": model, + "positive": positive_embeddings_final, + "negative": negative_embeddings_final, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": None, + + "loader_settings": { + "ckpt_name": ckpt_name, + "vae_name": vae_name, + "lora_name": lora_name, + "lora_model_strength": lora_model_strength, + "lora_clip_strength": lora_clip_strength, + "lora_stack": lora_stack, + + "clip_skip": clip_skip, + "a1111_prompt_style": a1111_prompt_style, + "positive": positive, + "positive_token_normalization": positive_token_normalization, + "positive_weight_interpretation": positive_weight_interpretation, + "negative": negative, + "negative_token_normalization": negative_token_normalization, + "negative_weight_interpretation": negative_weight_interpretation, + "resolution": resolution, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + } + } + + return {"ui": {"positive": positive_wildcard_prompt, "negative": negative_wildcard_prompt}, "result": (pipe, model, vae, clip, positive_embeddings_final, negative_embeddings_final, samples)} + +# A1111简易加载器 +class a1111Loader(fullLoader): + @classmethod + def INPUT_TYPES(cls): + a1111_prompt_style_default = False + checkpoints = folder_paths.get_filename_list("checkpoints") + loras = ["None"] + folder_paths.get_filename_list("loras") + return { + "required": { + "ckpt_name": (checkpoints,), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), + + "lora_name": (loras,), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + + "resolution": (resolution_strings, {"default": "512 x 512"}), + "empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default":"", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default":"", "placeholder": "Negative", "multiline": True}), + "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) + }, + "optional": { + "optional_lora_stack": ("LORA_STACK",), + "optional_controlnet_stack": ("CONTROL_NET_STACK",), + "a1111_prompt_style": ("BOOLEAN", {"default": a1111_prompt_style_default}), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "a1111loader" + CATEGORY = "EasyUse/Loaders" + + def a1111loader(self, ckpt_name, vae_name, clip_skip, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, negative, batch_size, optional_lora_stack=None, optional_controlnet_stack=None, a1111_prompt_style=False, prompt=None, + my_unique_id=None): + + return super().adv_pipeloader(ckpt_name, 'Default', vae_name, clip_skip, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, 'mean', 'A1111', + negative,'mean','A1111', + batch_size, None, None, None, optional_lora_stack=optional_lora_stack, optional_controlnet_stack=optional_controlnet_stack,a1111_prompt_style=a1111_prompt_style, prompt=prompt, + my_unique_id=my_unique_id + ) + +# Comfy简易加载器 +class comfyLoader(fullLoader): + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), + + "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + + "resolution": (resolution_strings, {"default": "512 x 512"}), + "empty_latent_width": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 512, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) + }, + "optional": {"optional_lora_stack": ("LORA_STACK",), "optional_controlnet_stack": ("CONTROL_NET_STACK",),}, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "comfyloader" + CATEGORY = "EasyUse/Loaders" + + def comfyloader(self, ckpt_name, vae_name, clip_skip, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, negative, batch_size, optional_lora_stack=None, optional_controlnet_stack=None, prompt=None, + my_unique_id=None): + return super().adv_pipeloader(ckpt_name, 'Default', vae_name, clip_skip, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, 'none', 'comfy', + negative, 'none', 'comfy', + batch_size, None, None, None, optional_lora_stack=optional_lora_stack, optional_controlnet_stack=optional_controlnet_stack, a1111_prompt_style=False, prompt=prompt, + my_unique_id=my_unique_id + ) + +# hydit简易加载器 +class hunyuanDiTLoader(fullLoader): + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + + "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + + "resolution": (resolution_strings, {"default": "1024 x 1024"}), + "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), + }, + "optional": {"optional_lora_stack": ("LORA_STACK",), "optional_controlnet_stack": ("CONTROL_NET_STACK",),}, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "hyditloader" + CATEGORY = "EasyUse/Loaders" + + def hyditloader(self, ckpt_name, vae_name, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, negative, batch_size, optional_lora_stack=None, optional_controlnet_stack=None, prompt=None, + my_unique_id=None): + + return super().adv_pipeloader(ckpt_name, 'Default', vae_name, 0, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, 'none', 'comfy', + negative, 'none', 'comfy', + batch_size, None, None, None, optional_lora_stack=optional_lora_stack, optional_controlnet_stack=optional_controlnet_stack, a1111_prompt_style=False, prompt=prompt, + my_unique_id=my_unique_id + ) + +# stable Cascade +class cascadeLoader: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + + return {"required": { + "stage_c": (folder_paths.get_filename_list("unet") + folder_paths.get_filename_list("checkpoints"),), + "stage_b": (folder_paths.get_filename_list("unet") + folder_paths.get_filename_list("checkpoints"),), + "stage_a": (["Baked VAE"]+folder_paths.get_filename_list("vae"),), + "clip_name": (["None"] + folder_paths.get_filename_list("clip"),), + + "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + + "resolution": (resolution_strings, {"default": "1024 x 1024"}), + "empty_latent_width": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 1024, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "compression": ("INT", {"default": 42, "min": 32, "max": 64, "step": 1}), + + "positive": ("STRING", {"default":"", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default":"", "placeholder": "Negative", "multiline": True}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), + }, + "optional": {"optional_lora_stack": ("LORA_STACK",), }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "LATENT", "VAE") + RETURN_NAMES = ("pipe", "model_c", "latent_c", "vae") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def is_ckpt(self, name): + is_ckpt = False + path = folder_paths.get_full_path("checkpoints", name) + if path is not None: + is_ckpt = True + return is_ckpt + + def adv_pipeloader(self, stage_c, stage_b, stage_a, clip_name, lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, compression, + positive, negative, batch_size, optional_lora_stack=None,prompt=None, + my_unique_id=None): + + vae: VAE | None = None + model_c: ModelPatcher | None = None + model_b: ModelPatcher | None = None + clip: CLIP | None = None + can_load_lora = True + pipe_lora_stack = [] + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + # Create Empty Latent + samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size, compression) + + if self.is_ckpt(stage_c): + model_c, clip, vae_c, clip_vision = easyCache.load_checkpoint(stage_c) + else: + model_c = easyCache.load_unet(stage_c) + vae_c = None + if self.is_ckpt(stage_b): + model_b, clip, vae_b, clip_vision = easyCache.load_checkpoint(stage_b) + else: + model_b = easyCache.load_unet(stage_b) + vae_b = None + + if optional_lora_stack is not None and can_load_lora: + for lora in optional_lora_stack: + lora = {"lora_name": lora[0], "model": model_c, "clip": clip, "model_strength": lora[1], "clip_strength": lora[2]} + model_c, clip = easyCache.load_lora(lora) + lora['model'] = model_c + lora['clip'] = clip + pipe_lora_stack.append(lora) + + if lora_name != "None" and can_load_lora: + lora = {"lora_name": lora_name, "model": model_c, "clip": clip, "model_strength": lora_model_strength, + "clip_strength": lora_clip_strength} + model_c, clip = easyCache.load_lora(lora) + pipe_lora_stack.append(lora) + + model = (model_c, model_b) + # Load clip + if clip_name != 'None': + clip = easyCache.load_clip(clip_name, "stable_cascade") + # Load vae + if stage_a not in ["Baked VAE", "Baked-VAE"]: + vae_b = easyCache.load_vae(stage_a) + + vae = (vae_c, vae_b) + # 判断是否连接 styles selector + is_positive_linked_styles_selector = is_linked_styles_selector(prompt, my_unique_id, 'positive') + is_negative_linked_styles_selector = is_linked_styles_selector(prompt, my_unique_id, 'negative') + + positive_seed = find_wildcards_seed(my_unique_id, positive, prompt) + # Translate cn to en + if has_chinese(positive): + positive = zh_to_en([positive])[0] + model_c, clip, positive, positive_decode, show_positive_prompt, pipe_lora_stack = process_with_loras(positive, + model_c, clip, + "positive", + positive_seed, + can_load_lora, + pipe_lora_stack, + easyCache) + positive_wildcard_prompt = positive_decode if show_positive_prompt or is_positive_linked_styles_selector else "" + negative_seed = find_wildcards_seed(my_unique_id, negative, prompt) + # Translate cn to en + if has_chinese(negative): + negative = zh_to_en([negative])[0] + model_c, clip, negative, negative_decode, show_negative_prompt, pipe_lora_stack = process_with_loras(negative, + model_c, clip, + "negative", + negative_seed, + can_load_lora, + pipe_lora_stack, + easyCache) + negative_wildcard_prompt = negative_decode if show_negative_prompt or is_negative_linked_styles_selector else "" + + tokens = clip.tokenize(positive) + cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) + positive_embeddings_final = [[cond, {"pooled_output": pooled}]] + + tokens = clip.tokenize(negative) + cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) + negative_embeddings_final = [[cond, {"pooled_output": pooled}]] + + image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) + + pipe = { + "model": model, + "positive": positive_embeddings_final, + "negative": negative_embeddings_final, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": 0, + + "loader_settings": { + "vae_name": stage_a, + "lora_name": lora_name, + "lora_model_strength": lora_model_strength, + "lora_clip_strength": lora_clip_strength, + "lora_stack": pipe_lora_stack, + + "positive": positive, + "positive_token_normalization": 'none', + "positive_weight_interpretation": 'comfy', + "negative": negative, + "negative_token_normalization": 'none', + "negative_weight_interpretation": 'comfy', + "resolution": resolution, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + "compression": compression + } + } + + return {"ui": {"positive": positive_wildcard_prompt, "negative": negative_wildcard_prompt}, + "result": (pipe, model_c, model_b, vae)} + +# Zero123简易加载器 (3D) +try: + from comfy_extras.nodes_stable3d import camera_embeddings +except FileNotFoundError: + log_node_error("EasyUse[zero123Loader]", "请更新ComfyUI到最新版本") + +class zero123Loader: + + @classmethod + def INPUT_TYPES(cls): + def get_file_list(filenames): + return [file for file in filenames if file != "put_models_here.txt" and "zero123" in file.lower()] + + return {"required": { + "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + + "init_image": ("IMAGE",), + "empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), + + "elevation": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}), + "azimuth": ("FLOAT", {"default": 0.0, "min": -180.0, "max": 180.0}), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, elevation, azimuth, prompt=None, my_unique_id=None): + model: ModelPatcher | None = None + vae: VAE | None = None + clip: CLIP | None = None + clip_vision = None + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) + + output = clip_vision.encode_image(init_image) + pooled = output.image_embeds.unsqueeze(0) + pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, -1) + encode_pixels = pixels[:, :, :, :3] + t = vae.encode(encode_pixels) + cam_embeds = camera_embeddings(elevation, azimuth) + cond = torch.cat([pooled, cam_embeds.repeat((pooled.shape[0], 1, 1))], dim=-1) + + positive = [[cond, {"concat_latent_image": t}]] + negative = [[torch.zeros_like(pooled), {"concat_latent_image": torch.zeros_like(t)}]] + latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]) + samples = {"samples": latent} + + image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) + + pipe = {"model": model, + "positive": positive, + "negative": negative, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": 0, + + "loader_settings": {"ckpt_name": ckpt_name, + "vae_name": vae_name, + + "positive": positive, + "negative": negative, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + "seed": 0, + } + } + + return (pipe, model, vae) + +# SV3D加载器 +class sv3DLoader(EasingBase): + + def __init__(self): + super().__init__() + + @classmethod + def INPUT_TYPES(cls): + def get_file_list(filenames): + return [file for file in filenames if file != "put_models_here.txt" and "sv3d" in file] + + return {"required": { + "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + + "init_image": ("IMAGE",), + "empty_latent_width": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 576, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + + "batch_size": ("INT", {"default": 21, "min": 1, "max": 4096}), + "interp_easing": (["linear", "ease_in", "ease_out", "ease_in_out"], {"default": "linear"}), + "easing_mode": (["azimuth", "elevation", "custom"], {"default": "azimuth"}), + }, + "optional": {"scheduler": ("STRING", {"default": "", "multiline": True})}, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "STRING") + RETURN_NAMES = ("pipe", "model", "interp_log") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def adv_pipeloader(self, ckpt_name, vae_name, init_image, empty_latent_width, empty_latent_height, batch_size, interp_easing, easing_mode, scheduler='',prompt=None, my_unique_id=None): + model: ModelPatcher | None = None + vae: VAE | None = None + clip: CLIP | None = None + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) + + output = clip_vision.encode_image(init_image) + pooled = output.image_embeds.unsqueeze(0) + pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, + -1) + encode_pixels = pixels[:, :, :, :3] + t = vae.encode(encode_pixels) + + azimuth_points = [] + elevation_points = [] + if easing_mode == 'azimuth': + azimuth_points = [(0, 0), (batch_size-1, 360)] + elevation_points = [(0, 0)] * batch_size + elif easing_mode == 'elevation': + azimuth_points = [(0, 0)] * batch_size + elevation_points = [(0, -90), (batch_size-1, 90)] + else: + schedulers = scheduler.rstrip('\n') + for line in schedulers.split('\n'): + frame_str, point_str = line.split(':') + point_str = point_str.strip()[1:-1] + point = point_str.split(',') + azimuth_point = point[0] + elevation_point = point[1] if point[1] else 0.0 + frame = int(frame_str.strip()) + azimuth = float(azimuth_point) + azimuth_points.append((frame, azimuth)) + elevation_val = float(elevation_point) + elevation_points.append((frame, elevation_val)) + azimuth_points.sort(key=lambda x: x[0]) + elevation_points.sort(key=lambda x: x[0]) + + #interpolation + next_point = 1 + next_elevation_point = 1 + elevations = [] + azimuths = [] + # For azimuth interpolation + for i in range(batch_size): + # Find the interpolated azimuth for the current frame + while next_point < len(azimuth_points) and i >= azimuth_points[next_point][0]: + next_point += 1 + if next_point == len(azimuth_points): + next_point -= 1 + prev_point = max(next_point - 1, 0) + + if azimuth_points[next_point][0] != azimuth_points[prev_point][0]: + timing = (i - azimuth_points[prev_point][0]) / ( + azimuth_points[next_point][0] - azimuth_points[prev_point][0]) + interpolated_azimuth = self.ease(azimuth_points[prev_point][1], azimuth_points[next_point][1], self.easing(timing, interp_easing)) + else: + interpolated_azimuth = azimuth_points[prev_point][1] + + # Interpolate the elevation + next_elevation_point = 1 + while next_elevation_point < len(elevation_points) and i >= elevation_points[next_elevation_point][0]: + next_elevation_point += 1 + if next_elevation_point == len(elevation_points): + next_elevation_point -= 1 + prev_elevation_point = max(next_elevation_point - 1, 0) + + if elevation_points[next_elevation_point][0] != elevation_points[prev_elevation_point][0]: + timing = (i - elevation_points[prev_elevation_point][0]) / ( + elevation_points[next_elevation_point][0] - elevation_points[prev_elevation_point][0]) + interpolated_elevation = self.ease(elevation_points[prev_point][1], elevation_points[next_point][1], self.easing(timing, interp_easing)) + else: + interpolated_elevation = elevation_points[prev_elevation_point][1] + + azimuths.append(interpolated_azimuth) + elevations.append(interpolated_elevation) + + log_node_info("easy sv3dLoader", "azimuths:" + str(azimuths)) + log_node_info("easy sv3dLoader", "elevations:" + str(elevations)) + + log = 'azimuths:' + str(azimuths) + '\n\n' + "elevations:" + str(elevations) + # Structure the final output + positive = [[pooled, {"concat_latent_image": t, "elevation": elevations, "azimuth": azimuths}]] + negative = [[torch.zeros_like(pooled), + {"concat_latent_image": torch.zeros_like(t), "elevation": elevations, "azimuth": azimuths}]] + + latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8]) + samples = {"samples": latent} + + image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) + + + pipe = {"model": model, + "positive": positive, + "negative": negative, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": 0, + + "loader_settings": {"ckpt_name": ckpt_name, + "vae_name": vae_name, + + "positive": positive, + "negative": negative, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + "seed": 0, + } + } + + return (pipe, model, log) + +#svd加载器 +class svdLoader: + + @classmethod + def INPUT_TYPES(cls): + def get_file_list(filenames): + return [file for file in filenames if file != "put_models_here.txt" and "svd" in file.lower()] + + return {"required": { + "ckpt_name": (get_file_list(folder_paths.get_filename_list("checkpoints")),), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + "clip_name": (["None"] + folder_paths.get_filename_list("clip"),), + + "init_image": ("IMAGE",), + "resolution": (resolution_strings, {"default": "1024 x 576"}), + "empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + + "video_frames": ("INT", {"default": 14, "min": 1, "max": 4096}), + "motion_bucket_id": ("INT", {"default": 127, "min": 1, "max": 1023}), + "fps": ("INT", {"default": 6, "min": 1, "max": 1024}), + "augmentation_level": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01}) + }, + "optional": { + "optional_positive": ("STRING", {"default": "", "multiline": True}), + "optional_negative": ("STRING", {"default": "", "multiline": True}), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def adv_pipeloader(self, ckpt_name, vae_name, clip_name, init_image, resolution, empty_latent_width, empty_latent_height, video_frames, motion_bucket_id, fps, augmentation_level, optional_positive=None, optional_negative=None, prompt=None, my_unique_id=None): + model: ModelPatcher | None = None + vae: VAE | None = None + clip: CLIP | None = None + clip_vision = None + + # resolution + if resolution != "自定义 x 自定义": + try: + width, height = map(int, resolution.split(' x ')) + empty_latent_width = width + empty_latent_height = height + except ValueError: + raise ValueError("Invalid base_resolution format.") + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + model, clip, vae, clip_vision = easyCache.load_checkpoint(ckpt_name, "Default", True) + + output = clip_vision.encode_image(init_image) + pooled = output.image_embeds.unsqueeze(0) + pixels = comfy.utils.common_upscale(init_image.movedim(-1, 1), empty_latent_width, empty_latent_height, "bilinear", "center").movedim(1, -1) + encode_pixels = pixels[:, :, :, :3] + if augmentation_level > 0: + encode_pixels += torch.randn_like(pixels) * augmentation_level + t = vae.encode(encode_pixels) + positive = [[pooled, + {"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level, + "concat_latent_image": t}]] + negative = [[torch.zeros_like(pooled), + {"motion_bucket_id": motion_bucket_id, "fps": fps, "augmentation_level": augmentation_level, + "concat_latent_image": torch.zeros_like(t)}]] + if optional_positive is not None and optional_positive != '': + if clip_name == 'None': + raise Exception("You need choose a open_clip model when positive is not empty") + clip = easyCache.load_clip(clip_name) + if has_chinese(optional_positive): + optional_positive = zh_to_en([optional_positive])[0] + positive_embeddings_final, = CLIPTextEncode().encode(clip, optional_positive) + positive, = ConditioningConcat().concat(positive, positive_embeddings_final) + if optional_negative is not None and optional_negative != '': + if clip_name == 'None': + raise Exception("You need choose a open_clip model when negative is not empty") + if has_chinese(optional_negative): + optional_positive = zh_to_en([optional_negative])[0] + negative_embeddings_final, = CLIPTextEncode().encode(clip, optional_negative) + negative, = ConditioningConcat().concat(negative, negative_embeddings_final) + + latent = torch.zeros([video_frames, 4, empty_latent_height // 8, empty_latent_width // 8]) + samples = {"samples": latent} + + image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) + + pipe = {"model": model, + "positive": positive, + "negative": negative, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": 0, + + "loader_settings": {"ckpt_name": ckpt_name, + "vae_name": vae_name, + + "positive": positive, + "negative": negative, + "resolution": resolution, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": 1, + "seed": 0, + } + } + + return (pipe, model, vae) + +#dynamiCrafter加载器 +from .dynamiCrafter import DynamiCrafter +class dynamiCrafterLoader(DynamiCrafter): + + def __init__(self): + super().__init__() + + @classmethod + def INPUT_TYPES(cls): + + return {"required": { + "model_name": (list(DYNAMICRAFTER_MODELS.keys()),), + "clip_skip": ("INT", {"default": -2, "min": -24, "max": 0, "step": 1}), + + "init_image": ("IMAGE",), + "resolution": (resolution_strings, {"default": "512 x 512"}), + "empty_latent_width": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 256, "min": 16, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "multiline": True}), + "negative": ("STRING", {"default": "", "multiline": True}), + + "use_interpolate": ("BOOLEAN", {"default": False}), + "fps": ("INT", {"default": 15, "min": 1, "max": 30, "step": 1},), + "frames": ("INT", {"default": 16}), + "scale_latents": ("BOOLEAN", {"default": False}) + }, + "optional": { + "optional_vae": ("VAE",), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def get_clip_file(self, node_name): + clip_list = folder_paths.get_filename_list("clip") + pattern = 'sd2-1-open-clip|model.(safetensors|bin)$' + clip_files = [e for e in clip_list if re.search(pattern, e, re.IGNORECASE)] + + clip_name = clip_files[0] if len(clip_files)>0 else None + clip_file = folder_paths.get_full_path("clip", clip_name) if clip_name else None + if clip_name is not None: + log_node_info(node_name, f"Using {clip_name}") + + return clip_file, clip_name + + def get_clipvision_file(self, node_name): + clipvision_list = folder_paths.get_filename_list("clip_vision") + pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model|open_clip_pytorch_model.(bin|safetensors))' + clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)] + + clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None + clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None + if clipvision_name is not None: + log_node_info(node_name, f"Using {clipvision_name}") + + return clipvision_file, clipvision_name + + def get_vae_file(self, node_name): + vae_list = folder_paths.get_filename_list("vae") + pattern = 'vae-ft-mse-840000-ema-pruned.(pt|bin|safetensors)$' + vae_files = [e for e in vae_list if re.search(pattern, e, re.IGNORECASE)] + + vae_name = vae_files[0] if len(vae_files)>0 else None + vae_file = folder_paths.get_full_path("vae", vae_name) if vae_name else None + if vae_name is not None: + log_node_info(node_name, f"Using {vae_name}") + + return vae_file, vae_name + + def adv_pipeloader(self, model_name, clip_skip, init_image, resolution, empty_latent_width, empty_latent_height, positive, negative, use_interpolate, fps, frames, scale_latents, optional_vae=None, prompt=None, my_unique_id=None): + positive_embeddings_final, negative_embeddings_final = None, None + # resolution + if resolution != "自定义 x 自定义": + try: + width, height = map(int, resolution.split(' x ')) + empty_latent_width = width + empty_latent_height = height + except ValueError: + raise ValueError("Invalid base_resolution format.") + + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + models_0 = list(DYNAMICRAFTER_MODELS.keys())[0] + + if optional_vae: + vae = optional_vae + vae_name = None + else: + vae_file, vae_name = self.get_vae_file("easy dynamiCrafterLoader") + if vae_file is None: + vae_name = "vae-ft-mse-840000-ema-pruned.safetensors" + get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['vae_url'], os.path.join(folder_paths.models_dir, "vae"), + vae_name) + vae = easyCache.load_vae(vae_name) + + clip_file, clip_name = self.get_clip_file("easy dynamiCrafterLoader") + if clip_file is None: + clip_name = 'sd2-1-open-clip.safetensors' + get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['clip_url'], os.path.join(folder_paths.models_dir, "clip"), + clip_name) + + clip = easyCache.load_clip(clip_name) + # load clip vision + clip_vision_file, clip_vision_name = self.get_clipvision_file("easy dynamiCrafterLoader") + if clip_vision_file is None: + clip_vision_name = 'CLIP-ViT-H-14-laion2B-s32B-b79K.safetensors' + clip_vision_file = get_local_filepath(DYNAMICRAFTER_MODELS[models_0]['clip_vision_url'], os.path.join(folder_paths.models_dir, "clip_vision"), + clip_vision_name) + clip_vision = load_clip_vision(clip_vision_file) + # load unet model + model_path = get_local_filepath(DYNAMICRAFTER_MODELS[model_name]['model_url'], DYNAMICRAFTER_DIR) + model_patcher, image_proj_model = self.load_dynamicrafter(model_path) + + # apply + model, empty_latent, image_latent = self.process_image_conditioning(model_patcher, clip_vision, vae, image_proj_model, init_image, use_interpolate, fps, frames, scale_latents) + + clipped = clip.clone() + if clip_skip != 0: + clipped.clip_layer(clip_skip) + + if positive is not None and positive != '': + if has_chinese(positive): + positive = zh_to_en([positive])[0] + positive_embeddings_final, = CLIPTextEncode().encode(clipped, positive) + if negative is not None and negative != '': + if has_chinese(negative): + negative = zh_to_en([negative])[0] + negative_embeddings_final, = CLIPTextEncode().encode(clipped, negative) + + image = easySampler.pil2tensor(Image.new('RGB', (1, 1), (0, 0, 0))) + + pipe = {"model": model, + "positive": positive_embeddings_final, + "negative": negative_embeddings_final, + "vae": vae, + "clip": clip, + "clip_vision": clip_vision, + + "samples": empty_latent, + "images": image, + "seed": 0, + + "loader_settings": {"ckpt_name": model_name, + "vae_name": vae_name, + + "positive": positive, + "negative": negative, + "resolution": resolution, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": 1, + "seed": 0, + } + } + + return (pipe, model, vae) + +# kolors Loader +from .kolors.text_encode import chatglm3_adv_text_encode +class kolorsLoader: + + @classmethod + def INPUT_TYPES(cls): + return { + "required":{ + "unet_name": (folder_paths.get_filename_list("unet"),), + "vae_name": (folder_paths.get_filename_list("vae"),), + "chatglm3_name": (folder_paths.get_filename_list("llm"),), + "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "resolution": (resolution_strings, {"default": "1024 x 576"}), + "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), + }, + "optional": { + "model_override": ("MODEL",), + "vae_override": ("VAE",), + "optional_lora_stack": ("LORA_STACK",), + "auto_clean_gpu": ("BOOLEAN", {"default": False}), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "adv_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def adv_pipeloader(self, unet_name, vae_name, chatglm3_name, lora_name, lora_model_strength, lora_clip_strength, resolution, empty_latent_width, empty_latent_height, positive, negative, batch_size, model_override=None, optional_lora_stack=None, vae_override=None, auto_clean_gpu=False, prompt=None, my_unique_id=None): + # load unet + if model_override: + model = model_override + else: + model = easyCache.load_kolors_unet(unet_name) + # load vae + if vae_override: + vae = vae_override + else: + vae = easyCache.load_vae(vae_name) + # load chatglm3 + chatglm3_model = easyCache.load_chatglm3(chatglm3_name) + # load lora + lora_stack = [] + if optional_lora_stack is not None: + for lora in optional_lora_stack: + lora = {"lora_name": lora[0], "model": model, "clip": None, "model_strength": lora[1], + "clip_strength": lora[2]} + model, _ = easyCache.load_lora(lora) + lora['model'] = model + lora['clip'] = None + lora_stack.append(lora) + + if lora_name != "None": + lora = {"lora_name": lora_name, "model": model, "clip": None, "model_strength": lora_model_strength, + "clip_strength": lora_clip_strength} + model, _ = easyCache.load_lora(lora) + lora_stack.append(lora) + + + # text encode + log_node_warn("Positive encoding...") + positive_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, positive, auto_clean_gpu) + log_node_warn("Negative encoding...") + negative_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, negative, auto_clean_gpu) + + # empty latent + samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size) + + pipe = { + "model": model, + "chatglm3_model": chatglm3_model, + "positive": positive_embeddings_final, + "negative": negative_embeddings_final, + "vae": vae, + "clip": None, + + "samples": samples, + "images": None, + + "loader_settings": { + "unet_name": unet_name, + "vae_name": vae_name, + "chatglm3_name": chatglm3_name, + + "lora_name": lora_name, + "lora_model_strength": lora_model_strength, + "lora_clip_strength": lora_clip_strength, + + "positive": positive, + "negative": negative, + "resolution": resolution, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + "auto_clean_gpu": auto_clean_gpu, + } + } + + return {"ui": {}, + "result": (pipe, model, vae, chatglm3_model, positive_embeddings_final, negative_embeddings_final, samples)} + + + return (chatglm3_model, None, None) + +# Flux Loader +class fluxLoader(fullLoader): + @classmethod + def INPUT_TYPES(cls): + checkpoints = folder_paths.get_filename_list("checkpoints") + loras = ["None"] + folder_paths.get_filename_list("loras") + return { + "required": { + "ckpt_name": (checkpoints,), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"),), + "lora_name": (loras,), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "lora_clip_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + "resolution": (resolution_strings, {"default": "1024 x 1024"}), + "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), + }, + "optional": { + "model_override": ("MODEL",), + "clip_override": ("CLIP",), + "vae_override": ("VAE",), + "optional_lora_stack": ("LORA_STACK",), + "optional_controlnet_stack": ("CONTROL_NET_STACK",), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "fluxloader" + CATEGORY = "EasyUse/Loaders" + + def fluxloader(self, ckpt_name, vae_name, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, optional_controlnet_stack=None, + a1111_prompt_style=False, prompt=None, + my_unique_id=None): + + if positive == '': + positive = None + + return super().adv_pipeloader(ckpt_name, 'Default', vae_name, 0, + lora_name, lora_model_strength, lora_clip_strength, + resolution, empty_latent_width, empty_latent_height, + positive, 'none', 'comfy', + None, 'none', 'comfy', + batch_size, model_override, clip_override, vae_override, optional_lora_stack=optional_lora_stack, + optional_controlnet_stack=optional_controlnet_stack, + a1111_prompt_style=a1111_prompt_style, prompt=prompt, + my_unique_id=my_unique_id) + + +# Dit Loader +from .dit.pixArt.config import pixart_conf, pixart_res + +class pixArtLoader: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "ckpt_name": (folder_paths.get_filename_list("checkpoints"),), + "model_name":(list(pixart_conf.keys()),), + "vae_name": (folder_paths.get_filename_list("vae"),), + "t5_type": (['sd3'],), + "clip_name": (folder_paths.get_filename_list("clip"),), + "padding": ("INT", {"default": 1, "min": 1, "max": 300}), + "t5_name": (folder_paths.get_filename_list("t5"),), + "device": (["auto", "cpu", "gpu"], {"default": "cpu"}), + "dtype": (["default", "auto (comfy)", "FP32", "FP16", "BF16"],), + + "lora_name": (["None"] + folder_paths.get_filename_list("loras"),), + "lora_model_strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}), + + "ratio": (["custom"] + list(pixart_res["PixArtMS_XL_2"].keys()), {"default":"1.00"}), + "empty_latent_width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), + + "batch_size": ("INT", {"default": 1, "min": 1, "max": 64}), + }, + "optional":{ + "optional_lora_stack": ("LORA_STACK",), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + FUNCTION = "pixart_pipeloader" + CATEGORY = "EasyUse/Loaders" + + def pixart_pipeloader(self, ckpt_name, model_name, vae_name, t5_type, clip_name, padding, t5_name, device, dtype, lora_name, lora_model_strength, ratio, empty_latent_width, empty_latent_height, positive, negative, batch_size, optional_lora_stack=None, prompt=None, my_unique_id=None): + # Clean models from loaded_objects + easyCache.update_loaded_objects(prompt) + + # load checkpoint + model = easyCache.load_dit_ckpt(ckpt_name=ckpt_name, model_name=model_name, pixart_conf=pixart_conf, + model_type='PixArt') + # load vae + vae = easyCache.load_vae(vae_name) + + # load t5 + if t5_type == 'sd3': + clip = easyCache.load_clip(clip_name=clip_name,type='sd3') + clip = easyCache.load_t5_from_sd3_clip(sd3_clip=clip, padding=padding) + lora_stack = None + if optional_lora_stack is not None: + for lora in optional_lora_stack: + lora = {"lora_name": lora[0], "model": model, "clip": clip, "model_strength": lora[1], + "clip_strength": lora[2]} + model, _ = easyCache.load_lora(lora, type='PixArt') + lora['model'] = model + lora['clip'] = clip + lora_stack.append(lora) + + if lora_name != "None": + lora = {"lora_name": lora_name, "model": model, "clip": clip, "model_strength": lora_model_strength, + "clip_strength": 1} + model, _ = easyCache.load_lora(lora, type='PixArt') + lora_stack.append(lora) + + positive_embeddings_final, = CLIPTextEncode().encode(clip, positive) + negative_embeddings_final, = CLIPTextEncode().encode(clip, negative) + else: + # todo t5v11 + positive_embeddings_final, negative_embeddings_final = None, None + clip = None + pass + + # Create Empty Latent + if ratio != 'custom': + if model_name in ['ControlPixArtMSHalf','PixArtMS_Sigma_XL_2_900M']: + res_name = 'PixArtMS_XL_2' + elif model_name in ['ControlPixArtHalf']: + res_name = 'PixArt_XL_2' + else: + res_name = model_name + width, height = pixart_res[res_name][ratio] + empty_latent_width = width + empty_latent_height = height + + latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8], device=sampler.device) + samples = {"samples": latent} + + log_node_warn("加载完毕...") + pipe = { + "model": model, + "positive": positive_embeddings_final, + "negative": negative_embeddings_final, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": None, + + "loader_settings": { + "ckpt_name": ckpt_name, + "clip_name": clip_name, + "vae_name": vae_name, + "t5_name": t5_name, + + "positive": positive, + "negative": negative, + "ratio": ratio, + "empty_latent_width": empty_latent_width, + "empty_latent_height": empty_latent_height, + "batch_size": batch_size, + } + } + + return {"ui": {}, + "result": (pipe, model, vae, clip, positive_embeddings_final, negative_embeddings_final, samples)} + + +# Mochi加载器 +class mochiLoader(fullLoader): + @classmethod + def INPUT_TYPES(cls): + checkpoints = folder_paths.get_filename_list("checkpoints") + return { + "required": { + "ckpt_name": (checkpoints,), + "vae_name": (["Baked VAE"] + folder_paths.get_filename_list("vae"), {"default": "mochi_vae.safetensors"}), + + "positive": ("STRING", {"default":"", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default":"", "placeholder": "Negative", "multiline": True}), + + "resolution": (resolution_strings, {"default": "width x height (custom)"}), + "empty_latent_width": ("INT", {"default": 848, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "empty_latent_height": ("INT", {"default": 480, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "length": ("INT", {"default": 25, "min": 7, "max": MAX_RESOLUTION, "step": 6}), + "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096, "tooltip": "The number of latent images in the batch."}) + }, + "optional": { + "model_override": ("MODEL",), "clip_override": ("CLIP",), "vae_override": ("VAE",), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "VAE") + RETURN_NAMES = ("pipe", "model", "vae") + + FUNCTION = "mochiLoader" + CATEGORY = "EasyUse/Loaders" + + def mochiLoader(self, ckpt_name, vae_name, + positive, negative, + resolution, empty_latent_width, empty_latent_height, + length, batch_size, model_override=None, clip_override=None, vae_override=None, optional_lora_stack=None, optional_controlnet_stack=None, a1111_prompt_style=False, prompt=None, + my_unique_id=None): + + return super().adv_pipeloader(ckpt_name, 'Default', vae_name, 0, + "None", 1.0, 1.0, + resolution, empty_latent_width, empty_latent_height, + positive, 'none', 'comfy', + negative,'none','comfy', + batch_size, model_override, clip_override, vae_override, a1111_prompt_style=False, video_length=length, prompt=prompt, + my_unique_id=my_unique_id + ) +# lora +class loraStack: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + max_lora_num = 10 + inputs = { + "required": { + "toggle": ("BOOLEAN", {"label_on": "enabled", "label_off": "disabled"}), + "mode": (["simple", "advanced"],), + "num_loras": ("INT", {"default": 1, "min": 1, "max": max_lora_num}), + }, + "optional": { + "optional_lora_stack": ("LORA_STACK",), + }, + } + + for i in range(1, max_lora_num+1): + inputs["optional"][f"lora_{i}_name"] = ( + ["None"] + folder_paths.get_filename_list("loras"), {"default": "None"}) + inputs["optional"][f"lora_{i}_strength"] = ( + "FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) + inputs["optional"][f"lora_{i}_model_strength"] = ( + "FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) + inputs["optional"][f"lora_{i}_clip_strength"] = ( + "FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}) + + return inputs + + RETURN_TYPES = ("LORA_STACK",) + RETURN_NAMES = ("lora_stack",) + FUNCTION = "stack" + + CATEGORY = "EasyUse/Loaders" + + def stack(self, toggle, mode, num_loras, optional_lora_stack=None, **kwargs): + if (toggle in [False, None, "False"]) or not kwargs: + return (None,) + + loras = [] + + # Import Stack values + if optional_lora_stack is not None: + loras.extend([l for l in optional_lora_stack if l[0] != "None"]) + + # Import Lora values + for i in range(1, num_loras + 1): + lora_name = kwargs.get(f"lora_{i}_name") + + if not lora_name or lora_name == "None": + continue + + if mode == "simple": + lora_strength = float(kwargs.get(f"lora_{i}_strength")) + loras.append((lora_name, lora_strength, lora_strength)) + elif mode == "advanced": + model_strength = float(kwargs.get(f"lora_{i}_model_strength")) + clip_strength = float(kwargs.get(f"lora_{i}_clip_strength")) + loras.append((lora_name, model_strength, clip_strength)) + return (loras,) + +class controlnetStack: + + + @classmethod + def INPUT_TYPES(s): + max_cn_num = 3 + inputs = { + "required": { + "toggle": ("BOOLEAN", {"label_on": "enabled", "label_off": "disabled"}), + "mode": (["simple", "advanced"],), + "num_controlnet": ("INT", {"default": 1, "min": 1, "max": max_cn_num}), + }, + "optional": { + "optional_controlnet_stack": ("CONTROL_NET_STACK",), + } + } + + for i in range(1, max_cn_num+1): + inputs["optional"][f"controlnet_{i}"] = (["None"] + folder_paths.get_filename_list("controlnet"), {"default": "None"}) + inputs["optional"][f"controlnet_{i}_strength"] = ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01},) + inputs["optional"][f"start_percent_{i}"] = ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001},) + inputs["optional"][f"end_percent_{i}"] = ("FLOAT",{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},) + inputs["optional"][f"scale_soft_weight_{i}"] = ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},) + inputs["optional"][f"image_{i}"] = ("IMAGE",) + return inputs + + RETURN_TYPES = ("CONTROL_NET_STACK",) + RETURN_NAMES = ("controlnet_stack",) + FUNCTION = "stack" + CATEGORY = "EasyUse/Loaders" + + def stack(self, toggle, mode, num_controlnet, optional_controlnet_stack=None, **kwargs): + if (toggle in [False, None, "False"]) or not kwargs: + return (None,) + + controlnets = [] + + # Import Stack values + if optional_controlnet_stack is not None: + controlnets.extend([l for l in optional_controlnet_stack if l[0] != "None"]) + + # Import Controlnet values + for i in range(1, num_controlnet+1): + controlnet_name = kwargs.get(f"controlnet_{i}") + + if not controlnet_name or controlnet_name == "None": + continue + + controlnet_strength = float(kwargs.get(f"controlnet_{i}_strength")) + start_percent = float(kwargs.get(f"start_percent_{i}")) if mode == "advanced" else 0 + end_percent = float(kwargs.get(f"end_percent_{i}")) if mode == "advanced" else 1.0 + scale_soft_weights = float(kwargs.get(f"scale_soft_weight_{i}")) + image = kwargs.get(f"image_{i}") + + controlnets.append((controlnet_name, controlnet_strength, start_percent, end_percent, scale_soft_weights, image, True)) + + return (controlnets,) +# controlnet +class controlnetSimple: + @classmethod + def INPUT_TYPES(s): + + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + }, + "optional": { + "control_net": ("CONTROL_NET",), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + } + } + + RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "positive", "negative") + + FUNCTION = "controlnetApply" + CATEGORY = "EasyUse/Loaders" + + def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, scale_soft_weights=1, union_type=None): + + positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], strength, 0, 1, control_net, scale_soft_weights, mask=None, easyCache=easyCache, model=pipe['model'], vae=pipe['vae']) + + new_pipe = { + "model": pipe['model'], + "positive": positive, + "negative": negative, + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": pipe["samples"], + "images": pipe["images"], + "seed": 0, + + "loader_settings": pipe["loader_settings"] + } + + del pipe + return (new_pipe, positive, negative) + +# controlnetADV +class controlnetAdvanced: + + @classmethod + def INPUT_TYPES(s): + + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + }, + "optional": { + "control_net": ("CONTROL_NET",), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + } + } + + RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "positive", "negative") + + FUNCTION = "controlnetApply" + CATEGORY = "EasyUse/Loaders" + + + def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1): + positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], + strength, start_percent, end_percent, control_net, scale_soft_weights, union_type=None, mask=None, easyCache=easyCache, model=pipe['model'], vae=pipe['vae']) + + new_pipe = { + "model": pipe['model'], + "positive": positive, + "negative": negative, + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": pipe["samples"], + "images": image, + "seed": 0, + + "loader_settings": pipe["loader_settings"] + } + + del pipe + + return (new_pipe, positive, negative) + +# controlnetPlusPlus +class controlnetPlusPlus: + + @classmethod + def INPUT_TYPES(s): + + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + }, + "optional": { + "control_net": ("CONTROL_NET",), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "scale_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + "union_type": (list(union_controlnet_types.keys()),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "positive", "negative") + + FUNCTION = "controlnetApply" + CATEGORY = "EasyUse/Loaders" + + + def controlnetApply(self, pipe, image, control_net_name, control_net=None, strength=1, start_percent=0, end_percent=1, scale_soft_weights=1, union_type=None): + if scale_soft_weights < 1: + if "ScaledSoftControlNetWeights" in ALL_NODE_CLASS_MAPPINGS: + soft_weight_cls = ALL_NODE_CLASS_MAPPINGS['ScaledSoftControlNetWeights'] + (weights, timestep_keyframe) = soft_weight_cls().load_weights(scale_soft_weights, False) + cn_adv_cls = ALL_NODE_CLASS_MAPPINGS['ACN_ControlNet++LoaderSingle'] + if union_type == 'auto': + union_type = 'none' + elif union_type == 'canny/lineart/anime_lineart/mlsd': + union_type = 'canny/lineart/mlsd' + elif union_type == 'repaint': + union_type = 'inpaint/outpaint' + control_net, = cn_adv_cls().load_controlnet_plusplus(control_net_name, union_type) + apply_adv_cls = ALL_NODE_CLASS_MAPPINGS['ACN_AdvancedControlNetApply'] + positive, negative, _ = apply_adv_cls().apply_controlnet(pipe["positive"], pipe["negative"], control_net, image, strength, start_percent, end_percent, timestep_kf=timestep_keyframe,) + else: + raise Exception( + f"[Advanced-ControlNet Not Found] you need to install 'COMFYUI-Advanced-ControlNet'") + else: + positive, negative = easyControlnet().apply(control_net_name, image, pipe["positive"], pipe["negative"], + strength, start_percent, end_percent, control_net, scale_soft_weights, union_type=union_type, mask=None, easyCache=easyCache, model=pipe['model']) + + new_pipe = { + "model": pipe['model'], + "positive": positive, + "negative": negative, + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": pipe["samples"], + "images": pipe["images"], + "seed": 0, + + "loader_settings": pipe["loader_settings"] + } + + del pipe + + return (new_pipe, positive, negative) + +# LLLiteLoader +from .libs.lllite import load_control_net_lllite_patch +class LLLiteLoader: + def __init__(self): + pass + @classmethod + def INPUT_TYPES(s): + def get_file_list(filenames): + return [file for file in filenames if file != "put_models_here.txt" and "lllite" in file] + + return { + "required": { + "model": ("MODEL",), + "model_name": (get_file_list(folder_paths.get_filename_list("controlnet")),), + "cond_image": ("IMAGE",), + "strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "steps": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}), + "end_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.1}), + } + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "load_lllite" + CATEGORY = "EasyUse/Loaders" + + def load_lllite(self, model, model_name, cond_image, strength, steps, start_percent, end_percent): + # cond_image is b,h,w,3, 0-1 + + model_path = os.path.join(folder_paths.get_full_path("controlnet", model_name)) + + model_lllite = model.clone() + patch = load_control_net_lllite_patch(model_path, cond_image, strength, steps, start_percent, end_percent) + if patch is not None: + model_lllite.set_model_attn1_patch(patch) + model_lllite.set_model_attn2_patch(patch) + + return (model_lllite,) + +# ---------------------------------------------------------------加载器 结束----------------------------------------------------------------------# + +#---------------------------------------------------------------Inpaint 开始----------------------------------------------------------------------# +# FooocusInpaint +class applyFooocusInpaint: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "latent": ("LATENT",), + "head": (list(FOOOCUS_INPAINT_HEAD.keys()),), + "patch": (list(FOOOCUS_INPAINT_PATCH.keys()),), + }, + } + + RETURN_TYPES = ("MODEL",) + RETURN_NAMES = ("model",) + CATEGORY = "EasyUse/Inpaint" + FUNCTION = "apply" + + def apply(self, model, latent, head, patch): + from .fooocus import InpaintHead, InpaintWorker + head_file = get_local_filepath(FOOOCUS_INPAINT_HEAD[head]["model_url"], INPAINT_DIR) + inpaint_head_model = InpaintHead() + sd = torch.load(head_file, map_location='cpu') + inpaint_head_model.load_state_dict(sd) + + patch_file = get_local_filepath(FOOOCUS_INPAINT_PATCH[patch]["model_url"], INPAINT_DIR) + inpaint_lora = comfy.utils.load_torch_file(patch_file, safe_load=True) + + patch = (inpaint_head_model, inpaint_lora) + worker = InpaintWorker(node_name="easy kSamplerInpainting") + cloned = model.clone() + + m, = worker.patch(cloned, latent, patch) + return (m,) + +# brushnet +from .brushnet import BrushNet +class applyBrushNet: + + def get_files_with_extension(folder='inpaint', extensions='.safetensors'): + return [file for file in folder_paths.get_filename_list(folder) if file.endswith(extensions)] + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "mask": ("MASK",), + "brushnet": (s.get_files_with_extension(),), + "dtype": (['float16', 'bfloat16', 'float32', 'float64'], ), + "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), + "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + CATEGORY = "EasyUse/Inpaint" + FUNCTION = "apply" + + def apply(self, pipe, image, mask, brushnet, dtype, scale, start_at, end_at): + + model = pipe['model'] + vae = pipe['vae'] + positive = pipe['positive'] + negative = pipe['negative'] + cls = BrushNet() + if brushnet in backend_cache.cache: + log_node_info("easy brushnetApply", f"Using {brushnet} Cached") + _, brushnet_model = backend_cache.cache[brushnet][1] + else: + brushnet_file = os.path.join(folder_paths.get_full_path("inpaint", brushnet)) + brushnet_model, = cls.load_brushnet_model(brushnet_file, dtype) + backend_cache.update_cache(brushnet, 'brushnet', (False, brushnet_model)) + m, positive, negative, latent = cls.brushnet_model_update(model=model, vae=vae, image=image, mask=mask, + brushnet=brushnet_model, positive=positive, + negative=negative, scale=scale, start_at=start_at, + end_at=end_at) + new_pipe = { + **pipe, + "model": m, + "positive": positive, + "negative": negative, + "samples": latent, + } + del pipe + return (new_pipe,) + +# #powerpaint +class applyPowerPaint: + def get_files_with_extension(folder='inpaint', extensions='.safetensors'): + return [file for file in folder_paths.get_filename_list(folder) if file.endswith(extensions)] + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "mask": ("MASK",), + "powerpaint_model": (s.get_files_with_extension(),), + "powerpaint_clip": (s.get_files_with_extension(extensions='.bin'),), + "dtype": (['float16', 'bfloat16', 'float32', 'float64'],), + "fitting": ("FLOAT", {"default": 1.0, "min": 0.3, "max": 1.0}), + "function": (['text guided', 'shape guided', 'object removal', 'context aware', 'image outpainting'],), + "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), + "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), + "save_memory": (['none', 'auto', 'max'],), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + CATEGORY = "EasyUse/Inpaint" + FUNCTION = "apply" + + def apply(self, pipe, image, mask, powerpaint_model, powerpaint_clip, dtype, fitting, function, scale, start_at, end_at, save_memory='none'): + model = pipe['model'] + vae = pipe['vae'] + positive = pipe['positive'] + negative = pipe['negative'] + + cls = BrushNet() + # load powerpaint clip + if powerpaint_clip in backend_cache.cache: + log_node_info("easy powerpaintApply", f"Using {powerpaint_clip} Cached") + _, ppclip = backend_cache.cache[powerpaint_clip][1] + else: + model_url = POWERPAINT_MODELS['base_fp16']['model_url'] + base_clip = get_local_filepath(model_url, os.path.join(folder_paths.models_dir, 'clip')) + ppclip, = cls.load_powerpaint_clip(base_clip, os.path.join(folder_paths.get_full_path("inpaint", powerpaint_clip))) + backend_cache.update_cache(powerpaint_clip, 'ppclip', (False, ppclip)) + # load powerpaint model + if powerpaint_model in backend_cache.cache: + log_node_info("easy powerpaintApply", f"Using {powerpaint_model} Cached") + _, powerpaint = backend_cache.cache[powerpaint_model][1] + else: + powerpaint_file = os.path.join(folder_paths.get_full_path("inpaint", powerpaint_model)) + powerpaint, = cls.load_brushnet_model(powerpaint_file, dtype) + backend_cache.update_cache(powerpaint_model, 'powerpaint', (False, powerpaint)) + m, positive, negative, latent = cls.powerpaint_model_update(model=model, vae=vae, image=image, mask=mask, powerpaint=powerpaint, + clip=ppclip, positive=positive, + negative=negative, fitting=fitting, function=function, + scale=scale, start_at=start_at, end_at=end_at, save_memory=save_memory) + new_pipe = { + **pipe, + "model": m, + "positive": positive, + "negative": negative, + "samples": latent, + } + del pipe + return (new_pipe,) + +from node_helpers import conditioning_set_values +class applyInpaint: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "mask": ("MASK",), + "inpaint_mode": (('normal', 'fooocus_inpaint', 'brushnet_random', 'brushnet_segmentation', 'powerpaint'),), + "encode": (('none', 'vae_encode_inpaint', 'inpaint_model_conditioning', 'different_diffusion'), {"default": "none"}), + "grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}), + "dtype": (['float16', 'bfloat16', 'float32', 'float64'],), + "fitting": ("FLOAT", {"default": 1.0, "min": 0.3, "max": 1.0}), + "function": (['text guided', 'shape guided', 'object removal', 'context aware', 'image outpainting'],), + "scale": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0}), + "start_at": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at": ("INT", {"default": 10000, "min": 0, "max": 10000}), + }, + "optional":{ + "noise_mask": ("BOOLEAN", {"default": True}) + } + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + CATEGORY = "EasyUse/Inpaint" + FUNCTION = "apply" + + def inpaint_model_conditioning(self, pipe, image, vae, mask, grow_mask_by, noise_mask=True): + if grow_mask_by >0: + mask, = GrowMask().expand_mask(mask, grow_mask_by, False) + positive, negative, = pipe['positive'], pipe['negative'] + + pixels = image + x = (pixels.shape[1] // 8) * 8 + y = (pixels.shape[2] // 8) * 8 + mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), + size=(pixels.shape[1], pixels.shape[2]), mode="bilinear") + + orig_pixels = pixels + pixels = orig_pixels.clone() + if pixels.shape[1] != x or pixels.shape[2] != y: + x_offset = (pixels.shape[1] % 8) // 2 + y_offset = (pixels.shape[2] % 8) // 2 + pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] + mask = mask[:, :, x_offset:x + x_offset, y_offset:y + y_offset] + + m = (1.0 - mask.round()).squeeze(1) + for i in range(3): + pixels[:, :, :, i] -= 0.5 + pixels[:, :, :, i] *= m + pixels[:, :, :, i] += 0.5 + concat_latent = vae.encode(pixels) + orig_latent = vae.encode(orig_pixels) + + out_latent = {} + + out_latent["samples"] = orig_latent + if noise_mask: + out_latent["noise_mask"] = mask + + out = [] + for conditioning in [positive, negative]: + c = conditioning_set_values(conditioning, {"concat_latent_image": concat_latent, + "concat_mask": mask}) + out.append(c) + + pipe['positive'] = out[0] + pipe['negative'] = out[1] + pipe['samples'] = out_latent + + return pipe + + def get_brushnet_model(self, type, model): + model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1' + if type == 'brushnet_random': + brush_model = BRUSHNET_MODELS['random_mask'][model_type]['model_url'] + if model_type == 'sdxl': + pattern = 'brushnet.random.mask.sdxl.*.(safetensors|bin)$' + else: + pattern = 'brushnet.random.mask.*.(safetensors|bin)$' + elif type == 'brushnet_segmentation': + brush_model = BRUSHNET_MODELS['segmentation_mask'][model_type]['model_url'] + if model_type == 'sdxl': + pattern = 'brushnet.segmentation.mask.sdxl.*.(safetensors|bin)$' + else: + pattern = 'brushnet.segmentation.mask.*.(safetensors|bin)$' + + + brushfile = [e for e in folder_paths.get_filename_list('inpaint') if re.search(pattern, e, re.IGNORECASE)] + brushname = brushfile[0] if brushfile else None + if not brushname: + from urllib.parse import urlparse + get_local_filepath(brush_model, INPAINT_DIR) + parsed_url = urlparse(brush_model) + brushname = os.path.basename(parsed_url.path) + return brushname + + def get_powerpaint_model(self, model): + model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1' + if model_type == 'sdxl': + raise Exception("Powerpaint not supported for SDXL models") + + powerpaint_model = POWERPAINT_MODELS['v2.1']['model_url'] + powerpaint_clip = POWERPAINT_MODELS['v2.1']['clip_url'] + + from urllib.parse import urlparse + get_local_filepath(powerpaint_model, os.path.join(INPAINT_DIR, 'powerpaint')) + model_parsed_url = urlparse(powerpaint_model) + clip_parsed_url = urlparse(powerpaint_clip) + model_name = os.path.join("powerpaint",os.path.basename(model_parsed_url.path)) + clip_name = os.path.join("powerpaint",os.path.basename(clip_parsed_url.path)) + return model_name, clip_name + + def apply(self, pipe, image, mask, inpaint_mode, encode, grow_mask_by, dtype, fitting, function, scale, start_at, end_at, noise_mask=True): + new_pipe = { + **pipe, + } + del pipe + if inpaint_mode in ['brushnet_random', 'brushnet_segmentation']: + brushnet = self.get_brushnet_model(inpaint_mode, new_pipe['model']) + new_pipe, = applyBrushNet().apply(new_pipe, image, mask, brushnet, dtype, scale, start_at, end_at) + elif inpaint_mode == 'powerpaint': + powerpaint_model, powerpaint_clip = self.get_powerpaint_model(new_pipe['model']) + new_pipe, = applyPowerPaint().apply(new_pipe, image, mask, powerpaint_model, powerpaint_clip, dtype, fitting, function, scale, start_at, end_at) + + vae = new_pipe['vae'] + if encode == 'none': + if inpaint_mode == 'fooocus_inpaint': + model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], + list(FOOOCUS_INPAINT_HEAD.keys())[0], + list(FOOOCUS_INPAINT_PATCH.keys())[0]) + new_pipe['model'] = model + elif encode == 'vae_encode_inpaint': + latent, = VAEEncodeForInpaint().encode(vae, image, mask, grow_mask_by) + new_pipe['samples'] = latent + if inpaint_mode == 'fooocus_inpaint': + model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], + list(FOOOCUS_INPAINT_HEAD.keys())[0], + list(FOOOCUS_INPAINT_PATCH.keys())[0]) + new_pipe['model'] = model + elif encode == 'inpaint_model_conditioning': + if inpaint_mode == 'fooocus_inpaint': + latent, = VAEEncodeForInpaint().encode(vae, image, mask, grow_mask_by) + new_pipe['samples'] = latent + model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], + list(FOOOCUS_INPAINT_HEAD.keys())[0], + list(FOOOCUS_INPAINT_PATCH.keys())[0]) + new_pipe['model'] = model + new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, 0, noise_mask=noise_mask) + else: + new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, grow_mask_by, noise_mask=noise_mask) + elif encode == 'different_diffusion': + if inpaint_mode == 'fooocus_inpaint': + latent, = VAEEncodeForInpaint().encode(vae, image, mask, grow_mask_by) + new_pipe['samples'] = latent + model, = applyFooocusInpaint().apply(new_pipe['model'], new_pipe['samples'], + list(FOOOCUS_INPAINT_HEAD.keys())[0], + list(FOOOCUS_INPAINT_PATCH.keys())[0]) + new_pipe['model'] = model + new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, 0, noise_mask=noise_mask) + else: + new_pipe = self.inpaint_model_conditioning(new_pipe, image, vae, mask, grow_mask_by, noise_mask=noise_mask) + cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion'] + if cls is not None: + model, = cls().apply(new_pipe['model']) + new_pipe['model'] = model + else: + raise Exception("Differential Diffusion not found,please update comfyui") + + return (new_pipe,) +# ---------------------------------------------------------------Inpaint 结束----------------------------------------------------------------------# + +#---------------------------------------------------------------适配器 开始----------------------------------------------------------------------# +class applyLoraStack: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "lora_stack": ("LORA_STACK",), + "model": ("MODEL",), + }, + "optional": { + "optional_clip": ("CLIP",), + } + } + + RETURN_TYPES = ("MODEL", "CLIP") + RETURN_NAMES = ("model", "clip") + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, lora_stack, model, optional_clip=None): + clip = None + if lora_stack is not None and len(lora_stack) > 0: + for lora in lora_stack: + lora = {"lora_name": lora[0], "model": model, "clip": optional_clip, "model_strength": lora[1], + "clip_strength": lora[2]} + model, clip = easyCache.load_lora(lora, model, optional_clip, use_cache=False) + return (model, clip) + +class applyControlnetStack: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "controlnet_stack": ("CONTROL_NET_STACK",), + "pipe": ("PIPE_LINE",), + }, + "optional": { + } + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, controlnet_stack, pipe): + + positive = pipe['positive'] + negative = pipe['negative'] + model = pipe['model'] + vae = pipe['vae'] + + if controlnet_stack is not None and len(controlnet_stack) >0: + for controlnet in controlnet_stack: + positive, negative = easyControlnet().apply(controlnet[0], controlnet[5], positive, negative, controlnet[1], start_percent=controlnet[2], end_percent=controlnet[3], control_net=None, scale_soft_weights=controlnet[4], mask=None, easyCache=easyCache, use_cache=False, model=model, vae=vae) + + new_pipe = { + **pipe, + "positive": positive, + "negetive": negative, + } + del pipe + + return (new_pipe,) + +# 风格对齐 +from .libs.styleAlign import styleAlignBatch, SHARE_NORM_OPTIONS, SHARE_ATTN_OPTIONS +class styleAlignedBatchAlign: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "share_norm": (SHARE_NORM_OPTIONS,), + "share_attn": (SHARE_ATTN_OPTIONS,), + "scale": ("FLOAT", {"default": 1, "min": 0, "max": 1.0, "step": 0.1}), + } + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "align" + CATEGORY = "EasyUse/Adapter" + + def align(self, model, share_norm, share_attn, scale): + return (styleAlignBatch(model, share_norm, share_attn, scale),) + +# 光照对齐 +from .ic_light.__init__ import ICLight, VAEEncodeArgMax +class icLightApply: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "mode": (list(IC_LIGHT_MODELS.keys()),), + "model": ("MODEL",), + "image": ("IMAGE",), + "vae": ("VAE",), + "lighting": (['None', 'Left Light', 'Right Light', 'Top Light', 'Bottom Light', 'Circle Light'],{"default": "None"}), + "source": (['Use Background Image', 'Use Flipped Background Image', 'Left Light', 'Right Light', 'Top Light', 'Bottom Light', 'Ambient'],{"default": "Use Background Image"}), + "remove_bg": ("BOOLEAN", {"default": True}), + }, + } + + RETURN_TYPES = ("MODEL", "IMAGE") + RETURN_NAMES = ("model", "lighting_image") + FUNCTION = "apply" + CATEGORY = "EasyUse/Adapter" + + def batch(self, image1, image2): + if image1.shape[1:] != image2.shape[1:]: + image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", + "center").movedim(1, -1) + s = torch.cat((image1, image2), dim=0) + return s + + def removebg(self, image): + if "easy imageRemBg" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception("Please re-install ComfyUI-Easy-Use") + cls = ALL_NODE_CLASS_MAPPINGS['easy imageRemBg'] + results = cls().remove('RMBG-1.4', image, 'Hide', 'ComfyUI') + if "result" in results: + image, _ = results['result'] + return image + + def apply(self, mode, model, image, vae, lighting, source, remove_bg): + model_type = get_sd_version(model) + if model_type == 'sdxl': + raise Exception("IC Light model is not supported for SDXL now") + + batch_size, height, width, channel = image.shape + if channel == 3: + # remove bg + if mode == 'Foreground' or batch_size == 1: + if remove_bg: + image = self.removebg(image) + else: + mask = torch.full((1, height, width), 1.0, dtype=torch.float32, device="cpu") + image, = JoinImageWithAlpha().join_image_with_alpha(image, mask) + + iclight = ICLight() + if mode == 'Foreground': + lighting_image = iclight.generate_lighting_image(image, lighting) + else: + lighting_image = iclight.generate_source_image(image, source) + if source not in ['Use Background Image', 'Use Flipped Background Image']: + _, height, width, _ = lighting_image.shape + mask = torch.full((1, height, width), 1.0, dtype=torch.float32, device="cpu") + lighting_image, = JoinImageWithAlpha().join_image_with_alpha(lighting_image, mask) + if batch_size < 2: + image = self.batch(image, lighting_image) + else: + original_image = [img.unsqueeze(0) for img in image] + original_image = self.removebg(original_image[0]) + image = self.batch(original_image, lighting_image) + + latent, = VAEEncodeArgMax().encode(vae, image) + key = 'iclight_' + mode + '_' + model_type + model_path = get_local_filepath(IC_LIGHT_MODELS[mode]['sd1']["model_url"], + os.path.join(folder_paths.models_dir, "unet")) + ic_model = None + if key in backend_cache.cache: + log_node_info("easy icLightApply", f"Using icLightModel {mode+'_'+model_type} Cached") + _, ic_model = backend_cache.cache[key][1] + m, _ = iclight.apply(model_path, model, latent, ic_model) + else: + m, ic_model = iclight.apply(model_path, model, latent, ic_model) + backend_cache.update_cache(key, 'iclight', (False, ic_model)) + return (m, lighting_image) + + +def insightface_loader(provider, name='buffalo_l'): + try: + from insightface.app import FaceAnalysis + except ImportError as e: + raise Exception(e) + path = os.path.join(folder_paths.models_dir, "insightface") + model = FaceAnalysis(name=name, root=path, providers=[provider + 'ExecutionProvider', ]) + model.prepare(ctx_id=0, det_size=(640, 640)) + return model + +# Apply Ipadapter +class ipadapter: + + def __init__(self): + self.normal_presets = [ + 'LIGHT - SD1.5 only (low strength)', + 'STANDARD (medium strength)', + 'VIT-G (medium strength)', + 'PLUS (high strength)', + 'PLUS (kolors genernal)', + 'REGULAR - FLUX and SD3.5 only (high strength)', + 'PLUS FACE (portraits)', + 'FULL FACE - SD1.5 only (portraits stronger)', + 'COMPOSITION' + ] + self.faceid_presets = [ + 'FACEID', + 'FACEID PLUS - SD1.5 only', + "FACEID PLUS KOLORS", + 'FACEID PLUS V2', + 'FACEID PORTRAIT (style transfer)', + 'FACEID PORTRAIT UNNORM - SDXL only (strong)' + ] + self.weight_types = ["linear", "ease in", "ease out", 'ease in-out', 'reverse in-out', 'weak input', 'weak output', 'weak middle', 'strong middle', 'style transfer', 'composition', 'strong style transfer', 'style and composition', 'style transfer precise'] + self.presets = self.normal_presets + self.faceid_presets + + + def error(self): + raise Exception(f"[ERROR] To use ipadapterApply, you need to install 'ComfyUI_IPAdapter_plus'") + + def get_clipvision_file(self, preset, node_name): + preset = preset.lower() + clipvision_list = folder_paths.get_filename_list("clip_vision") + if preset.startswith("regular"): + # pattern = 'sigclip.vision.patch14.384' + pattern = 'siglip.so400m.patch14.384' + elif preset.startswith("plus (kolors") or preset.startswith("faceid plus kolors"): + pattern = 'Vit.Large.patch14.336.(bin|safetensors)$' + elif preset.startswith("vit-g"): + pattern = '(ViT.bigG.14.*39B.b160k|ipadapter.*sdxl|sdxl.*model.(bin|safetensors))' + else: + pattern = '(ViT.H.14.*s32B.b79K|ipadapter.*sd15|sd1.?5.*model.(bin|safetensors))' + clipvision_files = [e for e in clipvision_list if re.search(pattern, e, re.IGNORECASE)] + clipvision_name = clipvision_files[0] if len(clipvision_files)>0 else None + clipvision_file = folder_paths.get_full_path("clip_vision", clipvision_name) if clipvision_name else None + # if clipvision_name is not None: + # log_node_info(node_name, f"Using {clipvision_name}") + return clipvision_file, clipvision_name + + def get_ipadapter_file(self, preset, model_type, node_name): + preset = preset.lower() + ipadapter_list = folder_paths.get_filename_list("ipadapter") + is_insightface = False + lora_pattern = None + is_sdxl = model_type == 'sdxl' + is_flux = model_type == 'flux' + + if preset.startswith("light"): + if is_sdxl: + raise Exception("light model is not supported for SDXL") + pattern = 'sd15.light.v11.(safetensors|bin)$' + # if light model v11 is not found, try with the old version + if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]: + pattern = 'sd15.light.(safetensors|bin)$' + elif preset.startswith("standard"): + if is_sdxl: + pattern = 'ip.adapter.sdxl.vit.h.(safetensors|bin)$' + else: + pattern = 'ip.adapter.sd15.(safetensors|bin)$' + elif preset.startswith("vit-g"): + if is_sdxl: + pattern = 'ip.adapter.sdxl.(safetensors|bin)$' + else: + pattern = 'sd15.vit.g.(safetensors|bin)$' + elif preset.startswith("regular"): + if is_flux: + pattern = 'ip.adapter.flux.1.dev.(safetensors|bin)$' + else: + pattern = 'ip.adapter.sd35.(safetensors|bin)$' + elif preset.startswith("plus (high"): + if is_sdxl: + pattern = 'plus.sdxl.vit.h.(safetensors|bin)$' + else: + pattern = 'ip.adapter.plus.sd15.(safetensors|bin)$' + elif preset.startswith("plus (kolors"): + if is_sdxl: + pattern = 'plus.gener(nal|al).(safetensors|bin)$' + else: + raise Exception("kolors model is not supported for SD15") + elif preset.startswith("plus face"): + if is_sdxl: + pattern = 'plus.face.sdxl.vit.h.(safetensors|bin)$' + else: + pattern = 'plus.face.sd15.(safetensors|bin)$' + elif preset.startswith("full"): + if is_sdxl: + raise Exception("full face model is not supported for SDXL") + pattern = 'full.face.sd15.(safetensors|bin)$' + elif preset.startswith("composition"): + if is_sdxl: + pattern = 'plus.composition.sdxl.(safetensors|bin)$' + else: + pattern = 'plus.composition.sd15.(safetensors|bin)$' + elif preset.startswith("faceid portrait ("): + if is_sdxl: + pattern = 'portrait.sdxl.(safetensors|bin)$' + else: + pattern = 'portrait.v11.sd15.(safetensors|bin)$' + # if v11 is not found, try with the old version + if not [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)]: + pattern = 'portrait.sd15.(safetensors|bin)$' + is_insightface = True + elif preset.startswith("faceid portrait unnorm"): + if is_sdxl: + pattern = r'portrait.sdxl.unnorm.(safetensors|bin)$' + else: + raise Exception("portrait unnorm model is not supported for SD1.5") + is_insightface = True + elif preset == "faceid": + if is_sdxl: + pattern = 'faceid.sdxl.(safetensors|bin)$' + lora_pattern = 'faceid.sdxl.lora.safetensors$' + else: + pattern = 'faceid.sd15.(safetensors|bin)$' + lora_pattern = 'faceid.sd15.lora.safetensors$' + is_insightface = True + elif preset.startswith("faceid plus kolors"): + if is_sdxl: + pattern = '(kolors.ip.adapter.faceid.plus|ipa.faceid.plus).(safetensors|bin)$' + else: + raise Exception("faceid plus kolors model is not supported for SD1.5") + is_insightface = True + elif preset.startswith("faceid plus -"): + if is_sdxl: + raise Exception("faceid plus model is not supported for SDXL") + pattern = 'faceid.plus.sd15.(safetensors|bin)$' + lora_pattern = 'faceid.plus.sd15.lora.safetensors$' + is_insightface = True + elif preset.startswith("faceid plus v2"): + if is_sdxl: + pattern = 'faceid.plusv2.sdxl.(safetensors|bin)$' + lora_pattern = 'faceid.plusv2.sdxl.lora.safetensors$' + else: + pattern = 'faceid.plusv2.sd15.(safetensors|bin)$' + lora_pattern = 'faceid.plusv2.sd15.lora.safetensors$' + is_insightface = True + else: + raise Exception(f"invalid type '{preset}'") + + ipadapter_files = [e for e in ipadapter_list if re.search(pattern, e, re.IGNORECASE)] + ipadapter_name = ipadapter_files[0] if len(ipadapter_files)>0 else None + ipadapter_file = folder_paths.get_full_path("ipadapter", ipadapter_name) if ipadapter_name else None + # if ipadapter_name is not None: + # log_node_info(node_name, f"Using {ipadapter_name}") + + return ipadapter_file, ipadapter_name, is_insightface, lora_pattern + + def get_lora_pattern(self, file): + basename = os.path.basename(file) + lora_pattern = None + if re.search(r'faceid.sdxl.(safetensors|bin)$', basename, re.IGNORECASE): + lora_pattern = 'faceid.sdxl.lora.safetensors$' + elif re.search(r'faceid.sd15.(safetensors|bin)$', basename, re.IGNORECASE): + lora_pattern = 'faceid.sd15.lora.safetensors$' + elif re.search(r'faceid.plus.sd15.(safetensors|bin)$', basename, re.IGNORECASE): + lora_pattern = 'faceid.plus.sd15.lora.safetensors$' + elif re.search(r'faceid.plusv2.sdxl.(safetensors|bin)$', basename, re.IGNORECASE): + lora_pattern = 'faceid.plusv2.sdxl.lora.safetensors$' + elif re.search(r'faceid.plusv2.sd15.(safetensors|bin)$', basename, re.IGNORECASE): + lora_pattern = 'faceid.plusv2.sd15.lora.safetensors$' + + return lora_pattern + + def get_lora_file(self, preset, pattern, model_type, model, model_strength, clip_strength, clip=None): + lora_list = folder_paths.get_filename_list("loras") + lora_files = [e for e in lora_list if re.search(pattern, e, re.IGNORECASE)] + lora_name = lora_files[0] if lora_files else None + if lora_name: + return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},) + else: + if "lora_url" in IPADAPTER_MODELS[preset][model_type]: + lora_name = get_local_filepath(IPADAPTER_MODELS[preset][model_type]["lora_url"], os.path.join(folder_paths.models_dir, "loras")) + return easyCache.load_lora({"model": model, "clip": clip, "lora_name": lora_name, "model_strength":model_strength, "clip_strength":clip_strength},) + return (model, clip) + + def ipadapter_model_loader(self, file): + model = comfy.utils.load_torch_file(file, safe_load=False) + + if file.lower().endswith(".safetensors"): + st_model = {"image_proj": {}, "ip_adapter": {}} + for key in model.keys(): + if key.startswith("image_proj."): + st_model["image_proj"][key.replace("image_proj.", "")] = model[key] + elif key.startswith("ip_adapter."): + st_model["ip_adapter"][key.replace("ip_adapter.", "")] = model[key] + model = st_model + del st_model + + model_keys = model.keys() + if "adapter_modules" in model_keys: + model["ip_adapter"] = model["adapter_modules"] + model["faceidplusv2"] = True + del model['adapter_modules'] + + if not "ip_adapter" in model_keys or not model["ip_adapter"]: + raise Exception("invalid IPAdapter model {}".format(file)) + + if 'plusv2' in file.lower(): + model["faceidplusv2"] = True + + if 'unnorm' in file.lower(): + model["portraitunnorm"] = True + + return model + + def load_model(self, model, preset, lora_model_strength, provider="CPU", clip_vision=None, optional_ipadapter=None, cache_mode='none', node_name='easy ipadapterApply'): + pipeline = {"clipvision": {'file': None, 'model': None}, "ipadapter": {'file': None, 'model': None}, + "insightface": {'provider': None, 'model': None}} + ipadapter, insightface, is_insightface, lora_pattern = None, None, None, None + if optional_ipadapter is not None: + pipeline = optional_ipadapter + if not clip_vision: + clip_vision = pipeline['clipvision']['model'] + ipadapter = pipeline['ipadapter']['model'] + if 'insightface' in pipeline: + insightface = pipeline['insightface']['model'] + lora_pattern = self.get_lora_pattern(pipeline['ipadapter']['file']) + + # 1. Load the clipvision model + if not clip_vision: + clipvision_file, clipvision_name = self.get_clipvision_file(preset, node_name) + if clipvision_file is None: + if preset.lower().startswith("regular"): + # model_url = IPADAPTER_CLIPVISION_MODELS["sigclip_vision_patch14_384"]["model_url"] + # clipvision_file = get_local_filepath(model_url, IPADAPTER_DIR, "sigclip_vision_patch14_384.bin") + from huggingface_hub import snapshot_download + import shutil + CLIP_PATH = os.path.join(folder_paths.models_dir, "clip_vision", "google--siglip-so400m-patch14-384") + print("CLIP_VISION not found locally. Downloading google/siglip-so400m-patch14-384...") + try: + snapshot_download( + repo_id="google/siglip-so400m-patch14-384", + local_dir=os.path.join(folder_paths.models_dir, "clip_vision", + "cache--google--siglip-so400m-patch14-384"), + local_dir_use_symlinks=False, + resume_download=True + ) + shutil.move(os.path.join(folder_paths.models_dir, "clip_vision", + "cache--google--siglip-so400m-patch14-384"), CLIP_PATH) + print(f"CLIP_VISION has been downloaded to {CLIP_PATH}") + except Exception as e: + print(f"Error downloading CLIP model: {e}") + raise + clipvision_file = CLIP_PATH + elif preset.lower().startswith("plus (kolors"): + model_url = IPADAPTER_CLIPVISION_MODELS["clip-vit-large-patch14-336"]["model_url"] + clipvision_file = get_local_filepath(model_url, IPADAPTER_DIR, "clip-vit-large-patch14-336.bin") + else: + model_url = IPADAPTER_CLIPVISION_MODELS["clip-vit-h-14-laion2B-s32B-b79K"]["model_url"] + clipvision_file = get_local_filepath(model_url, IPADAPTER_DIR, "clip-vit-h-14-laion2B-s32B-b79K.safetensors") + clipvision_name = os.path.basename(model_url) + if clipvision_file == pipeline['clipvision']['file']: + clip_vision = pipeline['clipvision']['model'] + elif cache_mode in ["all", "clip_vision only"] and clipvision_name in backend_cache.cache: + log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name} Cached") + _, clip_vision = backend_cache.cache[clipvision_name][1] + else: + if preset.lower().startswith("regular"): + from transformers import SiglipVisionModel, AutoProcessor + image_encoder_path = os.path.dirname(clipvision_file) + image_encoder = SiglipVisionModel.from_pretrained(image_encoder_path) + clip_image_processor = AutoProcessor.from_pretrained(image_encoder_path) + clip_vision = { + 'image_encoder': image_encoder, + 'clip_image_processor': clip_image_processor + } + else: + clip_vision = load_clip_vision(clipvision_file) + log_node_info("easy ipadapterApply", f"Using ClipVisonModel {clipvision_name}") + if cache_mode in ["all", "clip_vision only"]: + backend_cache.update_cache(clipvision_name, 'clip_vision', (False, clip_vision)) + pipeline['clipvision']['file'] = clipvision_file + pipeline['clipvision']['model'] = clip_vision + # 2. Load the ipadapter model + model_type = get_sd_version(model) + if not ipadapter: + ipadapter_file, ipadapter_name, is_insightface, lora_pattern = self.get_ipadapter_file(preset, model_type, node_name) + if ipadapter_file is None: + model_url = IPADAPTER_MODELS[preset][model_type]["model_url"] + local_file_name = IPADAPTER_MODELS[preset][model_type]['model_file_name'] if "model_file_name" in IPADAPTER_MODELS[preset][model_type] else None + ipadapter_file = get_local_filepath(model_url, IPADAPTER_DIR, local_file_name) + ipadapter_name = os.path.basename(model_url) + if ipadapter_file == pipeline['ipadapter']['file']: + ipadapter = pipeline['ipadapter']['model'] + elif cache_mode in ["all", "ipadapter only"] and ipadapter_name in backend_cache.cache: + log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name} Cached") + _, ipadapter = backend_cache.cache[ipadapter_name][1] + else: + ipadapter = self.ipadapter_model_loader(ipadapter_file) + pipeline['ipadapter']['file'] = ipadapter_file + log_node_info("easy ipadapterApply", f"Using IpAdapterModel {ipadapter_name}") + if cache_mode in ["all", "ipadapter only"]: + backend_cache.update_cache(ipadapter_name, 'ipadapter', (False, ipadapter)) + + pipeline['ipadapter']['model'] = ipadapter + + # 3. Load the lora model if needed + if lora_pattern is not None: + if lora_model_strength > 0: + model, _ = self.get_lora_file(preset, lora_pattern, model_type, model, lora_model_strength, 1) + + # 4. Load the insightface model if needed + if is_insightface: + if not insightface: + icache_key = 'insightface-' + provider + if provider == pipeline['insightface']['provider']: + insightface = pipeline['insightface']['model'] + elif cache_mode in ["all", "insightface only"] and icache_key in backend_cache.cache: + log_node_info("easy ipadapterApply", f"Using InsightFaceModel {icache_key} Cached") + _, insightface = backend_cache.cache[icache_key][1] + else: + insightface = insightface_loader(provider, 'antelopev2' if preset == 'FACEID PLUS KOLORS' else 'buffalo_l') + if cache_mode in ["all", "insightface only"]: + backend_cache.update_cache(icache_key, 'insightface',(False, insightface)) + pipeline['insightface']['provider'] = provider + pipeline['insightface']['model'] = insightface + + return (model, pipeline,) + +class ipadapterApply(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + presets = cls().presets + return { + "required": { + "model": ("MODEL",), + "image": ("IMAGE",), + "preset": (presets,), + "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}), + "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"], {"default": "CUDA"}), + "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}), + "weight_faceidv2": ("FLOAT", { "default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "all"},), + "use_tiled": ("BOOLEAN", {"default": False},), + }, + + "optional": { + "attn_mask": ("MASK",), + "optional_ipadapter": ("IPADAPTER",), + } + } + + RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",) + RETURN_NAMES = ("model", "images", "masks", "ipadapter", ) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, start_at, end_at, cache_mode, use_tiled, attn_mask=None, optional_ipadapter=None, weight_kolors=None): + images, masks = image, [None] + model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) + if preset == 'REGULAR - FLUX and SD3.5 only (high strength)': + from .ipadapter import InstantXFluxIpadapterApply, InstantXSD3IpadapterApply + model_type = get_sd_version(model) + if model_type == 'flux': + model, images = InstantXFluxIpadapterApply().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, provider) + elif model_type == 'sd3': + model, images = InstantXSD3IpadapterApply().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, provider) + elif use_tiled and preset not in self.faceid_presets: + if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"] + model, images, masks = cls().apply_tiled(model, ipadapter, image, weight, "linear", start_at, end_at, sharpening=0.0, combine_embeds="concat", image_negative=None, attn_mask=attn_mask, clip_vision=None, embeds_scaling='V only') + else: + if preset in ['FACEID PLUS KOLORS', 'FACEID PLUS V2', 'FACEID PORTRAIT (style transfer)']: + if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] + if weight_kolors is None: + weight_kolors = weight + model, images = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight=weight, weight_type="linear", combine_embeds="concat", weight_faceidv2=weight_faceidv2, image=image, image_negative=None, clip_vision=None, attn_mask=attn_mask, insightface=None, embeds_scaling='V only', weight_kolors=weight_kolors) + else: + if "IPAdapter" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapter"] + model, images = cls().apply_ipadapter(model, ipadapter, image, weight, start_at, end_at, weight_type='standard', attn_mask=attn_mask) + if images is None: + images = image + return (model, images, masks, ipadapter,) + +class ipadapterApplyAdvanced(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + presets = ipa_cls.presets + weight_types = ipa_cls.weight_types + return { + "required": { + "model": ("MODEL",), + "image": ("IMAGE",), + "preset": (presets,), + "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}), + "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"], {"default": "CUDA"}), + "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}), + "weight_faceidv2": ("FLOAT", {"default": 1.0, "min": -1, "max": 5.0, "step": 0.05 }), + "weight_type": (weight_types,), + "combine_embeds": (["concat", "add", "subtract", "average", "norm average"],), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), + "cache_mode": (["insightface only", "clip_vision only","ipadapter only", "all", "none"], {"default": "all"},), + "use_tiled": ("BOOLEAN", {"default": False},), + "use_batch": ("BOOLEAN", {"default": False},), + "sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}), + }, + + "optional": { + "image_negative": ("IMAGE",), + "attn_mask": ("MASK",), + "clip_vision": ("CLIP_VISION",), + "optional_ipadapter": ("IPADAPTER",), + "layer_weights": ("STRING", {"default": "", "multiline": True, "placeholder": "Mad Scientist Layer Weights"}), + } + } + + RETURN_TYPES = ("MODEL", "IMAGE", "MASK", "IPADAPTER",) + RETURN_NAMES = ("model", "images", "masks", "ipadapter", ) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, model, image, preset, lora_strength, provider, weight, weight_faceidv2, weight_type, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, use_tiled, use_batch, sharpening, weight_style=1.0, weight_composition=1.0, image_style=None, image_composition=None, expand_style=False, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None, layer_weights=None, weight_kolors=None): + images, masks = image, [None] + model, ipadapter = self.load_model(model, preset, lora_strength, provider, clip_vision=clip_vision, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) + + if weight_kolors is None: + weight_kolors = weight + + if layer_weights: + if "IPAdapterMS" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] + model, images = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, weight_style=weight_style, weight_composition=weight_composition, image_style=image_style, image_composition=image_composition, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling, layer_weights=layer_weights, weight_kolors=weight_kolors) + elif use_tiled: + if use_batch: + if "IPAdapterTiledBatch" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiledBatch"] + else: + if "IPAdapterTiled" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterTiled"] + model, images, masks = cls().apply_tiled(model, ipadapter, image=image, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, sharpening=sharpening, combine_embeds=combine_embeds, image_negative=image_negative, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling) + else: + if use_batch: + if "IPAdapterBatch" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterBatch"] + else: + if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] + model, images = cls().apply_ipadapter(model, ipadapter, weight=weight, weight_type=weight_type, start_at=start_at, end_at=end_at, combine_embeds=combine_embeds, weight_faceidv2=weight_faceidv2, image=image, image_negative=image_negative, weight_style=1.0, weight_composition=1.0, image_style=image_style, image_composition=image_composition, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling, weight_kolors=weight_kolors) + if images is None: + images = image + return (model, images, masks, ipadapter) + +class ipadapterApplyFaceIDKolors(ipadapterApplyAdvanced): + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + presets = ipa_cls.presets + weight_types = ipa_cls.weight_types + return { + "required": { + "model": ("MODEL",), + "image": ("IMAGE",), + "preset": (['FACEID PLUS KOLORS'], {"default":"FACEID PLUS KOLORS"}), + "lora_strength": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}), + "provider": (["CPU", "CUDA", "ROCM", "DirectML", "OpenVINO", "CoreML"], {"default": "CUDA"}), + "weight": ("FLOAT", {"default": 0.8, "min": -1, "max": 3, "step": 0.05}), + "weight_faceidv2": ("FLOAT", {"default": 1.0, "min": -1, "max": 5.0, "step": 0.05}), + "weight_kolors": ("FLOAT", {"default": 0.8, "min": -1, "max": 5.0, "step": 0.05}), + "weight_type": (weight_types,), + "combine_embeds": (["concat", "add", "subtract", "average", "norm average"],), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), + "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], {"default": "all"},), + "use_tiled": ("BOOLEAN", {"default": False},), + "use_batch": ("BOOLEAN", {"default": False},), + "sharpening": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.05}), + }, + + "optional": { + "image_negative": ("IMAGE",), + "attn_mask": ("MASK",), + "clip_vision": ("CLIP_VISION",), + "optional_ipadapter": ("IPADAPTER",), + } + } + + +class ipadapterStyleComposition(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + normal_presets = ipa_cls.normal_presets + weight_types = ipa_cls.weight_types + return { + "required": { + "model": ("MODEL",), + "image_style": ("IMAGE",), + "preset": (normal_presets,), + "weight_style": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}), + "weight_composition": ("FLOAT", {"default": 1.0, "min": -1, "max": 5, "step": 0.05}), + "expand_style": ("BOOLEAN", {"default": False}), + "combine_embeds": (["concat", "add", "subtract", "average", "norm average"], {"default": "average"}), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), + "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], + {"default": "all"},), + }, + "optional": { + "image_composition": ("IMAGE",), + "image_negative": ("IMAGE",), + "attn_mask": ("MASK",), + "clip_vision": ("CLIP_VISION",), + "optional_ipadapter": ("IPADAPTER",), + } + } + + CATEGORY = "EasyUse/Adapter" + + RETURN_TYPES = ("MODEL", "IPADAPTER",) + RETURN_NAMES = ("model", "ipadapter",) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, model, preset, weight_style, weight_composition, expand_style, combine_embeds, start_at, end_at, embeds_scaling, cache_mode, image_style=None , image_composition=None, image_negative=None, clip_vision=None, attn_mask=None, optional_ipadapter=None): + model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) + + if "IPAdapterAdvanced" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterAdvanced"] + + model, image = cls().apply_ipadapter(model, ipadapter, start_at=start_at, end_at=end_at, weight_style=weight_style, weight_composition=weight_composition, weight_type='linear', combine_embeds=combine_embeds, weight_faceidv2=weight_composition, image_style=image_style, image_composition=image_composition, image_negative=image_negative, expand_style=expand_style, clip_vision=clip_vision, attn_mask=attn_mask, insightface=None, embeds_scaling=embeds_scaling) + return (model, ipadapter) + +class ipadapterApplyEncoder(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + normal_presets = ipa_cls.normal_presets + max_embeds_num = 4 + inputs = { + "required": { + "model": ("MODEL",), + "clip_vision": ("CLIP_VISION",), + "image1": ("IMAGE",), + "preset": (normal_presets,), + "num_embeds": ("INT", {"default": 2, "min": 1, "max": max_embeds_num}), + }, + "optional": {} + } + + for i in range(1, max_embeds_num + 1): + if i > 1: + inputs["optional"][f"image{i}"] = ("IMAGE",) + for i in range(1, max_embeds_num + 1): + inputs["optional"][f"mask{i}"] = ("MASK",) + inputs["optional"][f"weight{i}"] = ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}) + inputs["optional"]["combine_method"] = (["concat", "add", "subtract", "average", "norm average", "max", "min"],) + inputs["optional"]["optional_ipadapter"] = ("IPADAPTER",) + inputs["optional"]["pos_embeds"] = ("EMBEDS",) + inputs["optional"]["neg_embeds"] = ("EMBEDS",) + return inputs + + RETURN_TYPES = ("MODEL", "CLIP_VISION","IPADAPTER", "EMBEDS", "EMBEDS", ) + RETURN_NAMES = ("model", "clip_vision","ipadapter", "pos_embed", "neg_embed",) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def batch(self, embeds, method): + if method == 'concat' and len(embeds) == 1: + return (embeds[0],) + + embeds = [embed for embed in embeds if embed is not None] + embeds = torch.cat(embeds, dim=0) + + if method == "add": + embeds = torch.sum(embeds, dim=0).unsqueeze(0) + elif method == "subtract": + embeds = embeds[0] - torch.mean(embeds[1:], dim=0) + embeds = embeds.unsqueeze(0) + elif method == "average": + embeds = torch.mean(embeds, dim=0).unsqueeze(0) + elif method == "norm average": + embeds = torch.mean(embeds / torch.norm(embeds, dim=0, keepdim=True), dim=0).unsqueeze(0) + elif method == "max": + embeds = torch.max(embeds, dim=0).values.unsqueeze(0) + elif method == "min": + embeds = torch.min(embeds, dim=0).values.unsqueeze(0) + + return embeds + + def apply(self, **kwargs): + model = kwargs['model'] + clip_vision = kwargs['clip_vision'] + preset = kwargs['preset'] + if 'optional_ipadapter' in kwargs: + ipadapter = kwargs['optional_ipadapter'] + else: + model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=clip_vision, optional_ipadapter=None, cache_mode='none') + + if "IPAdapterEncoder" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + encoder_cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterEncoder"] + pos_embeds = kwargs["pos_embeds"] if "pos_embeds" in kwargs else [] + neg_embeds = kwargs["neg_embeds"] if "neg_embeds" in kwargs else [] + for i in range(1, kwargs['num_embeds'] + 1): + if f"image{i}" not in kwargs: + raise Exception(f"image{i} is required") + kwargs[f"mask{i}"] = kwargs[f"mask{i}"] if f"mask{i}" in kwargs else None + kwargs[f"weight{i}"] = kwargs[f"weight{i}"] if f"weight{i}" in kwargs else 1.0 + + pos, neg = encoder_cls().encode(ipadapter, kwargs[f"image{i}"], kwargs[f"weight{i}"], kwargs[f"mask{i}"], clip_vision=clip_vision) + pos_embeds.append(pos) + neg_embeds.append(neg) + + pos_embeds = self.batch(pos_embeds, kwargs['combine_method']) + neg_embeds = self.batch(neg_embeds, kwargs['combine_method']) + + return (model,clip_vision, ipadapter, pos_embeds, neg_embeds) + +class ipadapterApplyEmbeds(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + weight_types = ipa_cls.weight_types + return { + "required": { + "model": ("MODEL",), + "clip_vision": ("CLIP_VISION",), + "ipadapter": ("IPADAPTER",), + "pos_embed": ("EMBEDS",), + "weight": ("FLOAT", {"default": 1.0, "min": -1, "max": 3, "step": 0.05}), + "weight_type": (weight_types,), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), + }, + + "optional": { + "neg_embed": ("EMBEDS",), + "attn_mask": ("MASK",), + } + } + + RETURN_TYPES = ("MODEL", "IPADAPTER",) + RETURN_NAMES = ("model", "ipadapter", ) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, model, ipadapter, clip_vision, pos_embed, weight, weight_type, start_at, end_at, embeds_scaling, attn_mask=None, neg_embed=None,): + if "IPAdapterEmbeds" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterEmbeds"] + model, image = cls().apply_ipadapter(model, ipadapter, pos_embed, weight, weight_type, start_at, end_at, neg_embed=neg_embed, attn_mask=attn_mask, clip_vision=clip_vision, embeds_scaling=embeds_scaling) + + return (model, ipadapter) + +class ipadapterApplyRegional(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + weight_types = ipa_cls.weight_types + return { + "required": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "positive": ("STRING", {"default": "", "placeholder": "positive", "multiline": True}), + "negative": ("STRING", {"default": "", "placeholder": "negative", "multiline": True}), + "image_weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 3.0, "step": 0.05}), + "prompt_weight": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.05}), + "weight_type": (weight_types,), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + }, + + "optional": { + "mask": ("MASK",), + "optional_ipadapter_params": ("IPADAPTER_PARAMS",), + }, + "hidden": {"prompt": "PROMPT", "my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE", "IPADAPTER_PARAMS", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "ipadapter_params", "positive", "negative") + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, pipe, image, positive, negative, image_weight, prompt_weight, weight_type, start_at, end_at, mask=None, optional_ipadapter_params=None, prompt=None, my_unique_id=None): + model = pipe['model'] + + if positive == '': + positive = pipe['loader_settings']['positive'] + if negative == '': + negative = pipe['loader_settings']['negative'] + + if "clip" not in pipe or not pipe['clip']: + if "chatglm3_model" in pipe: + chatglm3_model = pipe['chatglm3_model'] + # text encode + log_node_warn("Positive encoding...") + positive_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, positive, False) + log_node_warn("Negative encoding...") + negative_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, negative, False) + else: + clip = pipe['clip'] + clip_skip = pipe['loader_settings']['clip_skip'] + a1111_prompt_style = pipe['loader_settings']['a1111_prompt_style'] + pipe_lora_stack = pipe['loader_settings']['lora_stack'] + positive_token_normalization = pipe['loader_settings']['positive_token_normalization'] + positive_weight_interpretation = pipe['loader_settings']['positive_weight_interpretation'] + negative_token_normalization = pipe['loader_settings']['negative_token_normalization'] + negative_weight_interpretation = pipe['loader_settings']['negative_weight_interpretation'] + + positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, pipe_lora_stack, positive, positive_token_normalization, positive_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache) + negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, clip_skip, pipe_lora_stack, negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, my_unique_id, prompt, easyCache) + + #ipadapter regional + if "IPAdapterRegionalConditioning" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterRegionalConditioning"] + ipadapter_params, new_positive_embeds, new_negative_embeds = cls().conditioning(image, image_weight, prompt_weight, weight_type, start_at, end_at, mask=mask, positive=positive_embeddings_final, negative=negative_embeddings_final) + + if optional_ipadapter_params is not None: + positive_embeds = pipe['positive'] + new_positive_embeds + negative_embeds = pipe['negative'] + new_negative_embeds + _ipadapter_params = { + "image": optional_ipadapter_params["image"] + ipadapter_params["image"], + "attn_mask": optional_ipadapter_params["attn_mask"] + ipadapter_params["attn_mask"], + "weight": optional_ipadapter_params["weight"] + ipadapter_params["weight"], + "weight_type": optional_ipadapter_params["weight_type"] + ipadapter_params["weight_type"], + "start_at": optional_ipadapter_params["start_at"] + ipadapter_params["start_at"], + "end_at": optional_ipadapter_params["end_at"] + ipadapter_params["end_at"], + } + ipadapter_params = _ipadapter_params + del _ipadapter_params + else: + positive_embeds = new_positive_embeds + negative_embeds = new_negative_embeds + + new_pipe = { + **pipe, + "positive": positive_embeds, + "negative": negative_embeds, + } + + del pipe + + return (new_pipe, ipadapter_params, positive_embeds, negative_embeds) + +class ipadapterApplyFromParams(ipadapter): + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + ipa_cls = cls() + normal_presets = ipa_cls.normal_presets + return { + "required": { + "model": ("MODEL",), + "preset": (normal_presets,), + "ipadapter_params": ("IPADAPTER_PARAMS",), + "combine_embeds": (["concat", "add", "subtract", "average", "norm average", "max", "min"],), + "embeds_scaling": (['V only', 'K+V', 'K+V w/ C penalty', 'K+mean(V) w/ C penalty'],), + "cache_mode": (["insightface only", "clip_vision only", "ipadapter only", "all", "none"], + {"default": "insightface only"}), + }, + + "optional": { + "optional_ipadapter": ("IPADAPTER",), + "image_negative": ("IMAGE",), + } + } + + RETURN_TYPES = ("MODEL", "IPADAPTER",) + RETURN_NAMES = ("model", "ipadapter", ) + CATEGORY = "EasyUse/Adapter" + FUNCTION = "apply" + + def apply(self, model, preset, ipadapter_params, combine_embeds, embeds_scaling, cache_mode, optional_ipadapter=None, image_negative=None,): + model, ipadapter = self.load_model(model, preset, 0, 'CPU', clip_vision=None, optional_ipadapter=optional_ipadapter, cache_mode=cache_mode) + if "IPAdapterFromParams" not in ALL_NODE_CLASS_MAPPINGS: + self.error() + cls = ALL_NODE_CLASS_MAPPINGS["IPAdapterFromParams"] + model, image = cls().apply_ipadapter(model, ipadapter, clip_vision=None, combine_embeds=combine_embeds, embeds_scaling=embeds_scaling, image_negative=image_negative, ipadapter_params=ipadapter_params) + + return (model, ipadapter) + +#Apply InstantID +class instantID: + + def error(self): + raise Exception(f"[ERROR] To use instantIDApply, you need to install 'ComfyUI_InstantID'") + + def run(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + instantid_model, insightface_model, face_embeds = None, None, None + model = pipe['model'] + # Load InstantID + cache_key = 'instantID' + if cache_key in backend_cache.cache: + log_node_info("easy instantIDApply","Using InstantIDModel Cached") + _, instantid_model = backend_cache.cache[cache_key][1] + if "InstantIDModelLoader" in ALL_NODE_CLASS_MAPPINGS: + load_instant_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDModelLoader"] + instantid_model, = load_instant_cls().load_model(instantid_file) + backend_cache.update_cache(cache_key, 'instantid', (False, instantid_model)) + else: + self.error() + icache_key = 'insightface-' + insightface + if icache_key in backend_cache.cache: + log_node_info("easy instantIDApply", f"Using InsightFaceModel {insightface} Cached") + _, insightface_model = backend_cache.cache[icache_key][1] + elif "InstantIDFaceAnalysis" in ALL_NODE_CLASS_MAPPINGS: + load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["InstantIDFaceAnalysis"] + insightface_model, = load_insightface_cls().load_insight_face(insightface) + backend_cache.update_cache(icache_key, 'insightface', (False, insightface_model)) + else: + self.error() + + # Apply InstantID + if "ApplyInstantID" in ALL_NODE_CLASS_MAPPINGS: + instantid_apply = ALL_NODE_CLASS_MAPPINGS['ApplyInstantID'] + if control_net is None: + control_net = easyCache.load_controlnet(control_net_name, cn_soft_weights) + model, positive, negative = instantid_apply().apply_instantid(instantid_model, insightface_model, control_net, image, model, positive, negative, start_at, end_at, weight=weight, ip_weight=None, cn_strength=cn_strength, noise=noise, image_kps=image_kps, mask=mask) + else: + self.error() + + new_pipe = { + "model": model, + "positive": positive, + "negative": negative, + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": pipe["samples"], + "images": pipe["images"], + "seed": 0, + + "loader_settings": pipe["loader_settings"] + } + + del pipe + + return (new_pipe, model, positive, negative) + +class instantIDApply(instantID): + + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required":{ + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "instantid_file": (folder_paths.get_filename_list("instantid"),), + "insightface": (["CPU", "CUDA", "ROCM"],), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + "cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + "weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }), + "noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }), + }, + "optional": { + "image_kps": ("IMAGE",), + "mask": ("MASK",), + "control_net": ("CONTROL_NET",), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID" + }, + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "model", "positive", "negative") + + FUNCTION = "apply" + CATEGORY = "EasyUse/Adapter" + + + def apply(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + positive = pipe['positive'] + negative = pipe['negative'] + return self.run(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id) + +#Apply InstantID Advanced +class instantIDApplyAdvanced(instantID): + + def __init__(self): + super().__init__() + pass + + @classmethod + def INPUT_TYPES(cls): + return { + "required":{ + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "instantid_file": (folder_paths.get_filename_list("instantid"),), + "insightface": (["CPU", "CUDA", "ROCM"],), + "control_net_name": (folder_paths.get_filename_list("controlnet"),), + "cn_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}), + "cn_soft_weights": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001},), + "weight": ("FLOAT", {"default": .8, "min": 0.0, "max": 5.0, "step": 0.01, }), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001, }), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001, }), + "noise": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.05, }), + }, + "optional": { + "image_kps": ("IMAGE",), + "mask": ("MASK",), + "control_net": ("CONTROL_NET",), + "positive": ("CONDITIONING",), + "negative": ("CONDITIONING",), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID" + }, + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING") + RETURN_NAMES = ("pipe", "model", "positive", "negative") + + FUNCTION = "apply_advanced" + CATEGORY = "EasyUse/Adapter" + + def apply_advanced(self, pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps=None, mask=None, control_net=None, positive=None, negative=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + + positive = positive if positive is not None else pipe['positive'] + negative = negative if negative is not None else pipe['negative'] + + return self.run(pipe, image, instantid_file, insightface, control_net_name, cn_strength, cn_soft_weights, weight, start_at, end_at, noise, image_kps, mask, control_net, positive, negative, prompt, extra_pnginfo, my_unique_id) + +class applyPulID: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "pulid_file": (folder_paths.get_filename_list("pulid"),), + "insightface": (["CPU", "CUDA", "ROCM"],), + "image": ("IMAGE",), + "method": (["fidelity", "style", "neutral"],), + "weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 5.0, "step": 0.05}), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + }, + "optional": { + "attn_mask": ("MASK",), + }, + } + + RETURN_TYPES = ("MODEL",) + RETURN_NAMES = ("model",) + + FUNCTION = "run" + CATEGORY = "EasyUse/Adapter" + + def error(self): + raise Exception(f"[ERROR] To use pulIDApply, you need to install 'ComfyUI_PulID'") + + def run(self, model, image, pulid_file, insightface, weight, start_at, end_at, method=None, noise=0.0, fidelity=None, projection=None, attn_mask=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + pulid_model, insightface_model, eva_clip = None, None, None + # Load PulID + cache_key = 'pulID' + if cache_key in backend_cache.cache: + log_node_info("easy pulIDApply","Using InstantIDModel Cached") + _, pulid_model = backend_cache.cache[cache_key][1] + if "PulidModelLoader" in ALL_NODE_CLASS_MAPPINGS: + load_pulid_cls = ALL_NODE_CLASS_MAPPINGS["PulidModelLoader"] + pulid_model, = load_pulid_cls().load_model(pulid_file) + backend_cache.update_cache(cache_key, 'pulid', (False, pulid_model)) + else: + self.error() + # Load Insightface + icache_key = 'insightface-' + insightface + if icache_key in backend_cache.cache: + log_node_info("easy pulIDApply", f"Using InsightFaceModel {insightface} Cached") + _, insightface_model = backend_cache.cache[icache_key][1] + elif "PulidInsightFaceLoader" in ALL_NODE_CLASS_MAPPINGS: + load_insightface_cls = ALL_NODE_CLASS_MAPPINGS["PulidInsightFaceLoader"] + insightface_model, = load_insightface_cls().load_insightface(insightface) + backend_cache.update_cache(icache_key, 'insightface', (False, insightface_model)) + else: + self.error() + # Load Eva clip + ecache_key = 'eva_clip' + if ecache_key in backend_cache.cache: + log_node_info("easy pulIDApply", f"Using EVAClipModel Cached") + _, eva_clip = backend_cache.cache[ecache_key][1] + elif "PulidEvaClipLoader" in ALL_NODE_CLASS_MAPPINGS: + load_evaclip_cls = ALL_NODE_CLASS_MAPPINGS["PulidEvaClipLoader"] + eva_clip, = load_evaclip_cls().load_eva_clip() + backend_cache.update_cache(ecache_key, 'eva_clip', (False, eva_clip)) + else: + self.error() + + # Apply PulID + if method is not None: + if "ApplyPulid" in ALL_NODE_CLASS_MAPPINGS: + cls = ALL_NODE_CLASS_MAPPINGS['ApplyPulid'] + model, = cls().apply_pulid(model, pulid=pulid_model, eva_clip=eva_clip, face_analysis=insightface_model, image=image, weight=weight, method=method, start_at=start_at, end_at=end_at, attn_mask=attn_mask) + else: + self.error() + else: + if "ApplyPulidAdvanced" in ALL_NODE_CLASS_MAPPINGS: + cls = ALL_NODE_CLASS_MAPPINGS['ApplyPulidAdvanced'] + model, = cls().apply_pulid(model, pulid=pulid_model, eva_clip=eva_clip, face_analysis=insightface_model, image=image, weight=weight, projection=projection, fidelity=fidelity, noise=noise, start_at=start_at, end_at=end_at, attn_mask=attn_mask) + else: + self.error() + + return (model,) + +class applyPulIDADV(applyPulID): + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "pulid_file": (folder_paths.get_filename_list("pulid"),), + "insightface": (["CPU", "CUDA", "ROCM"],), + "image": ("IMAGE",), + "weight": ("FLOAT", {"default": 1.0, "min": -1.0, "max": 5.0, "step": 0.05}), + "projection": (["ortho_v2", "ortho", "none"], {"default":"ortho_v2"}), + "fidelity": ("INT", {"default": 8, "min": 0, "max": 32, "step": 1}), + "noise": ("FLOAT", {"default": 0.0, "min": -1.0, "max": 1.0, "step": 0.1}), + "start_at": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_at": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + }, + "optional": { + "attn_mask": ("MASK",), + }, + } + +# class applyOminiControl: +# @classmethod +# def INPUT_TYPES(s): +# return { +# "required": { +# "model": ("MODEL",), +# "image": ("IMAGE",), +# "vae": ("VAE",), +# }, +# "optional": { +# }, +# } +# +# RETURN_TYPES = ("MODEL",) +# RETURN_NAMES = ("model",) +# +# FUNCTION = "apply" +# CATEGORY = "EasyUse/Adapter" +# +# def apply(self, model, image, vae): +# from .omini_control import apply_omini_control +# new_model = apply_omini_control(model, image, vae) +# return (new_model,) + +# ---------------------------------------------------------------适配器 结束----------------------------------------------------------------------# + +#---------------------------------------------------------------预采样 开始----------------------------------------------------------------------# + +# 预采样设置(基础) +class samplerSettings: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS + new_schedulers,), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "optional": { + "image_to_latent": ("IMAGE",), + "latent": ("LATENT",), + }, + "hidden": + {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE", ) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def settings(self, pipe, steps, cfg, sampler_name, scheduler, denoise, seed, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + # 图生图转换 + vae = pipe["vae"] + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + if image_to_latent is not None: + _, height, width, _ = image_to_latent.shape + if height == 1 and width == 1: + samples = pipe["samples"] + images = pipe["images"] + else: + samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + images = image_to_latent + elif latent is not None: + samples = latent + images = pipe["images"] + else: + samples = pipe["samples"] + images = pipe["images"] + + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": samples, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "add_noise": "enabled" + } + } + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + +# 预采样设置(高级) +class samplerSettingsAdvanced: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS + new_schedulers,), + "start_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), + "end_at_step": ("INT", {"default": 10000, "min": 0, "max": 10000}), + "add_noise": (["enable (CPU)", "enable (GPU=A1111)", "disable"], {"default": "enable (CPU)"}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + "return_with_leftover_noise": (["disable", "enable"], ), + }, + "optional": { + "image_to_latent": ("IMAGE",), + "latent": ("LATENT",) + }, + "hidden": + {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE", ) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def settings(self, pipe, steps, cfg, sampler_name, scheduler, start_at_step, end_at_step, add_noise, seed, return_with_leftover_noise, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + # 图生图转换 + vae = pipe["vae"] + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + if image_to_latent is not None: + _, height, width, _ = image_to_latent.shape + if height == 1 and width == 1: + samples = pipe["samples"] + images = pipe["images"] + else: + samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + images = image_to_latent + elif latent is not None: + samples = latent + images = pipe["images"] + else: + samples = pipe["samples"] + images = pipe["images"] + + force_full_denoise = True + if return_with_leftover_noise == "enable": + force_full_denoise = False + + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": samples, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "start_step": start_at_step, + "last_step": end_at_step, + "denoise": 1.0, + "add_noise": add_noise, + "force_full_denoise": force_full_denoise + } + } + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + +# 预采样设置(噪声注入) +class samplerSettingsNoiseIn: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "factor": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step":0.01, "round": 0.01}), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS+new_schedulers,), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "optional": { + "optional_noise_seed": ("INT",{"forceInput": True}), + "optional_latent": ("LATENT",), + }, + "hidden": + {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE", ) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def slerp(self, val, low, high): + dims = low.shape + + low = low.reshape(dims[0], -1) + high = high.reshape(dims[0], -1) + + low_norm = low / torch.norm(low, dim=1, keepdim=True) + high_norm = high / torch.norm(high, dim=1, keepdim=True) + + low_norm[low_norm != low_norm] = 0.0 + high_norm[high_norm != high_norm] = 0.0 + + omega = torch.acos((low_norm * high_norm).sum(1)) + so = torch.sin(omega) + res = (torch.sin((1.0 - val) * omega) / so).unsqueeze(1) * low + (torch.sin(val * omega) / so).unsqueeze( + 1) * high + + return res.reshape(dims) + + def prepare_mask(self, mask, shape): + mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), + size=(shape[2], shape[3]), mode="bilinear") + mask = mask.expand((-1, shape[1], -1, -1)) + if mask.shape[0] < shape[0]: + mask = mask.repeat((shape[0] - 1) // mask.shape[0] + 1, 1, 1, 1)[:shape[0]] + return mask + + def expand_mask(self, mask, expand, tapered_corners): + try: + import scipy + + c = 0 if tapered_corners else 1 + kernel = np.array([[c, 1, c], + [1, 1, 1], + [c, 1, c]]) + mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1])) + out = [] + for m in mask: + output = m.numpy() + for _ in range(abs(expand)): + if expand < 0: + output = scipy.ndimage.grey_erosion(output, footprint=kernel) + else: + output = scipy.ndimage.grey_dilation(output, footprint=kernel) + output = torch.from_numpy(output) + out.append(output) + + return torch.stack(out, dim=0) + except: + return None + + def settings(self, pipe, factor, steps, cfg, sampler_name, scheduler, denoise, seed, optional_noise_seed=None, optional_latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + latent = optional_latent if optional_latent is not None else pipe["samples"] + model = pipe["model"] + + # generate base noise + batch_size, _, height, width = latent["samples"].shape + generator = torch.manual_seed(seed) + base_noise = torch.randn((1, 4, height, width), dtype=torch.float32, device="cpu", generator=generator).repeat(batch_size, 1, 1, 1).cpu() + + # generate variation noise + if optional_noise_seed is None or optional_noise_seed == seed: + optional_noise_seed = seed+1 + generator = torch.manual_seed(optional_noise_seed) + variation_noise = torch.randn((batch_size, 4, height, width), dtype=torch.float32, device="cpu", + generator=generator).cpu() + + slerp_noise = self.slerp(factor, base_noise, variation_noise) + + end_at_step = steps # min(steps, end_at_step) + start_at_step = round(end_at_step - end_at_step * denoise) + + device = comfy.model_management.get_torch_device() + comfy.model_management.load_model_gpu(model) + model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device()) + sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name, + scheduler=scheduler, denoise=1.0, model_options=model.model_options) + sigmas = sampler.sigmas + sigma = sigmas[start_at_step] - sigmas[end_at_step] + sigma /= model.model.latent_format.scale_factor + sigma = sigma.cpu().numpy() + + work_latent = latent.copy() + work_latent["samples"] = latent["samples"].clone() + slerp_noise * sigma + + if "noise_mask" in latent: + noise_mask = self.prepare_mask(latent["noise_mask"], latent['samples'].shape) + work_latent["samples"] = noise_mask * work_latent["samples"] + (1-noise_mask) * latent["samples"] + work_latent['noise_mask'] = self.expand_mask(latent["noise_mask"].clone(), 5, True) + + if pipe is None: + pipe = {} + + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": work_latent, + "images": pipe['images'], + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "add_noise": "disable" + } + } + + return (new_pipe,) + +# 预采样设置(自定义) +import comfy_extras.nodes_custom_sampler as custom_samplers +from tqdm import trange +class samplerCustomSettings: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "pipe": ("PIPE_LINE",), + "guider": (['CFG','DualCFG','Basic', 'IP2P+CFG', 'IP2P+DualCFG','IP2P+Basic'],{"default":"Basic"}), + "cfg": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0}), + "cfg_negative": ("FLOAT", {"default": 1.5, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS + ['inversed_euler'],), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['karrasADV','exponentialADV','polyExponential', 'sdturbo', 'vp', 'alignYourSteps', 'gits'],), + "coeff": ("FLOAT", {"default": 1.20, "min": 0.80, "max": 1.50, "step": 0.05}), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), + "sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), + "rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.01, "round": False}), + "beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), + "beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1000.0, "step": 0.01, "round": False}), + "eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step": 0.0001, "round": False}), + "flip_sigmas": ("BOOLEAN", {"default": False}), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "add_noise": (["enable (CPU)", "enable (GPU=A1111)", "disable"], {"default": "enable (CPU)"}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "optional": { + "image_to_latent": ("IMAGE",), + "latent": ("LATENT",), + "optional_sampler":("SAMPLER",), + "optional_sigmas":("SIGMAS",), + }, + "hidden": + {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE", ) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def ip2p(self, positive, negative, vae, pixels, latent=None): + if latent is not None: + concat_latent = latent + else: + x = (pixels.shape[1] // 8) * 8 + y = (pixels.shape[2] // 8) * 8 + + if pixels.shape[1] != x or pixels.shape[2] != y: + x_offset = (pixels.shape[1] % 8) // 2 + y_offset = (pixels.shape[2] % 8) // 2 + pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:] + + concat_latent = vae.encode(pixels) + + out_latent = {} + out_latent["samples"] = torch.zeros_like(concat_latent) + + out = [] + for conditioning in [positive, negative]: + c = [] + for t in conditioning: + d = t[1].copy() + d["concat_latent_image"] = concat_latent + n = [t[0], d] + c.append(n) + out.append(c) + return (out[0], out[1], out_latent) + + + def settings(self, pipe, guider, cfg, cfg_negative, sampler_name, scheduler, coeff, steps, sigma_max, sigma_min, rho, beta_d, beta_min, eps_s, flip_sigmas, denoise, add_noise, seed, image_to_latent=None, latent=None, optional_sampler=None, optional_sigmas=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + + # 图生图转换 + vae = pipe["vae"] + model = pipe["model"] + positive = pipe['positive'] + negative = pipe['negative'] + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + + if image_to_latent is not None: + _, height, width, _ = image_to_latent.shape + if height == 1 and width == 1: + samples = pipe["samples"] + images = pipe["images"] + else: + if "IP2P" in guider: + positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], vae, image_to_latent) + samples = latent + else: + samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + images = image_to_latent + elif latent is not None: + if "IP2P" in guider: + positive, negative, latent = self.ip2p(pipe['positive'], pipe['negative'], latent=latent) + samples = latent + else: + samples = latent + images = pipe["images"] + else: + samples = pipe["samples"] + images = pipe["images"] + + + new_pipe = { + "model": model, + "positive": positive, + "negative": negative, + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": samples, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "middle": pipe['negative'], + "steps": steps, + "cfg": cfg, + "cfg_negative": cfg_negative, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "add_noise": add_noise, + "custom": { + "guider": guider, + "coeff": coeff, + "sigma_max": sigma_max, + "sigma_min": sigma_min, + "rho": rho, + "beta_d": beta_d, + "beta_min": beta_min, + "eps_s": beta_min, + "flip_sigmas": flip_sigmas + }, + "optional_sampler": optional_sampler, + "optional_sigmas": optional_sigmas + } + } + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + +# 预采样设置(SDTurbo) +from .libs.gradual_latent_hires_fix import sample_dpmpp_2s_ancestral, sample_dpmpp_2m_sde, sample_lcm, sample_euler_ancestral +class sdTurboSettings: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": { + "pipe": ("PIPE_LINE",), + "steps": ("INT", {"default": 1, "min": 1, "max": 10}), + "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.SAMPLER_NAMES,), + "eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), + "s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), + "upscale_ratio": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 16.0, "step": 0.01, "round": False}), + "start_step": ("INT", {"default": 5, "min": 0, "max": 1000, "step": 1}), + "end_step": ("INT", {"default": 15, "min": 0, "max": 1000, "step": 1}), + "upscale_n_step": ("INT", {"default": 3, "min": 0, "max": 1000, "step": 1}), + "unsharp_kernel_size": ("INT", {"default": 3, "min": 1, "max": 21, "step": 1}), + "unsharp_sigma": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), + "unsharp_strength": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 10.0, "step": 0.01, "round": False}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def settings(self, pipe, steps, cfg, sampler_name, eta, s_noise, upscale_ratio, start_step, end_step, upscale_n_step, unsharp_kernel_size, unsharp_sigma, unsharp_strength, seed, prompt=None, extra_pnginfo=None, my_unique_id=None): + model = pipe['model'] + # sigma + timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[:steps] + sigmas = model.model.model_sampling.sigma(timesteps) + sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) + + #sampler + sample_function = None + extra_options = { + "eta": eta, + "s_noise": s_noise, + "upscale_ratio": upscale_ratio, + "start_step": start_step, + "end_step": end_step, + "upscale_n_step": upscale_n_step, + "unsharp_kernel_size": unsharp_kernel_size, + "unsharp_sigma": unsharp_sigma, + "unsharp_strength": unsharp_strength, + } + if sampler_name == "euler_ancestral": + sample_function = sample_euler_ancestral + elif sampler_name == "dpmpp_2s_ancestral": + sample_function = sample_dpmpp_2s_ancestral + elif sampler_name == "dpmpp_2m_sde": + sample_function = sample_dpmpp_2m_sde + elif sampler_name == "lcm": + sample_function = sample_lcm + + if sample_function is not None: + unsharp_kernel_size = unsharp_kernel_size if unsharp_kernel_size % 2 == 1 else unsharp_kernel_size + 1 + extra_options["unsharp_kernel_size"] = unsharp_kernel_size + _sampler = comfy.samplers.KSAMPLER(sample_function, extra_options) + else: + _sampler = comfy.samplers.sampler_object(sampler_name) + extra_options = None + + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": pipe["samples"], + "images": pipe["images"], + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "extra_options": extra_options, + "sampler": _sampler, + "sigmas": sigmas, + "steps": steps, + "cfg": cfg, + "add_noise": "enabled" + } + } + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + + +# cascade预采样参数 +class cascadeSettings: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), + "decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "optional": { + "image_to_latent_c": ("IMAGE",), + "latent_c": ("LATENT",), + }, + "hidden":{"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def settings(self, pipe, encode_vae_name, decode_vae_name, steps, cfg, sampler_name, scheduler, denoise, seed, model=None, image_to_latent_c=None, latent_c=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + images, samples_c = None, None + samples = pipe['samples'] + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + + encode_vae_name = encode_vae_name if encode_vae_name is not None else pipe['loader_settings']['encode_vae_name'] + decode_vae_name = decode_vae_name if decode_vae_name is not None else pipe['loader_settings']['decode_vae_name'] + + if image_to_latent_c is not None: + if encode_vae_name != 'None': + encode_vae = easyCache.load_vae(encode_vae_name) + else: + encode_vae = pipe['vae'][0] + if "compression" not in pipe["loader_settings"]: + raise Exception("compression is not found") + compression = pipe["loader_settings"]['compression'] + width = image_to_latent_c.shape[-2] + height = image_to_latent_c.shape[-3] + out_width = (width // compression) * encode_vae.downscale_ratio + out_height = (height // compression) * encode_vae.downscale_ratio + + s = comfy.utils.common_upscale(image_to_latent_c.movedim(-1, 1), out_width, out_height, "bicubic", + "center").movedim(1, + -1) + c_latent = encode_vae.encode(s[:, :, :, :3]) + b_latent = torch.zeros([c_latent.shape[0], 4, height // 4, width // 4]) + + samples_c = {"samples": c_latent} + samples_c = RepeatLatentBatch().repeat(samples_c, batch_size)[0] + + samples_b = {"samples": b_latent} + samples_b = RepeatLatentBatch().repeat(samples_b, batch_size)[0] + samples = (samples_c, samples_b) + images = image_to_latent_c + elif latent_c is not None: + samples_c = latent_c + samples = (samples_c, samples[1]) + images = pipe["images"] + if samples_c is not None: + samples = (samples_c, samples[1]) + + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": samples, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "encode_vae_name": encode_vae_name, + "decode_vae_name": decode_vae_name, + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "add_noise": "enabled" + } + } + + sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + +# layerDiffusion预采样参数 +class layerDiffusionSettings: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + { + "pipe": ("PIPE_LINE",), + "method": ([LayerMethod.FG_ONLY_ATTN.value, LayerMethod.FG_ONLY_CONV.value, LayerMethod.EVERYTHING.value, LayerMethod.FG_TO_BLEND.value, LayerMethod.BG_TO_BLEND.value],), + "weight": ("FLOAT",{"default": 1.0, "min": -1, "max": 3, "step": 0.05},), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler"}), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS+ new_schedulers, {"default": "normal"}), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "optional": { + "image": ("IMAGE",), + "blended_image": ("IMAGE",), + "mask": ("MASK",), + # "latent": ("LATENT",), + # "blended_latent": ("LATENT",), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def get_layer_diffusion_method(self, method, has_blend_latent): + method = LayerMethod(method) + if has_blend_latent: + if method == LayerMethod.BG_TO_BLEND: + method = LayerMethod.BG_BLEND_TO_FG + elif method == LayerMethod.FG_TO_BLEND: + method = LayerMethod.FG_BLEND_TO_BG + return method + + def settings(self, pipe, method, weight, steps, cfg, sampler_name, scheduler, denoise, seed, image=None, blended_image=None, mask=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None + vae = pipe["vae"] + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + + method = self.get_layer_diffusion_method(method, blend_samples is not None or blended_image is not None) + + if image is not None or "image" in pipe: + image = image if image is not None else pipe['image'] + if mask is not None: + print('inpaint') + samples, = VAEEncodeForInpaint().encode(vae, image, mask) + else: + samples = {"samples": vae.encode(image[:,:,:,:3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + images = image + elif "samp_images" in pipe: + samples = {"samples": vae.encode(pipe["samp_images"][:,:,:,:3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + images = pipe["samp_images"] + else: + if method not in [LayerMethod.FG_ONLY_ATTN, LayerMethod.FG_ONLY_CONV, LayerMethod.EVERYTHING]: + raise Exception("image is missing") + + samples = pipe["samples"] + images = pipe["images"] + + if method in [LayerMethod.BG_BLEND_TO_FG, LayerMethod.FG_BLEND_TO_BG]: + if blended_image is None and blend_samples is None: + raise Exception("blended_image is missing") + elif blended_image is not None: + blend_samples = {"samples": vae.encode(blended_image[:,:,:,:3])} + blend_samples = RepeatLatentBatch().repeat(blend_samples, batch_size)[0] + + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": samples, + "blend_samples": blend_samples, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "add_noise": "enabled", + "layer_diffusion_method": method, + "layer_diffusion_weight": weight, + } + } + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + +# 预采样设置(layerDiffuse附加) +class layerDiffusionSettingsADDTL: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + { + "pipe": ("PIPE_LINE",), + "foreground_prompt": ("STRING", {"default": "", "placeholder": "Foreground Additional Prompt", "multiline": True}), + "background_prompt": ("STRING", {"default": "", "placeholder": "Background Additional Prompt", "multiline": True}), + "blended_prompt": ("STRING", {"default": "", "placeholder": "Blended Additional Prompt", "multiline": True}), + }, + "optional": { + "optional_fg_cond": ("CONDITIONING",), + "optional_bg_cond": ("CONDITIONING",), + "optional_blended_cond": ("CONDITIONING",), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def settings(self, pipe, foreground_prompt, background_prompt, blended_prompt, optional_fg_cond=None, optional_bg_cond=None, optional_blended_cond=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + fg_cond, bg_cond, blended_cond = None, None, None + clip = pipe['clip'] + if optional_fg_cond is not None: + fg_cond = optional_fg_cond + elif foreground_prompt != "": + fg_cond, = CLIPTextEncode().encode(clip, foreground_prompt) + if optional_bg_cond is not None: + bg_cond = optional_bg_cond + elif background_prompt != "": + bg_cond, = CLIPTextEncode().encode(clip, background_prompt) + if optional_blended_cond is not None: + blended_cond = optional_blended_cond + elif blended_prompt != "": + blended_cond, = CLIPTextEncode().encode(clip, blended_prompt) + + new_pipe = { + **pipe, + "loader_settings": { + **pipe["loader_settings"], + "layer_diffusion_cond": (fg_cond, bg_cond, blended_cond) + } + } + + del pipe + + return (new_pipe,) + +# 预采样设置(动态CFG) +from .libs.dynthres_core import DynThresh +class dynamicCFGSettings: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "cfg_mode": (DynThresh.Modes,), + "cfg_scale_min": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 100.0, "step": 0.5}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS+new_schedulers,), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + "optional":{ + "image_to_latent": ("IMAGE",), + "latent": ("LATENT",) + }, + "hidden": + {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + + FUNCTION = "settings" + CATEGORY = "EasyUse/PreSampling" + + def settings(self, pipe, steps, cfg, cfg_mode, cfg_scale_min,sampler_name, scheduler, denoise, seed, image_to_latent=None, latent=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + + + dynamic_thresh = DynThresh(7.0, 1.0,"CONSTANT", 0, cfg_mode, cfg_scale_min, 0, 0, 999, False, + "MEAN", "AD", 1) + + def sampler_dyn_thresh(args): + input = args["input"] + cond = input - args["cond"] + uncond = input - args["uncond"] + cond_scale = args["cond_scale"] + time_step = args["timestep"] + dynamic_thresh.step = 999 - time_step[0] + + return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None) + + model = pipe['model'] + + m = model.clone() + m.set_model_sampler_cfg_function(sampler_dyn_thresh) + + # 图生图转换 + vae = pipe["vae"] + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + if image_to_latent is not None: + samples = {"samples": vae.encode(image_to_latent[:, :, :, :3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + images = image_to_latent + elif latent is not None: + samples = RepeatLatentBatch().repeat(latent, batch_size)[0] + images = pipe["images"] + else: + samples = pipe["samples"] + images = pipe["images"] + + new_pipe = { + "model": m, + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": pipe['vae'], + "clip": pipe['clip'], + + "samples": samples, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise + }, + } + + del pipe + + return {"ui": {"value": [seed]}, "result": (new_pipe,)} + +# 动态CFG +class dynamicThresholdingFull: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model": ("MODEL",), + "mimic_scale": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "threshold_percentile": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "mimic_mode": (DynThresh.Modes,), + "mimic_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "cfg_mode": (DynThresh.Modes,), + "cfg_scale_min": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100.0, "step": 0.5}), + "sched_val": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step": 0.01}), + "separate_feature_channels": (["enable", "disable"],), + "scaling_startpoint": (DynThresh.Startpoints,), + "variability_measure": (DynThresh.Variabilities,), + "interpolate_phi": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + } + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "patch" + CATEGORY = "EasyUse/PreSampling" + + def patch(self, model, mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, cfg_scale_min, + sched_val, separate_feature_channels, scaling_startpoint, variability_measure, interpolate_phi): + dynamic_thresh = DynThresh(mimic_scale, threshold_percentile, mimic_mode, mimic_scale_min, cfg_mode, + cfg_scale_min, sched_val, 0, 999, separate_feature_channels == "enable", + scaling_startpoint, variability_measure, interpolate_phi) + + def sampler_dyn_thresh(args): + input = args["input"] + cond = input - args["cond"] + uncond = input - args["uncond"] + cond_scale = args["cond_scale"] + time_step = args["timestep"] + dynamic_thresh.step = 999 - time_step[0] + + return input - dynamic_thresh.dynthresh(cond, uncond, cond_scale, None) + + m = model.clone() + m.set_model_sampler_cfg_function(sampler_dyn_thresh) + return (m,) + +#---------------------------------------------------------------预采样参数 结束---------------------------------------------------------------------- + +#---------------------------------------------------------------采样器 开始---------------------------------------------------------------------- + +# 完整采样器 +from .libs.chooser import ChooserMessage, ChooserCancelled +class samplerFull: + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS+new_schedulers,), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + "model": ("MODEL",), + "positive": ("CONDITIONING",), + "negative": ("CONDITIONING",), + "latent": ("LATENT",), + "vae": ("VAE",), + "clip": ("CLIP",), + "xyPlot": ("XYPLOT",), + "image": ("IMAGE",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "INT",) + RETURN_NAMES = ("pipe", "image", "model", "positive", "negative", "latent", "vae", "clip", "seed",) + OUTPUT_NODE = True + FUNCTION = "run" + CATEGORY = "EasyUse/Sampler" + + def ip2p(self, positive, negative, vae=None, pixels=None, latent=None): + if latent is not None: + concat_latent = latent + else: + x = (pixels.shape[1] // 8) * 8 + y = (pixels.shape[2] // 8) * 8 + + if pixels.shape[1] != x or pixels.shape[2] != y: + x_offset = (pixels.shape[1] % 8) // 2 + y_offset = (pixels.shape[2] % 8) // 2 + pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] + + concat_latent = vae.encode(pixels) + + out_latent = {} + out_latent["samples"] = torch.zeros_like(concat_latent) + + out = [] + for conditioning in [positive, negative]: + c = [] + for t in conditioning: + d = t[1].copy() + d["concat_latent_image"] = concat_latent + n = [t[0], d] + c.append(n) + out.append(c) + return (out[0], out[1], out_latent) + + def get_inversed_euler_sampler(self): + @torch.no_grad() + def sample_inversed_euler(model, x, sigmas, extra_args=None, callback=None, disable=None, s_churn=0., s_tmin=0.,s_tmax=float('inf'), s_noise=1.): + """Implements Algorithm 2 (Euler steps) from Karras et al. (2022).""" + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + for i in trange(1, len(sigmas), disable=disable): + sigma_in = sigmas[i - 1] + + if i == 1: + sigma_t = sigmas[i] + else: + sigma_t = sigma_in + + denoised = model(x, sigma_t * s_in, **extra_args) + + if i == 1: + d = (x - denoised) / (2 * sigmas[i]) + else: + d = (x - denoised) / sigmas[i - 1] + + dt = sigmas[i] - sigmas[i - 1] + x = x + d * dt + if callback is not None: + callback( + {'x': x, 'i': i, 'sigma': sigmas[i], 'sigma_hat': sigmas[i], 'denoised': denoised}) + return x / sigmas[-1] + + ksampler = comfy.samplers.KSAMPLER(sample_inversed_euler) + return (ksampler,) + + def get_custom_cls(self, sampler_name): + try: + cls = custom_samplers.__dict__[sampler_name] + return cls() + except: + raise Exception(f"Custom sampler {sampler_name} not found, Please updated your ComfyUI") + + def add_model_patch_option(self, model): + if 'transformer_options' not in model.model_options: + model.model_options['transformer_options'] = {} + to = model.model_options['transformer_options'] + if "model_patch" not in to: + to["model_patch"] = {} + return to + + def get_sampler_custom(self, model, positive, negative, loader_settings): + _guider = None + middle = loader_settings['middle'] if "middle" in loader_settings else negative + steps = loader_settings['steps'] if "steps" in loader_settings else 20 + cfg = loader_settings['cfg'] if "cfg" in loader_settings else 8.0 + cfg_negative = loader_settings['cfg_negative'] if "cfg_negative" in loader_settings else 8.0 + sampler_name = loader_settings['sampler_name'] if "sampler_name" in loader_settings else "euler" + scheduler = loader_settings['scheduler'] if "scheduler" in loader_settings else "normal" + guider = loader_settings['custom']['guider'] if "guider" in loader_settings['custom'] else "CFG" + beta_d = loader_settings['custom']['beta_d'] if "beta_d" in loader_settings['custom'] else 0.1 + beta_min = loader_settings['custom']['beta_min'] if "beta_min" in loader_settings['custom'] else 0.1 + eps_s = loader_settings['custom']['eps_s'] if "eps_s" in loader_settings['custom'] else 0.1 + sigma_max = loader_settings['custom']['sigma_max'] if "sigma_max" in loader_settings['custom'] else 14.61 + sigma_min = loader_settings['custom']['sigma_min'] if "sigma_min" in loader_settings['custom'] else 0.03 + rho = loader_settings['custom']['rho'] if "rho" in loader_settings['custom'] else 7.0 + coeff = loader_settings['custom']['coeff'] if "coeff" in loader_settings['custom'] else 1.2 + flip_sigmas = loader_settings['custom']['flip_sigmas'] if "flip_sigmas" in loader_settings['custom'] else False + denoise = loader_settings['denoise'] if "denoise" in loader_settings else 1.0 + optional_sigmas = loader_settings['optional_sigmas'] if "optional_sigmas" in loader_settings else None + optional_sampler = loader_settings['optional_sampler'] if "optional_sampler" in loader_settings else None + + # sigmas + if optional_sigmas is not None: + sigmas = optional_sigmas + else: + if scheduler == 'vp': + sigmas, = self.get_custom_cls('VPScheduler').get_sigmas(steps, beta_d, beta_min, eps_s) + elif scheduler == 'karrasADV': + sigmas, = self.get_custom_cls('KarrasScheduler').get_sigmas(steps, sigma_max, sigma_min, rho) + elif scheduler == 'exponentialADV': + sigmas, = self.get_custom_cls('ExponentialScheduler').get_sigmas(steps, sigma_max, sigma_min) + elif scheduler == 'polyExponential': + sigmas, = self.get_custom_cls('PolyexponentialScheduler').get_sigmas(steps, sigma_max, sigma_min, rho) + elif scheduler == 'sdturbo': + sigmas, = self.get_custom_cls('SDTurboScheduler').get_sigmas(model, steps, denoise) + elif scheduler == 'alignYourSteps': + model_type = get_sd_version(model) + if model_type == 'unknown': + model_type = 'sdxl' + sigmas, = alignYourStepsScheduler().get_sigmas(model_type.upper(), steps, denoise) + elif scheduler == 'gits': + sigmas, = gitsScheduler().get_sigmas(coeff, steps, denoise) + else: + sigmas, = self.get_custom_cls('BasicScheduler').get_sigmas(model, scheduler, steps, denoise) + + # filp_sigmas + if flip_sigmas: + sigmas, = self.get_custom_cls('FlipSigmas').get_sigmas(sigmas) + + ####################################################################################### + # brushnet + to = None + transformer_options = model.model_options['transformer_options'] if "transformer_options" in model.model_options else {} + if 'model_patch' in transformer_options and 'brushnet' in transformer_options['model_patch']: + to = self.add_model_patch_option(model) + mp = to['model_patch'] + if isinstance(model.model.model_config, comfy.supported_models.SD15): + mp['SDXL'] = False + elif isinstance(model.model.model_config, comfy.supported_models.SDXL): + mp['SDXL'] = True + else: + print('Base model type: ', type(model.model.model_config)) + raise Exception("Unsupported model type: ", type(model.model.model_config)) + + mp['all_sigmas'] = sigmas + mp['unet'] = model.model.diffusion_model + mp['step'] = 0 + mp['total_steps'] = 1 + ####################################################################################### + # guider + if cfg > 0 and get_sd_version(model) == 'flux': + c = [] + for t in positive: + n = [t[0], t[1]] + n[1]['guidance'] = cfg + c.append(n) + positive = c + + if guider in ['CFG', 'IP2P+CFG']: + _guider, = self.get_custom_cls('CFGGuider').get_guider(model, positive, negative, cfg) + elif guider in ['DualCFG', 'IP2P+DualCFG']: + _guider, = self.get_custom_cls('DualCFGGuider').get_guider(model, positive, middle, + negative, cfg, cfg_negative) + else: + _guider, = self.get_custom_cls('BasicGuider').get_guider(model, positive) + + # sampler + if optional_sampler: + _sampler = optional_sampler + else: + if sampler_name == 'inversed_euler': + _sampler, = self.get_inversed_euler_sampler() + else: + _sampler, = self.get_custom_cls('KSamplerSelect').get_sampler(sampler_name) + + + return (_guider, _sampler, sigmas) + + def run(self, pipe, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed=None, model=None, positive=None, negative=None, latent=None, vae=None, clip=None, xyPlot=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False, downscale_options=None, image=None): + + samp_model = model if model is not None else pipe["model"] + samp_positive = positive if positive is not None else pipe["positive"] + samp_negative = negative if negative is not None else pipe["negative"] + samp_samples = latent if latent is not None else pipe["samples"] + samp_vae = vae if vae is not None else pipe["vae"] + samp_clip = clip if clip is not None else pipe["clip"] + + samp_seed = seed if seed is not None else pipe['seed'] + + samp_custom = pipe["loader_settings"] if "custom" in pipe["loader_settings"] else None + + steps = steps if steps is not None else pipe['loader_settings']['steps'] + start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0 + last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000 + cfg = cfg if cfg is not None else pipe['loader_settings']['cfg'] + sampler_name = sampler_name if sampler_name is not None else pipe['loader_settings']['sampler_name'] + scheduler = scheduler if scheduler is not None else pipe['loader_settings']['scheduler'] + denoise = denoise if denoise is not None else pipe['loader_settings']['denoise'] + add_noise = pipe['loader_settings']['add_noise'] if 'add_noise' in pipe['loader_settings'] else 'enabled' + force_full_denoise = pipe['loader_settings']['force_full_denoise'] if 'force_full_denoise' in pipe['loader_settings'] else True + noise_device = 'GPU' if ('a1111_prompt_style' in pipe['loader_settings'] and pipe['loader_settings']['a1111_prompt_style']) or add_noise == 'enable (GPU=A1111)' else 'CPU' + + if image is not None and latent is None: + samp_samples = {"samples": samp_vae.encode(image[:, :, :, :3])} + + disable_noise = False + if add_noise == "disable": + disable_noise = True + + def downscale_model_unet(samp_model): + # 获取Unet参数 + if "PatchModelAddDownscale" in ALL_NODE_CLASS_MAPPINGS: + cls = ALL_NODE_CLASS_MAPPINGS['PatchModelAddDownscale'] + # 自动收缩Unet + if downscale_options['downscale_factor'] is None: + unet_config = samp_model.model.model_config.unet_config + if unet_config is not None and "samples" in samp_samples: + height = samp_samples['samples'].shape[2] * 8 + width = samp_samples['samples'].shape[3] * 8 + context_dim = unet_config.get('context_dim') + longer_side = width if width > height else height + if context_dim is not None and longer_side > context_dim: + width_downscale_factor = float(width / context_dim) + height_downscale_factor = float(height / context_dim) + if width_downscale_factor > 1.75: + log_node_warn("Patch model unet add downscale...") + log_node_warn("Downscale factor:" + str(width_downscale_factor)) + (samp_model,) = cls().patch(samp_model, downscale_options['block_number'], width_downscale_factor, 0, 0.35, True, "bicubic", + "bicubic") + elif height_downscale_factor > 1.25: + log_node_warn("Patch model unet add downscale....") + log_node_warn("Downscale factor:" + str(height_downscale_factor)) + (samp_model,) = cls().patch(samp_model, downscale_options['block_number'], height_downscale_factor, 0, 0.35, True, "bicubic", + "bicubic") + else: + cls = ALL_NODE_CLASS_MAPPINGS['PatchModelAddDownscale'] + log_node_warn("Patch model unet add downscale....") + log_node_warn("Downscale factor:" + str(downscale_options['downscale_factor'])) + (samp_model,) = cls().patch(samp_model, downscale_options['block_number'], downscale_options['downscale_factor'], downscale_options['start_percent'], downscale_options['end_percent'], downscale_options['downscale_after_skip'], downscale_options['downscale_method'], downscale_options['upscale_method']) + return samp_model + + def process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, + samp_negative, + steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, + image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, + preview_latent, force_full_denoise=force_full_denoise, disable_noise=disable_noise, samp_custom=None, noise_device='cpu'): + + # LayerDiffusion + layerDiffuse = None + samp_blend_samples = None + layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None + if layer_diffusion_method is not None: + layerDiffuse = LayerDiffuse() + samp_blend_samples = pipe["blend_samples"] if "blend_samples" in pipe else None + additional_cond = pipe["loader_settings"]['layer_diffusion_cond'] if "layer_diffusion_cond" in pipe[ + 'loader_settings'] else (None, None, None) + method = layerDiffuse.get_layer_diffusion_method(pipe['loader_settings']['layer_diffusion_method'], + samp_blend_samples is not None) + + images = pipe["images"] if "images" in pipe else None + weight = pipe['loader_settings']['layer_diffusion_weight'] if 'layer_diffusion_weight' in pipe[ + 'loader_settings'] else 1.0 + samp_model, samp_positive, samp_negative = layerDiffuse.apply_layer_diffusion(samp_model, method, weight, + samp_samples, samp_blend_samples, + samp_positive, samp_negative, + images, additional_cond) + resolution = pipe['loader_settings']['resolution'] if 'resolution' in pipe['loader_settings'] else "自定义 X 自定义" + empty_latent_width = pipe['loader_settings']['empty_latent_width'] if 'empty_latent_width' in pipe['loader_settings'] else 512 + empty_latent_height = pipe['loader_settings']['empty_latent_height'] if 'empty_latent_height' in pipe['loader_settings'] else 512 + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + samp_samples = sampler.emptyLatent(resolution, empty_latent_width, empty_latent_height, batch_size) + + # Downscale Model Unet + if samp_model is not None and downscale_options is not None: + samp_model = downscale_model_unet(samp_model) + # 推理初始时间 + start_time = int(time.time() * 1000) + # 开始推理 + if samp_custom is not None: + _guider, _sampler, sigmas = self.get_sampler_custom(samp_model, samp_positive, samp_negative, samp_custom) + samp_samples, samp_blend_samples = sampler.custom_advanced_ksampler(_guider, _sampler, sigmas, samp_samples, add_noise, samp_seed, preview_latent=preview_latent) + elif scheduler == 'align_your_steps': + model_type = get_sd_version(samp_model) + if model_type == 'unknown': + model_type = 'sdxl' + sigmas, = alignYourStepsScheduler().get_sigmas(model_type.upper(), steps, denoise) + _sampler = comfy.samplers.sampler_object(sampler_name) + samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, _sampler, sigmas, samp_positive, samp_negative, samp_samples, disable_noise=disable_noise, preview_latent=preview_latent, noise_device=noise_device) + elif scheduler == 'gits': + sigmas, = gitsScheduler().get_sigmas(coeff=1.2, steps=steps, denoise=denoise) + _sampler = comfy.samplers.sampler_object(sampler_name) + samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, _sampler, sigmas, samp_positive, samp_negative, samp_samples, disable_noise=disable_noise, preview_latent=preview_latent, noise_device=noise_device) + else: + samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, samp_positive, samp_negative, samp_samples, denoise=denoise, preview_latent=preview_latent, start_step=start_step, last_step=last_step, force_full_denoise=force_full_denoise, disable_noise=disable_noise, noise_device=noise_device) + # 推理结束时间 + end_time = int(time.time() * 1000) + latent = samp_samples["samples"] + + # 解码图片 + if image_output == 'None': + samp_images, new_images, alpha, results = None, None, None, None + spent_time = 'Diffusion:' + str((end_time - start_time) / 1000) + '″' + else: + if tile_size is not None: + samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, ) + else: + samp_images = samp_vae.decode(latent).cpu() + if len(samp_images.shape) == 5: # Combine batches + samp_images = samp_images.reshape(-1, samp_images.shape[-3], samp_images.shape[-2], samp_images.shape[-1]) + # LayerDiffusion Decode + if layerDiffuse is not None: + new_images, samp_images, alpha = layerDiffuse.layer_diffusion_decode(layer_diffusion_method, latent, samp_blend_samples, samp_images, samp_model) + else: + new_images = samp_images + alpha = None + + # 推理总耗时(包含解码) + end_decode_time = int(time.time() * 1000) + spent_time = 'Diffusion:' + str((end_time-start_time)/1000)+'″, VAEDecode:' + str((end_decode_time-end_time)/1000)+'″ ' + + results = easySave(new_images, save_prefix, image_output, prompt, extra_pnginfo) + + new_pipe = { + **pipe, + "positive": samp_positive, + "negative": samp_negative, + "vae": samp_vae, + "clip": samp_clip, + + "samples": samp_samples, + "blend_samples": samp_blend_samples, + "images": new_images, + "samp_images": samp_images, + "alpha": alpha, + "seed": samp_seed, + + "loader_settings": { + **pipe["loader_settings"], + "spent_time": spent_time + } + } + + del pipe + + if image_output == 'Preview&Choose': + if my_unique_id not in ChooserMessage.stash: + ChooserMessage.stash[my_unique_id] = {} + my_stash = ChooserMessage.stash[my_unique_id] + + PromptServer.instance.send_sync("easyuse-image-choose", {"id": my_unique_id, "urls": results}) + # wait for selection + try: + selections = ChooserMessage.waitForMessage(my_unique_id, asList=True) + samples = samp_samples['samples'] + samples = [samples[x] for x in selections if x >= 0] if len(selections) > 1 else [samples[0]] + new_images = [new_images[x] for x in selections if x >= 0] if len(selections) > 1 else [new_images[0]] + samp_images = [samp_images[x] for x in selections if x >= 0] if len(selections) > 1 else [samp_images[0]] + new_images = torch.stack(new_images, dim=0) + samp_images = torch.stack(samp_images, dim=0) + samples = torch.stack(samples, dim=0) + samp_samples = {"samples": samples} + new_pipe['samples'] = samp_samples + new_pipe['loader_settings']['batch_size'] = len(new_images) + except ChooserCancelled: + raise comfy.model_management.InterruptProcessingException() + + new_pipe['images'] = new_images + new_pipe['samp_images'] = samp_images + + return {"ui": {"images": results}, + "result": sampler.get_output(new_pipe,)} + + if image_output in ("Hide", "Hide&Save", "None"): + return {"ui":{}, "result":sampler.get_output(new_pipe,)} + + if image_output in ("Sender", "Sender&Save"): + PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) + + return {"ui": {"images": results}, + "result": sampler.get_output(new_pipe,)} + + def process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, + steps, cfg, sampler_name, scheduler, denoise, + image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom, noise_device): + + sampleXYplot = easyXYPlot(xyPlot, save_prefix, image_output, prompt, extra_pnginfo, my_unique_id, sampler, easyCache) + + if not sampleXYplot.validate_xy_plot(): + return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, + samp_negative, steps, 0, 10000, cfg, + sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, + extra_pnginfo, my_unique_id, preview_latent, samp_custom=samp_custom, noise_device=noise_device) + + # Downscale Model Unet + if samp_model is not None and downscale_options is not None: + samp_model = downscale_model_unet(samp_model) + + blend_samples = pipe['blend_samples'] if "blend_samples" in pipe else None + layer_diffusion_method = pipe['loader_settings']['layer_diffusion_method'] if 'layer_diffusion_method' in pipe['loader_settings'] else None + + plot_image_vars = { + "x_node_type": sampleXYplot.x_node_type, "y_node_type": sampleXYplot.y_node_type, + "lora_name": pipe["loader_settings"]["lora_name"] if "lora_name" in pipe["loader_settings"] else None, + "lora_model_strength": pipe["loader_settings"]["lora_model_strength"] if "model_strength" in pipe["loader_settings"] else None, + "lora_clip_strength": pipe["loader_settings"]["lora_clip_strength"] if "clip_strength" in pipe["loader_settings"] else None, + "lora_stack": pipe["loader_settings"]["lora_stack"] if "lora_stack" in pipe["loader_settings"] else None, + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "seed": samp_seed, + "images": pipe['images'], + + "model": samp_model, "vae": samp_vae, "clip": samp_clip, "positive_cond": samp_positive, + "negative_cond": samp_negative, + "noise_device":noise_device, + + "ckpt_name": pipe['loader_settings']['ckpt_name'] if "ckpt_name" in pipe["loader_settings"] else None, + "vae_name": pipe['loader_settings']['vae_name'] if "vae_name" in pipe["loader_settings"] else None, + "clip_skip": pipe['loader_settings']['clip_skip'] if "clip_skip" in pipe["loader_settings"] else None, + "positive": pipe['loader_settings']['positive'] if "positive" in pipe["loader_settings"] else None, + "positive_token_normalization": pipe['loader_settings']['positive_token_normalization'] if "positive_token_normalization" in pipe["loader_settings"] else None, + "positive_weight_interpretation": pipe['loader_settings']['positive_weight_interpretation'] if "positive_weight_interpretation" in pipe["loader_settings"] else None, + "negative": pipe['loader_settings']['negative'] if "negative" in pipe["loader_settings"] else None, + "negative_token_normalization": pipe['loader_settings']['negative_token_normalization'] if "negative_token_normalization" in pipe["loader_settings"] else None, + "negative_weight_interpretation": pipe['loader_settings']['negative_weight_interpretation'] if "negative_weight_interpretation" in pipe["loader_settings"] else None, + } + + if "models" in pipe["loader_settings"]: + plot_image_vars["models"] = pipe["loader_settings"]["models"] + if "vae_use" in pipe["loader_settings"]: + plot_image_vars["vae_use"] = pipe["loader_settings"]["vae_use"] + if "a1111_prompt_style" in pipe["loader_settings"]: + plot_image_vars["a1111_prompt_style"] = pipe["loader_settings"]["a1111_prompt_style"] + if "cnet_stack" in pipe["loader_settings"]: + plot_image_vars["cnet"] = pipe["loader_settings"]["cnet_stack"] + if "positive_cond_stack" in pipe["loader_settings"]: + plot_image_vars["positive_cond_stack"] = pipe["loader_settings"]["positive_cond_stack"] + if "negative_cond_stack" in pipe["loader_settings"]: + plot_image_vars["negative_cond_stack"] = pipe["loader_settings"]["negative_cond_stack"] + if layer_diffusion_method: + plot_image_vars["layer_diffusion_method"] = layer_diffusion_method + if "layer_diffusion_weight" in pipe["loader_settings"]: + plot_image_vars["layer_diffusion_weight"] = pipe['loader_settings']['layer_diffusion_weight'] + if "layer_diffusion_cond" in pipe["loader_settings"]: + plot_image_vars["layer_diffusion_cond"] = pipe['loader_settings']['layer_diffusion_cond'] + if "empty_samples" in pipe["loader_settings"]: + plot_image_vars["empty_samples"] = pipe["loader_settings"]['empty_samples'] + + latent_image = sampleXYplot.get_latent(pipe["samples"]) + latents_plot = sampleXYplot.get_labels_and_sample(plot_image_vars, latent_image, preview_latent, start_step, + last_step, force_full_denoise, disable_noise) + + samp_samples = {"samples": latents_plot} + + images, image_list = sampleXYplot.plot_images_and_labels() + + # Generate output_images + output_images = torch.stack([tensor.squeeze() for tensor in image_list]) + + if layer_diffusion_method is not None: + layerDiffuse = LayerDiffuse() + new_images, samp_images, alpha = layerDiffuse.layer_diffusion_decode(layer_diffusion_method, latents_plot, blend_samples, + output_images, samp_model) + else: + new_images = output_images + samp_images = output_images + alpha = None + + results = easySave(images, save_prefix, image_output, prompt, extra_pnginfo) + + new_pipe = { + **pipe, + "positive": samp_positive, + "negative": samp_negative, + "vae": samp_vae, + "clip": samp_clip, + + "samples": samp_samples, + "blend_samples": blend_samples, + "samp_images": samp_images, + "images": new_images, + "seed": samp_seed, + "alpha": alpha, + + "loader_settings": pipe["loader_settings"], + } + + del pipe + + if image_output in ("Hide", "Hide&Save", "None"): + return {"ui": {}, "result": sampler.get_output(new_pipe,)} + + return {"ui": {"images": results}, "result": sampler.get_output(new_pipe)} + + preview_latent = True + if image_output in ("Hide", "Hide&Save", "None"): + preview_latent = False + + xyplot_id = next((x for x in prompt if "XYPlot" in str(prompt[x]["class_type"])), None) + if xyplot_id is None: + xyPlot = None + else: + xyPlot = pipe["loader_settings"]["xyplot"] if "xyplot" in pipe["loader_settings"] else xyPlot + + # Fooocus model patch + model_options = samp_model.model_options if samp_model.model_options else samp_model.model.model_options + transformer_options = model_options["transformer_options"] if "transformer_options" in model_options else {} + if "fooocus" in transformer_options: + from .fooocus import applyFooocusInpaint + del transformer_options["fooocus"] + with applyFooocusInpaint(): + if xyPlot is not None: + return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, + samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, + link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, + preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom, noise_device) + else: + return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, + samp_positive, samp_negative, steps, start_step, last_step, cfg, + sampler_name, scheduler, denoise, image_output, link_id, save_prefix, + tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, + force_full_denoise, disable_noise, samp_custom, noise_device) + else: + if xyPlot is not None: + return process_xyPlot(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, xyPlot, force_full_denoise, disable_noise, samp_custom, noise_device) + else: + return process_sample_state(pipe, samp_model, samp_clip, samp_samples, samp_vae, samp_seed, samp_positive, samp_negative, steps, start_step, last_step, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, tile_size, prompt, extra_pnginfo, my_unique_id, preview_latent, force_full_denoise, disable_noise, samp_custom, noise_device) + +# 简易采样器 +class samplerSimple(samplerFull): + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + + RETURN_TYPES = ("PIPE_LINE", "IMAGE",) + RETURN_NAMES = ("pipe", "image",) + OUTPUT_NODE = True + FUNCTION = "simple" + CATEGORY = "EasyUse/Sampler" + + def simple(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + + return super().run(pipe, None, None, None, None, None, image_output, link_id, save_prefix, + None, model, None, None, None, None, None, None, + None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) + +class samplerSimpleCustom(samplerFull): + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "image_output": (["Hide", "Preview", "Preview&Choose", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "None"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + + RETURN_TYPES = ("PIPE_LINE", "LATENT", "LATENT", "IMAGE") + RETURN_NAMES = ("pipe", "output", "denoised_output", "image") + OUTPUT_NODE = True + FUNCTION = "simple" + CATEGORY = "EasyUse/Sampler" + + def simple(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + + result = super().run(pipe, None, None, None, None, None, image_output, link_id, save_prefix, + None, model, None, None, None, None, None, None, + None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) + + pipe = result["result"][0] if "result" in result else None + + return ({"ui": result['ui'], "result": (pipe, pipe["samples"], pipe["blend_samples"], pipe["images"])}) + +# 简易采样器 (Tiled) +class samplerSimpleTiled(samplerFull): + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "tile_size": ("INT", {"default": 512, "min": 320, "max": 4096, "step": 64}), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save", "None"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}) + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE",) + RETURN_NAMES = ("pipe", "image",) + OUTPUT_NODE = True + FUNCTION = "tiled" + CATEGORY = "EasyUse/Sampler" + + def tiled(self, pipe, tile_size=512, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + + return super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, + None, model, None, None, None, None, None, None, + tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) + +# 简易采样器 (LayerDiffusion) +class samplerSimpleLayerDiffusion(samplerFull): + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"], {"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}) + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": { + "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "MASK") + RETURN_NAMES = ("pipe", "final_image", "original_image", "alpha") + OUTPUT_NODE = True + OUTPUT_IS_LIST = (False, False, False, True) + FUNCTION = "layerDiffusion" + CATEGORY = "EasyUse/Sampler" + + def layerDiffusion(self, pipe, image_output='preview', link_id=0, save_prefix='ComfyUI', model=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + + result = super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, + None, model, None, None, None, None, None, None, + None, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) + pipe = result["result"][0] if "result" in result else None + return ({"ui":result['ui'], "result":(pipe, pipe["images"], pipe["samp_images"], pipe["alpha"])}) + +# 简易采样器(收缩Unet) +class samplerSimpleDownscaleUnet(samplerFull): + + upscale_methods = ["bicubic", "nearest-exact", "bilinear", "area", "bislerp"] + + @classmethod + def INPUT_TYPES(s): + return {"required": + {"pipe": ("PIPE_LINE",), + "downscale_mode": (["None", "Auto", "Custom"],{"default": "Auto"}), + "block_number": ("INT", {"default": 3, "min": 1, "max": 32, "step": 1}), + "downscale_factor": ("FLOAT", {"default": 2.0, "min": 0.1, "max": 9.0, "step": 0.001}), + "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "end_percent": ("FLOAT", {"default": 0.35, "min": 0.0, "max": 1.0, "step": 0.001}), + "downscale_after_skip": ("BOOLEAN", {"default": True}), + "downscale_method": (s.upscale_methods,), + "upscale_method": (s.upscale_methods,), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + + RETURN_TYPES = ("PIPE_LINE", "IMAGE",) + RETURN_NAMES = ("pipe", "image",) + OUTPUT_NODE = True + FUNCTION = "downscale_unet" + CATEGORY = "EasyUse/Sampler" + + def downscale_unet(self, pipe, downscale_mode, block_number, downscale_factor, start_percent, end_percent, downscale_after_skip, downscale_method, upscale_method, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + downscale_options = None + if downscale_mode == 'Auto': + downscale_options = { + "block_number": block_number, + "downscale_factor": None, + "start_percent": 0, + "end_percent":0.35, + "downscale_after_skip": True, + "downscale_method": "bicubic", + "upscale_method": "bicubic" + } + elif downscale_mode == 'Custom': + downscale_options = { + "block_number": block_number, + "downscale_factor": downscale_factor, + "start_percent": start_percent, + "end_percent": end_percent, + "downscale_after_skip": downscale_after_skip, + "downscale_method": downscale_method, + "upscale_method": upscale_method + } + + return super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, + None, model, None, None, None, None, None, None, + tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise, downscale_options) +# 简易采样器 (内补) +class samplerSimpleInpainting(samplerFull): + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + "additional": (["None", "InpaintModelCond", "Differential Diffusion", "Fooocus Inpaint", "Fooocus Inpaint + DD", "Brushnet Random", "Brushnet Random + DD", "Brushnet Segmentation", "Brushnet Segmentation + DD"],{"default": "None"}) + }, + "optional": { + "model": ("MODEL",), + "mask": ("MASK",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE", "VAE") + RETURN_NAMES = ("pipe", "image", "vae") + OUTPUT_NODE = True + FUNCTION = "inpainting" + CATEGORY = "EasyUse/Sampler" + + def dd(self, model, positive, negative, pixels, vae, mask): + positive, negative, latent = InpaintModelConditioning().encode(positive, negative, pixels, vae, mask, noise_mask=True) + cls = ALL_NODE_CLASS_MAPPINGS['DifferentialDiffusion'] + if cls is not None: + model, = cls().apply(model) + else: + raise Exception("Differential Diffusion not found,please update comfyui") + return positive, negative, latent, model + + def get_brushnet_model(self, type, model): + model_type = 'sdxl' if isinstance(model.model.model_config, comfy.supported_models.SDXL) else 'sd1' + if type == 'random': + brush_model = BRUSHNET_MODELS['random_mask'][model_type]['model_url'] + if model_type == 'sdxl': + pattern = 'brushnet.random.mask.sdxl.*.(safetensors|bin)$' + else: + pattern = 'brushnet.random.mask.*.(safetensors|bin)$' + elif type == 'segmentation': + brush_model = BRUSHNET_MODELS['segmentation_mask'][model_type]['model_url'] + if model_type == 'sdxl': + pattern = 'brushnet.segmentation.mask.sdxl.*.(safetensors|bin)$' + else: + pattern = 'brushnet.segmentation.mask.*.(safetensors|bin)$' + + + brushfile = [e for e in folder_paths.get_filename_list('inpaint') if re.search(pattern, e, re.IGNORECASE)] + brushname = brushfile[0] if brushfile else None + if not brushname: + from urllib.parse import urlparse + get_local_filepath(brush_model, INPAINT_DIR) + parsed_url = urlparse(brush_model) + brushname = os.path.basename(parsed_url.path) + return brushname + + def apply_brushnet(self, brushname, model, vae, image, mask, positive, negative, scale=1.0, start_at=0, end_at=10000): + if "BrushNetLoader" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception("BrushNetLoader not found,please install ComfyUI-BrushNet") + cls = ALL_NODE_CLASS_MAPPINGS['BrushNetLoader'] + brushnet, = cls().brushnet_loading(brushname, 'float16') + cls = ALL_NODE_CLASS_MAPPINGS['BrushNet'] + m, positive, negative, latent = cls().model_update(model=model, vae=vae, image=image, mask=mask, brushnet=brushnet, positive=positive, negative=negative, scale=scale, start_at=start_at, end_at=end_at) + return m, positive, negative, latent + + def inpainting(self, pipe, grow_mask_by, image_output, link_id, save_prefix, additional, model=None, mask=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + _model = model if model is not None else pipe['model'] + latent = pipe['samples'] if 'samples' in pipe else None + positive = pipe['positive'] + negative = pipe['negative'] + images = pipe["images"] if pipe and "images" in pipe else None + vae = pipe["vae"] if pipe and "vae" in pipe else None + if 'noise_mask' in latent and mask is None: + mask = latent['noise_mask'] + elif mask is not None: + if images is None: + raise Exception("No Images found") + if vae is None: + raise Exception("No VAE found") + + if additional == 'Differential Diffusion': + positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) + elif additional == 'InpaintModelCond': + if mask is not None: + mask, = GrowMask().expand_mask(mask, grow_mask_by, False) + positive, negative, latent = InpaintModelConditioning().encode(positive, negative, images, vae, mask, True) + elif additional == 'Fooocus Inpaint': + head = list(FOOOCUS_INPAINT_HEAD.keys())[0] + patch = list(FOOOCUS_INPAINT_PATCH.keys())[0] + if mask is not None: + latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by) + _model, = applyFooocusInpaint().apply(_model, latent, head, patch) + elif additional == 'Fooocus Inpaint + DD': + head = list(FOOOCUS_INPAINT_HEAD.keys())[0] + patch = list(FOOOCUS_INPAINT_PATCH.keys())[0] + if mask is not None: + latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by) + _model, = applyFooocusInpaint().apply(_model, latent, head, patch) + positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) + elif additional == 'Brushnet Random': + mask, = GrowMask().expand_mask(mask, grow_mask_by, False) + brush_name = self.get_brushnet_model('random', _model) + _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, + negative) + elif additional == 'Brushnet Random + DD': + mask, = GrowMask().expand_mask(mask, grow_mask_by, False) + brush_name = self.get_brushnet_model('random', _model) + _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, + negative) + positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) + elif additional == 'Brushnet Segmentation': + mask, = GrowMask().expand_mask(mask, grow_mask_by, False) + brush_name = self.get_brushnet_model('segmentation', _model) + _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, + negative) + elif additional == 'Brushnet Segmentation + DD': + mask, = GrowMask().expand_mask(mask, grow_mask_by, False) + brush_name = self.get_brushnet_model('segmentation', _model) + _model, positive, negative, latent = self.apply_brushnet(brush_name, _model, vae, images, mask, positive, + negative) + positive, negative, latent, _model = self.dd(_model, positive, negative, images, vae, mask) + else: + latent, = VAEEncodeForInpaint().encode(vae, images, mask, grow_mask_by) + + results = super().run(pipe, None, None,None,None,None, image_output, link_id, save_prefix, + None, _model, positive, negative, latent, vae, None, None, + tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) + + result = results['result'] + + return {"ui":results['ui'],"result":(result[0], result[1], result[0]['vae'],)} + +# SDTurbo采样器 +class samplerSDTurbo: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", + "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE",) + RETURN_NAMES = ("pipe", "image",) + OUTPUT_NODE = True + FUNCTION = "run" + + CATEGORY = "EasyUse/Sampler" + + def run(self, pipe, image_output, link_id, save_prefix, model=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None,): + # Clean loaded_objects + easyCache.update_loaded_objects(prompt) + + my_unique_id = int(my_unique_id) + + samp_model = pipe["model"] if model is None else model + samp_positive = pipe["positive"] + samp_negative = pipe["negative"] + samp_samples = pipe["samples"] + samp_vae = pipe["vae"] + samp_clip = pipe["clip"] + + samp_seed = pipe['seed'] + + samp_sampler = pipe['loader_settings']['sampler'] + + sigmas = pipe['loader_settings']['sigmas'] + cfg = pipe['loader_settings']['cfg'] + steps = pipe['loader_settings']['steps'] + + disable_noise = False + + preview_latent = True + if image_output in ("Hide", "Hide&Save"): + preview_latent = False + + # 推理初始时间 + start_time = int(time.time() * 1000) + # 开始推理 + samp_samples = sampler.custom_ksampler(samp_model, samp_seed, steps, cfg, samp_sampler, sigmas, samp_positive, samp_negative, samp_samples, + disable_noise, preview_latent) + # 推理结束时间 + end_time = int(time.time() * 1000) + + latent = samp_samples['samples'] + + # 解码图片 + if tile_size is not None: + samp_images = samp_vae.decode_tiled(latent, tile_x=tile_size // 8, tile_y=tile_size // 8, ) + else: + samp_images = samp_vae.decode(latent).cpu() + + # 推理总耗时(包含解码) + end_decode_time = int(time.time() * 1000) + spent_time = 'Diffusion:' + str((end_time - start_time) / 1000) + '″, VAEDecode:' + str( + (end_decode_time - end_time) / 1000) + '″ ' + + # Clean loaded_objects + easyCache.update_loaded_objects(prompt) + + results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo) + sampler.update_value_by_id("results", my_unique_id, results) + + new_pipe = { + "model": samp_model, + "positive": samp_positive, + "negative": samp_negative, + "vae": samp_vae, + "clip": samp_clip, + + "samples": samp_samples, + "images": samp_images, + "seed": samp_seed, + + "loader_settings": { + **pipe["loader_settings"], + "spent_time": spent_time + } + } + + sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) + + del pipe + + if image_output in ("Hide", "Hide&Save"): + return {"ui": {}, + "result": sampler.get_output(new_pipe, )} + + if image_output in ("Sender", "Sender&Save"): + PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) + + return {"ui": {"images": results}, + "result": sampler.get_output(new_pipe, )} + + +# Cascade完整采样器 +class samplerCascadeFull: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "encode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), + "decode_vae_name": (["None"] + folder_paths.get_filename_list("vae"),), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 4.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default":"euler_ancestral"}), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default":"simple"}), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + "seed": ("INT", {"default": 0, "min": 0, "max": MAX_SEED_NUM}), + }, + + "optional": { + "image_to_latent_c": ("IMAGE",), + "latent_c": ("LATENT",), + "model_c": ("MODEL",), + }, + "hidden":{"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "LATENT") + RETURN_NAMES = ("pipe", "model_b", "latent_b") + OUTPUT_NODE = True + + FUNCTION = "run" + CATEGORY = "EasyUse/Sampler" + + def run(self, pipe, encode_vae_name, decode_vae_name, steps, cfg, sampler_name, scheduler, denoise, image_output, link_id, save_prefix, seed, image_to_latent_c=None, latent_c=None, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + + encode_vae_name = encode_vae_name if encode_vae_name is not None else pipe['loader_settings']['encode_vae_name'] + decode_vae_name = decode_vae_name if decode_vae_name is not None else pipe['loader_settings']['decode_vae_name'] + + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + if image_to_latent_c is not None: + if encode_vae_name != 'None': + encode_vae = easyCache.load_vae(encode_vae_name) + else: + encode_vae = pipe['vae'][0] + if "compression" not in pipe["loader_settings"]: + raise Exception("compression is not found") + + compression = pipe["loader_settings"]['compression'] + width = image_to_latent_c.shape[-2] + height = image_to_latent_c.shape[-3] + out_width = (width // compression) * encode_vae.downscale_ratio + out_height = (height // compression) * encode_vae.downscale_ratio + + s = comfy.utils.common_upscale(image_to_latent_c.movedim(-1, 1), out_width, out_height, "bicubic", + "center").movedim(1, -1) + latent_c = encode_vae.encode(s[:, :, :, :3]) + latent_b = torch.zeros([latent_c.shape[0], 4, height // 4, width // 4]) + + samples_c = {"samples": latent_c} + samples_c = RepeatLatentBatch().repeat(samples_c, batch_size)[0] + + samples_b = {"samples": latent_b} + samples_b = RepeatLatentBatch().repeat(samples_b, batch_size)[0] + images = image_to_latent_c + elif latent_c is not None: + samples_c = latent_c + samples_b = pipe["samples"][1] + images = pipe["images"] + else: + samples_c = pipe["samples"][0] + samples_b = pipe["samples"][1] + images = pipe["images"] + + # Clean loaded_objects + easyCache.update_loaded_objects(prompt) + samp_model = model_c if model_c else pipe["model"][0] + samp_positive = pipe["positive"] + samp_negative = pipe["negative"] + samp_samples = samples_c + + samp_seed = seed if seed is not None else pipe['seed'] + + steps = steps if steps is not None else pipe['loader_settings']['steps'] + start_step = pipe['loader_settings']['start_step'] if 'start_step' in pipe['loader_settings'] else 0 + last_step = pipe['loader_settings']['last_step'] if 'last_step' in pipe['loader_settings'] else 10000 + cfg = cfg if cfg is not None else pipe['loader_settings']['cfg'] + sampler_name = sampler_name if sampler_name is not None else pipe['loader_settings']['sampler_name'] + scheduler = scheduler if scheduler is not None else pipe['loader_settings']['scheduler'] + denoise = denoise if denoise is not None else pipe['loader_settings']['denoise'] + noise_device = 'gpu' if "a1111_prompt_style" in pipe['loader_settings'] and pipe['loader_settings']['a1111_prompt_style'] else 'cpu' + # 推理初始时间 + start_time = int(time.time() * 1000) + # 开始推理 + samp_samples = sampler.common_ksampler(samp_model, samp_seed, steps, cfg, sampler_name, scheduler, + samp_positive, samp_negative, samp_samples, denoise=denoise, + preview_latent=False, start_step=start_step, + last_step=last_step, force_full_denoise=False, + disable_noise=False, noise_device=noise_device) + # 推理结束时间 + end_time = int(time.time() * 1000) + stage_c = samp_samples["samples"] + results = None + + if image_output not in ['Hide', 'Hide&Save']: + if decode_vae_name != 'None': + decode_vae = easyCache.load_vae(decode_vae_name) + else: + decode_vae = pipe['vae'][0] + samp_images = decode_vae.decode(stage_c).cpu() + + results = easySave(samp_images, save_prefix, image_output, prompt, extra_pnginfo) + sampler.update_value_by_id("results", my_unique_id, results) + + # 推理总耗时(包含解码) + end_decode_time = int(time.time() * 1000) + spent_time = 'Diffusion:' + str((end_time - start_time) / 1000) + '″, VAEDecode:' + str( + (end_decode_time - end_time) / 1000) + '″ ' + + # Clean loaded_objects + easyCache.update_loaded_objects(prompt) + # zero_out + c1 = [] + for t in samp_positive: + d = t[1].copy() + if "pooled_output" in d: + d["pooled_output"] = torch.zeros_like(d["pooled_output"]) + n = [torch.zeros_like(t[0]), d] + c1.append(n) + # stage_b_conditioning + c2 = [] + for t in c1: + d = t[1].copy() + d['stable_cascade_prior'] = stage_c + n = [t[0], d] + c2.append(n) + + + new_pipe = { + "model": pipe['model'][1], + "positive": c2, + "negative": c1, + "vae": pipe['vae'][1], + "clip": pipe['clip'], + + "samples": samples_b, + "images": images, + "seed": seed, + + "loader_settings": { + **pipe["loader_settings"], + "spent_time": spent_time + } + } + sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) + + del pipe + + if image_output in ("Hide", "Hide&Save"): + return {"ui": {}, + "result": sampler.get_output(new_pipe, )} + + if image_output in ("Sender", "Sender&Save") and results is not None: + PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) + + return {"ui": {"images": results}, "result": (new_pipe, new_pipe['model'], new_pipe['samples'])} + +# 简易采样器Cascade +class samplerCascadeSimple(samplerCascadeFull): + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + return {"required": + {"pipe": ("PIPE_LINE",), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"], {"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "model_c": ("MODEL",), + }, + "hidden": + {"tile_size": "INT", "prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + "embeddingsList": (folder_paths.get_filename_list("embeddings"),) + } + } + + + RETURN_TYPES = ("PIPE_LINE", "IMAGE",) + RETURN_NAMES = ("pipe", "image",) + OUTPUT_NODE = True + FUNCTION = "simple" + CATEGORY = "EasyUse/Sampler" + + def simple(self, pipe, image_output, link_id, save_prefix, model_c=None, tile_size=None, prompt=None, extra_pnginfo=None, my_unique_id=None, force_full_denoise=False, disable_noise=False): + + return super().run(pipe, None, None,None, None,None,None,None, image_output, link_id, save_prefix, + None, None, None, model_c, tile_size, prompt, extra_pnginfo, my_unique_id, force_full_denoise, disable_noise) + +class unsampler: + @classmethod + def INPUT_TYPES(s): + return {"required":{ + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "end_at_step": ("INT", {"default": 0, "min": 0, "max": 10000}), + "cfg": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS,), + "normalize": (["disable", "enable"],), + + }, + "optional": { + "pipe": ("PIPE_LINE",), + "optional_model": ("MODEL",), + "optional_positive": ("CONDITIONING",), + "optional_negative": ("CONDITIONING",), + "optional_latent": ("LATENT",), + } + } + + RETURN_TYPES = ("PIPE_LINE", "LATENT",) + RETURN_NAMES = ("pipe", "latent",) + FUNCTION = "unsampler" + + CATEGORY = "EasyUse/Sampler" + + def unsampler(self, cfg, sampler_name, steps, end_at_step, scheduler, normalize, pipe=None, optional_model=None, optional_positive=None, optional_negative=None, + optional_latent=None): + + model = optional_model if optional_model is not None else pipe["model"] + positive = optional_positive if optional_positive is not None else pipe["positive"] + negative = optional_negative if optional_negative is not None else pipe["negative"] + latent_image = optional_latent if optional_latent is not None else pipe["samples"] + + normalize = normalize == "enable" + device = comfy.model_management.get_torch_device() + latent = latent_image + latent_image = latent["samples"] + + end_at_step = min(end_at_step, steps - 1) + end_at_step = steps - end_at_step + + noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu") + noise_mask = None + if "noise_mask" in latent: + noise_mask = comfy.sampler_helpers.prepare_mask(latent["noise_mask"], noise.shape, device) + + noise = noise.to(device) + latent_image = latent_image.to(device) + + _positive = comfy.sampler_helpers.convert_cond(positive) + _negative = comfy.sampler_helpers.convert_cond(negative) + models, inference_memory = comfy.sampler_helpers.get_additional_models({"positive": _positive, "negative": _negative}, model.model_dtype()) + + + comfy.model_management.load_models_gpu([model] + models, model.memory_required(noise.shape) + inference_memory) + + model_patcher = comfy.model_patcher.ModelPatcher(model.model, load_device=device, offload_device=comfy.model_management.unet_offload_device()) + + sampler = comfy.samplers.KSampler(model_patcher, steps=steps, device=device, sampler=sampler_name, + scheduler=scheduler, denoise=1.0, model_options=model.model_options) + + sigmas = sampler.sigmas.flip(0) + 0.0001 + + pbar = comfy.utils.ProgressBar(steps) + + def callback(step, x0, x, total_steps): + pbar.update_absolute(step + 1, total_steps) + + samples = sampler.sample(noise, positive, negative, cfg=cfg, latent_image=latent_image, + force_full_denoise=False, denoise_mask=noise_mask, sigmas=sigmas, start_step=0, + last_step=end_at_step, callback=callback) + if normalize: + # technically doesn't normalize because unsampling is not guaranteed to end at a std given by the schedule + samples -= samples.mean() + samples /= samples.std() + samples = samples.cpu() + + comfy.sample.cleanup_additional_models(models) + + out = latent.copy() + out["samples"] = samples + + if pipe is None: + pipe = {} + + new_pipe = { + **pipe, + "samples": out + } + + return (new_pipe, out,) + +#---------------------------------------------------------------采样器 结束---------------------------------------------------------------------- + +#---------------------------------------------------------------修复 开始----------------------------------------------------------------------# + +# 高清修复 +class hiresFix: + upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos", "bislerp"] + crop_methods = ["disabled", "center"] + + @classmethod + def INPUT_TYPES(s): + return {"required": { + "model_name": (folder_paths.get_filename_list("upscale_models"),), + "rescale_after_model": ([False, True], {"default": True}), + "rescale_method": (s.upscale_methods,), + "rescale": (["by percentage", "to Width/Height", 'to longer side - maintain aspect'],), + "percent": ("INT", {"default": 50, "min": 0, "max": 1000, "step": 1}), + "width": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "height": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "longer_side": ("INT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "crop": (s.crop_methods,), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "pipe": ("PIPE_LINE",), + "image": ("IMAGE",), + "vae": ("VAE",), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", + }, + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE", "LATENT", ) + RETURN_NAMES = ('pipe', 'image', "latent", ) + + FUNCTION = "upscale" + CATEGORY = "EasyUse/Fix" + OUTPUT_NODE = True + + def vae_encode_crop_pixels(self, pixels): + x = (pixels.shape[1] // 8) * 8 + y = (pixels.shape[2] // 8) * 8 + if pixels.shape[1] != x or pixels.shape[2] != y: + x_offset = (pixels.shape[1] % 8) // 2 + y_offset = (pixels.shape[2] % 8) // 2 + pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] + return pixels + + def upscale(self, model_name, rescale_after_model, rescale_method, rescale, percent, width, height, + longer_side, crop, image_output, link_id, save_prefix, pipe=None, image=None, vae=None, prompt=None, + extra_pnginfo=None, my_unique_id=None): + + new_pipe = {} + if pipe is not None: + image = image if image is not None else pipe["images"] + vae = vae if vae is not None else pipe.get("vae") + elif image is None or vae is None: + raise ValueError("pipe or image or vae missing.") + # Load Model + model_path = folder_paths.get_full_path("upscale_models", model_name) + sd = comfy.utils.load_torch_file(model_path, safe_load=True) + upscale_model = model_loading.load_state_dict(sd).eval() + + # Model upscale + device = comfy.model_management.get_torch_device() + upscale_model.to(device) + in_img = image.movedim(-1, -3).to(device) + + tile = 128 + 64 + overlap = 8 + steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile, + tile_y=tile, overlap=overlap) + pbar = comfy.utils.ProgressBar(steps) + s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap, + upscale_amount=upscale_model.scale, pbar=pbar) + upscale_model.cpu() + s = torch.clamp(s.movedim(-3, -1), min=0, max=1.0) + + # Post Model Rescale + if rescale_after_model == True: + samples = s.movedim(-1, 1) + orig_height = samples.shape[2] + orig_width = samples.shape[3] + if rescale == "by percentage" and percent != 0: + height = percent / 100 * orig_height + width = percent / 100 * orig_width + if (width > MAX_RESOLUTION): + width = MAX_RESOLUTION + if (height > MAX_RESOLUTION): + height = MAX_RESOLUTION + + width = easySampler.enforce_mul_of_64(width) + height = easySampler.enforce_mul_of_64(height) + elif rescale == "to longer side - maintain aspect": + longer_side = easySampler.enforce_mul_of_64(longer_side) + if orig_width > orig_height: + width, height = longer_side, easySampler.enforce_mul_of_64(longer_side * orig_height / orig_width) + else: + width, height = easySampler.enforce_mul_of_64(longer_side * orig_width / orig_height), longer_side + + s = comfy.utils.common_upscale(samples, width, height, rescale_method, crop) + s = s.movedim(1, -1) + + # vae encode + pixels = self.vae_encode_crop_pixels(s) + t = vae.encode(pixels[:, :, :, :3]) + + if pipe is not None: + new_pipe = { + "model": pipe['model'], + "positive": pipe['positive'], + "negative": pipe['negative'], + "vae": vae, + "clip": pipe['clip'], + + "samples": {"samples": t}, + "images": s, + "seed": pipe['seed'], + + "loader_settings": { + **pipe["loader_settings"], + } + } + del pipe + else: + new_pipe = {} + + results = easySave(s, save_prefix, image_output, prompt, extra_pnginfo) + + if image_output in ("Sender", "Sender&Save"): + PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) + + if image_output in ("Hide", "Hide&Save"): + return (new_pipe, s, {"samples": t},) + + return {"ui": {"images": results}, + "result": (new_pipe, s, {"samples": t},)} + +# 预细节修复 +class preDetailerFix: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "pipe": ("PIPE_LINE",), + "guide_size": ("FLOAT", {"default": 256, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}), + "max_size": ("FLOAT", {"default": 768, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS + ['align_your_steps'],), + "denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}), + "feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}), + "noise_mask": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), + "force_inpaint": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), + "drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}), + "wildcard": ("STRING", {"multiline": True, "dynamicPrompts": False}), + "cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}), + }, + "optional": { + "bbox_segm_pipe": ("PIPE_LINE",), + "sam_pipe": ("PIPE_LINE",), + "optional_image": ("IMAGE",), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + OUTPUT_IS_LIST = (False,) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Fix" + + def doit(self, pipe, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, scheduler, denoise, feather, noise_mask, force_inpaint, drop_size, wildcard, cycle, bbox_segm_pipe=None, sam_pipe=None, optional_image=None): + + model = pipe["model"] if "model" in pipe else None + if model is None: + raise Exception(f"[ERROR] pipe['model'] is missing") + clip = pipe["clip"] if"clip" in pipe else None + if clip is None: + raise Exception(f"[ERROR] pipe['clip'] is missing") + vae = pipe["vae"] if "vae" in pipe else None + if vae is None: + raise Exception(f"[ERROR] pipe['vae'] is missing") + if optional_image is not None: + images = optional_image + else: + images = pipe["images"] if "images" in pipe else None + if images is None: + raise Exception(f"[ERROR] pipe['image'] is missing") + positive = pipe["positive"] if "positive" in pipe else None + if positive is None: + raise Exception(f"[ERROR] pipe['positive'] is missing") + negative = pipe["negative"] if "negative" in pipe else None + if negative is None: + raise Exception(f"[ERROR] pipe['negative'] is missing") + bbox_segm_pipe = bbox_segm_pipe or (pipe["bbox_segm_pipe"] if pipe and "bbox_segm_pipe" in pipe else None) + if bbox_segm_pipe is None: + raise Exception(f"[ERROR] bbox_segm_pipe or pipe['bbox_segm_pipe'] is missing") + sam_pipe = sam_pipe or (pipe["sam_pipe"] if pipe and "sam_pipe" in pipe else None) + if sam_pipe is None: + raise Exception(f"[ERROR] sam_pipe or pipe['sam_pipe'] is missing") + + loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {} + + if(scheduler == 'align_your_steps'): + model_version = get_sd_version(model) + if model_version == 'sdxl': + scheduler = 'AYS SDXL' + elif model_version == 'svd': + scheduler = 'AYS SVD' + else: + scheduler = 'AYS SD1' + + new_pipe = { + "images": images, + "model": model, + "clip": clip, + "vae": vae, + "positive": positive, + "negative": negative, + "seed": seed, + + "bbox_segm_pipe": bbox_segm_pipe, + "sam_pipe": sam_pipe, + + "loader_settings": loader_settings, + + "detail_fix_settings": { + "guide_size": guide_size, + "guide_size_for": guide_size_for, + "max_size": max_size, + "seed": seed, + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "feather": feather, + "noise_mask": noise_mask, + "force_inpaint": force_inpaint, + "drop_size": drop_size, + "wildcard": wildcard, + "cycle": cycle + } + } + + + del bbox_segm_pipe + del sam_pipe + + return (new_pipe,) + +# 预遮罩细节修复 +class preMaskDetailerFix: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "pipe": ("PIPE_LINE",), + "mask": ("MASK",), + + "guide_size": ("FLOAT", {"default": 384, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "guide_size_for": ("BOOLEAN", {"default": True, "label_on": "bbox", "label_off": "crop_region"}), + "max_size": ("FLOAT", {"default": 1024, "min": 64, "max": MAX_RESOLUTION, "step": 8}), + "mask_mode": ("BOOLEAN", {"default": True, "label_on": "masked only", "label_off": "whole"}), + + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), + "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0}), + "sampler_name": (comfy.samplers.KSampler.SAMPLERS,), + "scheduler": (comfy.samplers.KSampler.SCHEDULERS,), + "denoise": ("FLOAT", {"default": 0.5, "min": 0.0001, "max": 1.0, "step": 0.01}), + + "feather": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}), + "crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}), + "drop_size": ("INT", {"min": 1, "max": MAX_RESOLUTION, "step": 1, "default": 10}), + "refiner_ratio": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 1.0}), + "batch_size": ("INT", {"default": 1, "min": 1, "max": 100}), + "cycle": ("INT", {"default": 1, "min": 1, "max": 10, "step": 1}), + }, + "optional": { + # "patch": ("INPAINT_PATCH",), + "optional_image": ("IMAGE",), + "inpaint_model": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}), + "noise_mask_feather": ("INT", {"default": 20, "min": 0, "max": 100, "step": 1}), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + OUTPUT_IS_LIST = (False,) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Fix" + + def doit(self, pipe, mask, guide_size, guide_size_for, max_size, mask_mode, seed, steps, cfg, sampler_name, scheduler, denoise, feather, crop_factor, drop_size,refiner_ratio, batch_size, cycle, optional_image=None, inpaint_model=False, noise_mask_feather=20): + + model = pipe["model"] if "model" in pipe else None + if model is None: + raise Exception(f"[ERROR] pipe['model'] is missing") + clip = pipe["clip"] if"clip" in pipe else None + if clip is None: + raise Exception(f"[ERROR] pipe['clip'] is missing") + vae = pipe["vae"] if "vae" in pipe else None + if vae is None: + raise Exception(f"[ERROR] pipe['vae'] is missing") + if optional_image is not None: + images = optional_image + else: + images = pipe["images"] if "images" in pipe else None + if images is None: + raise Exception(f"[ERROR] pipe['image'] is missing") + positive = pipe["positive"] if "positive" in pipe else None + if positive is None: + raise Exception(f"[ERROR] pipe['positive'] is missing") + negative = pipe["negative"] if "negative" in pipe else None + if negative is None: + raise Exception(f"[ERROR] pipe['negative'] is missing") + latent = pipe["samples"] if "samples" in pipe else None + if latent is None: + raise Exception(f"[ERROR] pipe['samples'] is missing") + + if 'noise_mask' not in latent: + if images is None: + raise Exception("No Images found") + if vae is None: + raise Exception("No VAE found") + x = (images.shape[1] // 8) * 8 + y = (images.shape[2] // 8) * 8 + mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), + size=(images.shape[1], images.shape[2]), mode="bilinear") + + pixels = images.clone() + if pixels.shape[1] != x or pixels.shape[2] != y: + x_offset = (pixels.shape[1] % 8) // 2 + y_offset = (pixels.shape[2] % 8) // 2 + pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :] + mask = mask[:, :, x_offset:x + x_offset, y_offset:y + y_offset] + + mask_erosion = mask + + m = (1.0 - mask.round()).squeeze(1) + for i in range(3): + pixels[:, :, :, i] -= 0.5 + pixels[:, :, :, i] *= m + pixels[:, :, :, i] += 0.5 + t = vae.encode(pixels) + + latent = {"samples": t, "noise_mask": (mask_erosion[:, :, :x, :y].round())} + # when patch was linked + # if patch is not None: + # worker = InpaintWorker(node_name="easy kSamplerInpainting") + # model, = worker.patch(model, latent, patch) + + loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {} + + new_pipe = { + "images": images, + "model": model, + "clip": clip, + "vae": vae, + "positive": positive, + "negative": negative, + "seed": seed, + "mask": mask, + + "loader_settings": loader_settings, + + "detail_fix_settings": { + "guide_size": guide_size, + "guide_size_for": guide_size_for, + "max_size": max_size, + "seed": seed, + "steps": steps, + "cfg": cfg, + "sampler_name": sampler_name, + "scheduler": scheduler, + "denoise": denoise, + "feather": feather, + "crop_factor": crop_factor, + "drop_size": drop_size, + "refiner_ratio": refiner_ratio, + "batch_size": batch_size, + "cycle": cycle + }, + + "mask_settings": { + "mask_mode": mask_mode, + "inpaint_model": inpaint_model, + "noise_mask_feather": noise_mask_feather + } + } + + del pipe + + return (new_pipe,) + +# 细节修复 +class detailerFix: + @classmethod + def INPUT_TYPES(s): + return {"required": { + "pipe": ("PIPE_LINE",), + "image_output": (["Hide", "Preview", "Save", "Hide&Save", "Sender", "Sender&Save"],{"default": "Preview"}), + "link_id": ("INT", {"default": 0, "min": 0, "max": sys.maxsize, "step": 1}), + "save_prefix": ("STRING", {"default": "ComfyUI"}), + }, + "optional": { + "model": ("MODEL",), + }, + "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO", "my_unique_id": "UNIQUE_ID", } + } + + RETURN_TYPES = ("PIPE_LINE", "IMAGE", "IMAGE", "IMAGE") + RETURN_NAMES = ("pipe", "image", "cropped_refined", "cropped_enhanced_alpha") + OUTPUT_NODE = True + OUTPUT_IS_LIST = (False, False, True, True) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Fix" + + + def doit(self, pipe, image_output, link_id, save_prefix, model=None, prompt=None, extra_pnginfo=None, my_unique_id=None): + + # Clean loaded_objects + easyCache.update_loaded_objects(prompt) + + my_unique_id = int(my_unique_id) + + model = model or (pipe["model"] if "model" in pipe else None) + if model is None: + raise Exception(f"[ERROR] model or pipe['model'] is missing") + + detail_fix_settings = pipe["detail_fix_settings"] if "detail_fix_settings" in pipe else None + if detail_fix_settings is None: + raise Exception(f"[ERROR] detail_fix_settings or pipe['detail_fix_settings'] is missing") + + mask = pipe["mask"] if "mask" in pipe else None + + image = pipe["images"] + clip = pipe["clip"] + vae = pipe["vae"] + seed = pipe["seed"] + positive = pipe["positive"] + negative = pipe["negative"] + loader_settings = pipe["loader_settings"] if "loader_settings" in pipe else {} + guide_size = pipe["detail_fix_settings"]["guide_size"] if "guide_size" in pipe["detail_fix_settings"] else 256 + guide_size_for = pipe["detail_fix_settings"]["guide_size_for"] if "guide_size_for" in pipe[ + "detail_fix_settings"] else True + max_size = pipe["detail_fix_settings"]["max_size"] if "max_size" in pipe["detail_fix_settings"] else 768 + steps = pipe["detail_fix_settings"]["steps"] if "steps" in pipe["detail_fix_settings"] else 20 + cfg = pipe["detail_fix_settings"]["cfg"] if "cfg" in pipe["detail_fix_settings"] else 1.0 + sampler_name = pipe["detail_fix_settings"]["sampler_name"] if "sampler_name" in pipe[ + "detail_fix_settings"] else None + scheduler = pipe["detail_fix_settings"]["scheduler"] if "scheduler" in pipe["detail_fix_settings"] else None + denoise = pipe["detail_fix_settings"]["denoise"] if "denoise" in pipe["detail_fix_settings"] else 0.5 + feather = pipe["detail_fix_settings"]["feather"] if "feather" in pipe["detail_fix_settings"] else 5 + crop_factor = pipe["detail_fix_settings"]["crop_factor"] if "crop_factor" in pipe["detail_fix_settings"] else 3.0 + drop_size = pipe["detail_fix_settings"]["drop_size"] if "drop_size" in pipe["detail_fix_settings"] else 10 + refiner_ratio = pipe["detail_fix_settings"]["refiner_ratio"] if "refiner_ratio" in pipe else 0.2 + batch_size = pipe["detail_fix_settings"]["batch_size"] if "batch_size" in pipe["detail_fix_settings"] else 1 + noise_mask = pipe["detail_fix_settings"]["noise_mask"] if "noise_mask" in pipe["detail_fix_settings"] else None + force_inpaint = pipe["detail_fix_settings"]["force_inpaint"] if "force_inpaint" in pipe["detail_fix_settings"] else False + wildcard = pipe["detail_fix_settings"]["wildcard"] if "wildcard" in pipe["detail_fix_settings"] else "" + cycle = pipe["detail_fix_settings"]["cycle"] if "cycle" in pipe["detail_fix_settings"] else 1 + + bbox_segm_pipe = pipe["bbox_segm_pipe"] if pipe and "bbox_segm_pipe" in pipe else None + sam_pipe = pipe["sam_pipe"] if "sam_pipe" in pipe else None + + # 细节修复初始时间 + start_time = int(time.time() * 1000) + if "mask_settings" in pipe: + mask_mode = pipe['mask_settings']["mask_mode"] if "inpaint_model" in pipe['mask_settings'] else True + inpaint_model = pipe['mask_settings']["inpaint_model"] if "inpaint_model" in pipe['mask_settings'] else False + noise_mask_feather = pipe['mask_settings']["noise_mask_feather"] if "noise_mask_feather" in pipe['mask_settings'] else 20 + cls = ALL_NODE_CLASS_MAPPINGS["MaskDetailerPipe"] + if "MaskDetailerPipe" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception(f"[ERROR] To use MaskDetailerPipe, you need to install 'Impact Pack'") + basic_pipe = (model, clip, vae, positive, negative) + result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, basic_pipe, refiner_basic_pipe_opt = cls().doit(image, mask, basic_pipe, guide_size, guide_size_for, max_size, mask_mode, + seed, steps, cfg, sampler_name, scheduler, denoise, + feather, crop_factor, drop_size, refiner_ratio, batch_size, cycle=1, + refiner_basic_pipe_opt=None, detailer_hook=None, inpaint_model=inpaint_model, noise_mask_feather=noise_mask_feather) + result_mask = mask + result_cnet_images = () + else: + if bbox_segm_pipe is None: + raise Exception(f"[ERROR] bbox_segm_pipe or pipe['bbox_segm_pipe'] is missing") + if sam_pipe is None: + raise Exception(f"[ERROR] sam_pipe or pipe['sam_pipe'] is missing") + bbox_detector_opt, bbox_threshold, bbox_dilation, bbox_crop_factor, segm_detector_opt = bbox_segm_pipe + sam_model_opt, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative = sam_pipe + if "FaceDetailer" not in ALL_NODE_CLASS_MAPPINGS: + raise Exception(f"[ERROR] To use FaceDetailer, you need to install 'Impact Pack'") + cls = ALL_NODE_CLASS_MAPPINGS["FaceDetailer"] + + result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, pipe, result_cnet_images = cls().doit( + image, model, clip, vae, guide_size, guide_size_for, max_size, seed, steps, cfg, sampler_name, + scheduler, + positive, negative, denoise, feather, noise_mask, force_inpaint, + bbox_threshold, bbox_dilation, bbox_crop_factor, + sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, + sam_mask_hint_use_negative, drop_size, bbox_detector_opt, wildcard, cycle, sam_model_opt, + segm_detector_opt, + detailer_hook=None) + + # 细节修复结束时间 + end_time = int(time.time() * 1000) + + spent_time = 'Fix:' + str((end_time - start_time) / 1000) + '"' + + results = easySave(result_img, save_prefix, image_output, prompt, extra_pnginfo) + sampler.update_value_by_id("results", my_unique_id, results) + + # Clean loaded_objects + easyCache.update_loaded_objects(prompt) + + new_pipe = { + "samples": None, + "images": result_img, + "model": model, + "clip": clip, + "vae": vae, + "seed": seed, + "positive": positive, + "negative": negative, + "wildcard": wildcard, + "bbox_segm_pipe": bbox_segm_pipe, + "sam_pipe": sam_pipe, + + "loader_settings": { + **loader_settings, + "spent_time": spent_time + }, + "detail_fix_settings": detail_fix_settings + } + if "mask_settings" in pipe: + new_pipe["mask_settings"] = pipe["mask_settings"] + + sampler.update_value_by_id("pipe_line", my_unique_id, new_pipe) + + del bbox_segm_pipe + del sam_pipe + del pipe + + if image_output in ("Hide", "Hide&Save"): + return (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images) + + if image_output in ("Sender", "Sender&Save"): + PromptServer.instance.send_sync("img-send", {"link_id": link_id, "images": results}) + + return {"ui": {"images": results}, "result": (new_pipe, result_img, result_cropped_enhanced, result_cropped_enhanced_alpha, result_mask, result_cnet_images )} + +class ultralyticsDetectorForDetailerFix: + @classmethod + def INPUT_TYPES(s): + bboxs = ["bbox/" + x for x in folder_paths.get_filename_list("ultralytics_bbox")] + segms = ["segm/" + x for x in folder_paths.get_filename_list("ultralytics_segm")] + return {"required": + {"model_name": (bboxs + segms,), + "bbox_threshold": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}), + "bbox_dilation": ("INT", {"default": 10, "min": -512, "max": 512, "step": 1}), + "bbox_crop_factor": ("FLOAT", {"default": 3.0, "min": 1.0, "max": 10, "step": 0.1}), + } + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("bbox_segm_pipe",) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Fix" + + def doit(self, model_name, bbox_threshold, bbox_dilation, bbox_crop_factor): + if 'UltralyticsDetectorProvider' not in ALL_NODE_CLASS_MAPPINGS: + raise Exception(f"[ERROR] To use UltralyticsDetectorProvider, you need to install 'Impact Pack'") + cls = ALL_NODE_CLASS_MAPPINGS['UltralyticsDetectorProvider'] + bbox_detector, segm_detector = cls().doit(model_name) + pipe = (bbox_detector, bbox_threshold, bbox_dilation, bbox_crop_factor, segm_detector) + return (pipe,) + +class samLoaderForDetailerFix: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model_name": (folder_paths.get_filename_list("sams"),), + "device_mode": (["AUTO", "Prefer GPU", "CPU"],{"default": "AUTO"}), + "sam_detection_hint": ( + ["center-1", "horizontal-2", "vertical-2", "rect-4", "diamond-4", "mask-area", "mask-points", + "mask-point-bbox", "none"],), + "sam_dilation": ("INT", {"default": 0, "min": -512, "max": 512, "step": 1}), + "sam_threshold": ("FLOAT", {"default": 0.93, "min": 0.0, "max": 1.0, "step": 0.01}), + "sam_bbox_expansion": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}), + "sam_mask_hint_threshold": ("FLOAT", {"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.01}), + "sam_mask_hint_use_negative": (["False", "Small", "Outter"],), + } + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("sam_pipe",) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Fix" + + def doit(self, model_name, device_mode, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative): + if 'SAMLoader' not in ALL_NODE_CLASS_MAPPINGS: + raise Exception(f"[ERROR] To use SAMLoader, you need to install 'Impact Pack'") + cls = ALL_NODE_CLASS_MAPPINGS['SAMLoader'] + (sam_model,) = cls().load_model(model_name, device_mode) + pipe = (sam_model, sam_detection_hint, sam_dilation, sam_threshold, sam_bbox_expansion, sam_mask_hint_threshold, sam_mask_hint_use_negative) + return (pipe,) + +#---------------------------------------------------------------修复 结束---------------------------------------------------------------------- + +#---------------------------------------------------------------节点束 开始----------------------------------------------------------------------# +# 节点束输入 +class pipeIn: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": {}, + "optional": { + "pipe": ("PIPE_LINE",), + "model": ("MODEL",), + "pos": ("CONDITIONING",), + "neg": ("CONDITIONING",), + "latent": ("LATENT",), + "vae": ("VAE",), + "clip": ("CLIP",), + "image": ("IMAGE",), + "xyPlot": ("XYPLOT",), + }, + "hidden": {"my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + FUNCTION = "flush" + + CATEGORY = "EasyUse/Pipe" + + def flush(self, pipe=None, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, image=None, xyplot=None, my_unique_id=None): + + model = model if model is not None else pipe.get("model") + if model is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Model missing from pipeLine") + pos = pos if pos is not None else pipe.get("positive") + if pos is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Pos Conditioning missing from pipeLine") + neg = neg if neg is not None else pipe.get("negative") + if neg is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine") + vae = vae if vae is not None else pipe.get("vae") + if vae is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine") + clip = clip if clip is not None else pipe.get("clip") if pipe is not None and "clip" in pipe else None + # if clip is None: + # log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine") + if latent is not None: + samples = latent + elif image is None: + samples = pipe.get("samples") if pipe is not None else None + image = pipe.get("images") if pipe is not None else None + elif image is not None: + if pipe is None: + batch_size = 1 + else: + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + samples = {"samples": vae.encode(image[:, :, :, :3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + + if pipe is None: + pipe = {"loader_settings": {"positive": "", "negative": "", "xyplot": None}} + + xyplot = xyplot if xyplot is not None else pipe['loader_settings']['xyplot'] if xyplot in pipe['loader_settings'] else None + + new_pipe = { + **pipe, + "model": model, + "positive": pos, + "negative": neg, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": pipe.get('seed') if pipe is not None and "seed" in pipe else None, + + "loader_settings": { + **pipe["loader_settings"], + "xyplot": xyplot + } + } + del pipe + + return (new_pipe,) + +# 节点束输出 +class pipeOut: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + }, + "hidden": {"my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "IMAGE", "INT",) + RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "image", "seed",) + FUNCTION = "flush" + + CATEGORY = "EasyUse/Pipe" + + def flush(self, pipe, my_unique_id=None): + model = pipe.get("model") + pos = pipe.get("positive") + neg = pipe.get("negative") + latent = pipe.get("samples") + vae = pipe.get("vae") + clip = pipe.get("clip") + image = pipe.get("images") + seed = pipe.get("seed") + + return pipe, model, pos, neg, latent, vae, clip, image, seed + +# 编辑节点束 +class pipeEdit: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "clip_skip": ("INT", {"default": -1, "min": -24, "max": 0, "step": 1}), + + "optional_positive": ("STRING", {"default": "", "multiline": True}), + "positive_token_normalization": (["none", "mean", "length", "length+mean"],), + "positive_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), + + "optional_negative": ("STRING", {"default": "", "multiline": True}), + "negative_token_normalization": (["none", "mean", "length", "length+mean"],), + "negative_weight_interpretation": (["comfy", "A1111", "comfy++", "compel", "fixed attention"],), + + "a1111_prompt_style": ("BOOLEAN", {"default": False}), + "conditioning_mode": (['replace', 'concat', 'combine', 'average', 'timestep'], {"default": "replace"}), + "average_strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}), + "old_cond_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "old_cond_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "new_cond_start": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), + "new_cond_end": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), + }, + "optional": { + "pipe": ("PIPE_LINE",), + "model": ("MODEL",), + "pos": ("CONDITIONING",), + "neg": ("CONDITIONING",), + "latent": ("LATENT",), + "vae": ("VAE",), + "clip": ("CLIP",), + "image": ("IMAGE",), + }, + "hidden": {"my_unique_id": "UNIQUE_ID", "prompt":"PROMPT"}, + } + + RETURN_TYPES = ("PIPE_LINE", "MODEL", "CONDITIONING", "CONDITIONING", "LATENT", "VAE", "CLIP", "IMAGE") + RETURN_NAMES = ("pipe", "model", "pos", "neg", "latent", "vae", "clip", "image") + FUNCTION = "edit" + + CATEGORY = "EasyUse/Pipe" + + def edit(self, clip_skip, optional_positive, positive_token_normalization, positive_weight_interpretation, optional_negative, negative_token_normalization, negative_weight_interpretation, a1111_prompt_style, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end, pipe=None, model=None, pos=None, neg=None, latent=None, vae=None, clip=None, image=None, my_unique_id=None, prompt=None): + + model = model if model is not None else pipe.get("model") + if model is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Model missing from pipeLine") + vae = vae if vae is not None else pipe.get("vae") + if vae is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "VAE missing from pipeLine") + clip = clip if clip is not None else pipe.get("clip") + if clip is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Clip missing from pipeLine") + if image is None: + image = pipe.get("images") if pipe is not None else None + samples = latent if latent is not None else pipe.get("samples") + if samples is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Latent missing from pipeLine") + else: + batch_size = pipe["loader_settings"]["batch_size"] if "batch_size" in pipe["loader_settings"] else 1 + samples = {"samples": vae.encode(image[:, :, :, :3])} + samples = RepeatLatentBatch().repeat(samples, batch_size)[0] + + pipe_lora_stack = pipe.get("lora_stack") if pipe is not None and "lora_stack" in pipe else [] + + steps = pipe["loader_settings"]["steps"] if "steps" in pipe["loader_settings"] else 1 + if pos is None and optional_positive != '': + pos, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, clip_skip, + pipe_lora_stack, optional_positive, positive_token_normalization,positive_weight_interpretation, + a1111_prompt_style, my_unique_id, prompt, easyCache, True, steps) + pos = set_cond(pipe['positive'], pos, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end) + pipe['loader_settings']['positive'] = positive_wildcard_prompt + pipe['loader_settings']['positive_token_normalization'] = positive_token_normalization + pipe['loader_settings']['positive_weight_interpretation'] = positive_weight_interpretation + if a1111_prompt_style: + pipe['loader_settings']['a1111_prompt_style'] = True + else: + pos = pipe.get("positive") + if pos is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Pos Conditioning missing from pipeLine") + + if neg is None and optional_negative != '': + neg, negative_wildcard_prompt, model, clip = prompt_to_cond("negative", model, clip, clip_skip, pipe_lora_stack, optional_negative, + negative_token_normalization, negative_weight_interpretation, + a1111_prompt_style, my_unique_id, prompt, easyCache, True, steps) + neg = set_cond(pipe['negative'], neg, conditioning_mode, average_strength, old_cond_start, old_cond_end, new_cond_start, new_cond_end) + pipe['loader_settings']['negative'] = negative_wildcard_prompt + pipe['loader_settings']['negative_token_normalization'] = negative_token_normalization + pipe['loader_settings']['negative_weight_interpretation'] = negative_weight_interpretation + if a1111_prompt_style: + pipe['loader_settings']['a1111_prompt_style'] = True + else: + neg = pipe.get("negative") + if neg is None: + log_node_warn(f'pipeIn[{my_unique_id}]', "Neg Conditioning missing from pipeLine") + if pipe is None: + pipe = {"loader_settings": {"positive": "", "negative": "", "xyplot": None}} + + new_pipe = { + **pipe, + "model": model, + "positive": pos, + "negative": neg, + "vae": vae, + "clip": clip, + + "samples": samples, + "images": image, + "seed": pipe.get('seed') if pipe is not None and "seed" in pipe else None, + "loader_settings":{ + **pipe["loader_settings"] + } + } + del pipe + + return (new_pipe, model,pos, neg, latent, vae, clip, image) + +# 编辑节点束提示词 +class pipeEditPrompt: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "positive": ("STRING", {"default": "", "multiline": True}), + "negative": ("STRING", {"default": "", "multiline": True}), + }, + "hidden": {"my_unique_id": "UNIQUE_ID", "prompt": "PROMPT"}, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + FUNCTION = "edit" + + CATEGORY = "EasyUse/Pipe" + + def edit(self, pipe, positive, negative, my_unique_id=None, prompt=None): + model = pipe.get("model") + if model is None: + log_node_warn(f'pipeEdit[{my_unique_id}]', "Model missing from pipeLine") + + from .kolors.loader import is_kolors_model + model_type = get_sd_version(model) + if model_type == 'sdxl' and is_kolors_model(model): + auto_clean_gpu = pipe["loader_settings"]["auto_clean_gpu"] if "auto_clean_gpu" in pipe["loader_settings"] else False + chatglm3_model = pipe["chatglm3_model"] if "chatglm3_model" in pipe else None + # text encode + log_node_warn("Positive encoding...") + positive_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, positive, auto_clean_gpu) + log_node_warn("Negative encoding...") + negative_embeddings_final = chatglm3_adv_text_encode(chatglm3_model, negative, auto_clean_gpu) + else: + clip_skip = pipe["loader_settings"]["clip_skip"] if "clip_skip" in pipe["loader_settings"] else -1 + lora_stack = pipe.get("lora_stack") if pipe is not None and "lora_stack" in pipe else [] + clip = pipe.get("clip") if pipe is not None and "clip" in pipe else None + positive_token_normalization = pipe["loader_settings"]["positive_token_normalization"] if "positive_token_normalization" in pipe["loader_settings"] else "none" + positive_weight_interpretation = pipe["loader_settings"]["positive_weight_interpretation"] if "positive_weight_interpretation" in pipe["loader_settings"] else "comfy" + negative_token_normalization = pipe["loader_settings"]["negative_token_normalization"] if "negative_token_normalization" in pipe["loader_settings"] else "none" + negative_weight_interpretation = pipe["loader_settings"]["negative_weight_interpretation"] if "negative_weight_interpretation" in pipe["loader_settings"] else "comfy" + a1111_prompt_style = pipe["loader_settings"]["a1111_prompt_style"] if "a1111_prompt_style" in pipe["loader_settings"] else False + # Prompt to Conditioning + positive_embeddings_final, positive_wildcard_prompt, model, clip = prompt_to_cond('positive', model, clip, + clip_skip, lora_stack, + positive, + positive_token_normalization, + positive_weight_interpretation, + a1111_prompt_style, + my_unique_id, prompt, + easyCache, + model_type=model_type) + negative_embeddings_final, negative_wildcard_prompt, model, clip = prompt_to_cond('negative', model, clip, + clip_skip, lora_stack, + negative, + negative_token_normalization, + negative_weight_interpretation, + a1111_prompt_style, + my_unique_id, prompt, + easyCache, + model_type=model_type) + new_pipe = { + **pipe, + "model": model, + "positive": positive_embeddings_final, + "negative": negative_embeddings_final, + } + del pipe + + return (new_pipe,) + + +# 节点束到基础节点束(pipe to ComfyUI-Impack-pack's basic_pipe) +class pipeToBasicPipe: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + }, + "hidden": {"my_unique_id": "UNIQUE_ID"}, + } + + RETURN_TYPES = ("BASIC_PIPE",) + RETURN_NAMES = ("basic_pipe",) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Pipe" + + def doit(self, pipe, my_unique_id=None): + new_pipe = (pipe.get('model'), pipe.get('clip'), pipe.get('vae'), pipe.get('positive'), pipe.get('negative')) + del pipe + return (new_pipe,) + +# 批次索引 +class pipeBatchIndex: + @classmethod + def INPUT_TYPES(s): + return {"required": {"pipe": ("PIPE_LINE",), + "batch_index": ("INT", {"default": 0, "min": 0, "max": 63}), + "length": ("INT", {"default": 1, "min": 1, "max": 64}), + }, + "hidden": {"my_unique_id": "UNIQUE_ID"},} + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + FUNCTION = "doit" + + CATEGORY = "EasyUse/Pipe" + + def doit(self, pipe, batch_index, length, my_unique_id=None): + samples = pipe["samples"] + new_samples, = LatentFromBatch().frombatch(samples, batch_index, length) + new_pipe = { + **pipe, + "samples": new_samples + } + del pipe + return (new_pipe,) + +# pipeXYPlot +class pipeXYPlot: + lora_list = ["None"] + folder_paths.get_filename_list("loras") + lora_strengths = {"min": -4.0, "max": 4.0, "step": 0.01} + token_normalization = ["none", "mean", "length", "length+mean"] + weight_interpretation = ["comfy", "A1111", "compel", "comfy++"] + + loader_dict = { + "ckpt_name": folder_paths.get_filename_list("checkpoints"), + "vae_name": ["Baked-VAE"] + folder_paths.get_filename_list("vae"), + "clip_skip": {"min": -24, "max": -1, "step": 1}, + "lora_name": lora_list, + "lora_model_strength": lora_strengths, + "lora_clip_strength": lora_strengths, + "positive": [], + "negative": [], + } + + sampler_dict = { + "steps": {"min": 1, "max": 100, "step": 1}, + "cfg": {"min": 0.0, "max": 100.0, "step": 1.0}, + "sampler_name": comfy.samplers.KSampler.SAMPLERS, + "scheduler": comfy.samplers.KSampler.SCHEDULERS, + "denoise": {"min": 0.0, "max": 1.0, "step": 0.01}, + "seed": {"min": 0, "max": MAX_SEED_NUM}, + } + + plot_dict = {**sampler_dict, **loader_dict} + + plot_values = ["None", ] + plot_values.append("---------------------") + for k in sampler_dict: + plot_values.append(f'preSampling: {k}') + plot_values.append("---------------------") + for k in loader_dict: + plot_values.append(f'loader: {k}') + + def __init__(self): + pass + + rejected = ["None", "---------------------", "Nothing"] + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "grid_spacing": ("INT", {"min": 0, "max": 500, "step": 5, "default": 0, }), + "output_individuals": (["False", "True"], {"default": "False"}), + "flip_xy": (["False", "True"], {"default": "False"}), + "x_axis": (pipeXYPlot.plot_values, {"default": 'None'}), + "x_values": ( + "STRING", {"default": '', "multiline": True, "placeholder": 'insert values seperated by "; "'}), + "y_axis": (pipeXYPlot.plot_values, {"default": 'None'}), + "y_values": ( + "STRING", {"default": '', "multiline": True, "placeholder": 'insert values seperated by "; "'}), + }, + "optional": { + "pipe": ("PIPE_LINE",) + }, + "hidden": { + "plot_dict": (pipeXYPlot.plot_dict,), + }, + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + FUNCTION = "plot" + + CATEGORY = "EasyUse/Pipe" + + def plot(self, grid_spacing, output_individuals, flip_xy, x_axis, x_values, y_axis, y_values, pipe=None, font_path=None): + def clean_values(values): + original_values = values.split("; ") + cleaned_values = [] + + for value in original_values: + # Strip the semi-colon + cleaned_value = value.strip(';').strip() + + if cleaned_value == "": + continue + + # Try to convert the cleaned_value back to int or float if possible + try: + cleaned_value = int(cleaned_value) + except ValueError: + try: + cleaned_value = float(cleaned_value) + except ValueError: + pass + + # Append the cleaned_value to the list + cleaned_values.append(cleaned_value) + + return cleaned_values + + if x_axis in self.rejected: + x_axis = "None" + x_values = [] + else: + x_values = clean_values(x_values) + + if y_axis in self.rejected: + y_axis = "None" + y_values = [] + else: + y_values = clean_values(y_values) + + if flip_xy == "True": + x_axis, y_axis = y_axis, x_axis + x_values, y_values = y_values, x_values + + + xy_plot = {"x_axis": x_axis, + "x_vals": x_values, + "y_axis": y_axis, + "y_vals": y_values, + "custom_font": font_path, + "grid_spacing": grid_spacing, + "output_individuals": output_individuals} + + if pipe is not None: + new_pipe = pipe + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "xyplot": xy_plot + } + del pipe + return (new_pipe, xy_plot,) + +# pipeXYPlotAdvanced +import platform +class pipeXYPlotAdvanced: + if platform.system() == "Windows": + system_root = os.environ.get("SystemRoot") + user_root = os.environ.get("USERPROFILE") + font_dir = os.path.join(system_root, "Fonts") if system_root else None + user_font_dir = os.path.join(user_root, "AppData","Local","Microsoft","Windows", "Fonts") if user_root else None + + # Default debian-based Linux & MacOS font dirs + elif platform.system() == "Linux": + font_dir = "/usr/share/fonts/truetype" + user_font_dir = None + elif platform.system() == "Darwin": + font_dir = "/System/Library/Fonts" + user_font_dir = None + else: + font_dir = None + user_font_dir = None + + @classmethod + def INPUT_TYPES(s): + files_list = [] + if s.font_dir and os.path.exists(s.font_dir): + font_dir = s.font_dir + files_list = files_list + [f for f in os.listdir(font_dir) if os.path.isfile(os.path.join(font_dir, f)) and f.lower().endswith(".ttf")] + + if s.user_font_dir and os.path.exists(s.user_font_dir): + files_list = files_list + [f for f in os.listdir(s.user_font_dir) if os.path.isfile(os.path.join(s.user_font_dir, f)) and f.lower().endswith(".ttf")] + + return { + "required": { + "pipe": ("PIPE_LINE",), + "grid_spacing": ("INT", {"min": 0, "max": 500, "step": 5, "default": 0, }), + "output_individuals": (["False", "True"], {"default": "False"}), + "flip_xy": (["False", "True"], {"default": "False"}), + }, + "optional": { + "X": ("X_Y",), + "Y": ("X_Y",), + "font": (["None"] + files_list,) + }, + "hidden": {"my_unique_id": "UNIQUE_ID"} + } + + RETURN_TYPES = ("PIPE_LINE",) + RETURN_NAMES = ("pipe",) + FUNCTION = "plot" + + CATEGORY = "EasyUse/Pipe" + + def plot(self, pipe, grid_spacing, output_individuals, flip_xy, X=None, Y=None, font=None, my_unique_id=None): + font_path = os.path.join(self.font_dir, font) if font != "None" else None + if font_path and not os.path.exists(font_path): + font_path = os.path.join(self.user_font_dir, font) + + if X != None: + x_axis = X.get('axis') + x_values = X.get('values') + else: + x_axis = "Nothing" + x_values = [""] + if Y != None: + y_axis = Y.get('axis') + y_values = Y.get('values') + else: + y_axis = "Nothing" + y_values = [""] + + if pipe is not None: + new_pipe = pipe + positive = pipe["loader_settings"]["positive"] if "positive" in pipe["loader_settings"] else "" + negative = pipe["loader_settings"]["negative"] if "negative" in pipe["loader_settings"] else "" + + if x_axis == 'advanced: ModelMergeBlocks': + models = X.get('models') + vae_use = X.get('vae_use') + if models is None: + raise Exception("models is not found") + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "models": models, + "vae_use": vae_use + } + if y_axis == 'advanced: ModelMergeBlocks': + models = Y.get('models') + vae_use = Y.get('vae_use') + if models is None: + raise Exception("models is not found") + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "models": models, + "vae_use": vae_use + } + + if x_axis in ['advanced: Lora', 'advanced: Checkpoint']: + lora_stack = X.get('lora_stack') + _lora_stack = [] + if lora_stack is not None: + for lora in lora_stack: + _lora_stack.append( + {"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "model_strength": lora[1], + "clip_strength": lora[2]}) + del lora_stack + x_values = "; ".join(x_values) + lora_stack = pipe['lora_stack'] + _lora_stack if 'lora_stack' in pipe else _lora_stack + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "lora_stack": lora_stack, + } + + if y_axis in ['advanced: Lora', 'advanced: Checkpoint']: + lora_stack = Y.get('lora_stack') + _lora_stack = [] + if lora_stack is not None: + for lora in lora_stack: + _lora_stack.append( + {"lora_name": lora[0], "model": pipe['model'], "clip": pipe['clip'], "model_strength": lora[1], + "clip_strength": lora[2]}) + del lora_stack + y_values = "; ".join(y_values) + lora_stack = pipe['lora_stack'] + _lora_stack if 'lora_stack' in pipe else _lora_stack + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "lora_stack": lora_stack, + } + + if x_axis == 'advanced: Seeds++ Batch': + if new_pipe['seed']: + value = x_values + x_values = [] + for index in range(value): + x_values.append(str(new_pipe['seed'] + index)) + x_values = "; ".join(x_values) + if y_axis == 'advanced: Seeds++ Batch': + if new_pipe['seed']: + value = y_values + y_values = [] + for index in range(value): + y_values.append(str(new_pipe['seed'] + index)) + y_values = "; ".join(y_values) + + if x_axis == 'advanced: Positive Prompt S/R': + if positive: + x_value = x_values + x_values = [] + for index, value in enumerate(x_value): + search_txt, replace_txt, replace_all = value + if replace_all: + txt = replace_txt if replace_txt is not None else positive + x_values.append(txt) + else: + txt = positive.replace(search_txt, replace_txt, 1) if replace_txt is not None else positive + x_values.append(txt) + x_values = "; ".join(x_values) + if y_axis == 'advanced: Positive Prompt S/R': + if positive: + y_value = y_values + y_values = [] + for index, value in enumerate(y_value): + search_txt, replace_txt, replace_all = value + if replace_all: + txt = replace_txt if replace_txt is not None else positive + y_values.append(txt) + else: + txt = positive.replace(search_txt, replace_txt, 1) if replace_txt is not None else positive + y_values.append(txt) + y_values = "; ".join(y_values) + + if x_axis == 'advanced: Negative Prompt S/R': + if negative: + x_value = x_values + x_values = [] + for index, value in enumerate(x_value): + search_txt, replace_txt, replace_all = value + if replace_all: + txt = replace_txt if replace_txt is not None else negative + x_values.append(txt) + else: + txt = negative.replace(search_txt, replace_txt, 1) if replace_txt is not None else negative + x_values.append(txt) + x_values = "; ".join(x_values) + if y_axis == 'advanced: Negative Prompt S/R': + if negative: + y_value = y_values + y_values = [] + for index, value in enumerate(y_value): + search_txt, replace_txt, replace_all = value + if replace_all: + txt = replace_txt if replace_txt is not None else negative + y_values.append(txt) + else: + txt = negative.replace(search_txt, replace_txt, 1) if replace_txt is not None else negative + y_values.append(txt) + y_values = "; ".join(y_values) + + if "advanced: ControlNet" in x_axis: + x_value = x_values + x_values = [] + cnet = [] + for index, value in enumerate(x_value): + cnet.append(value) + x_values.append(str(index)) + x_values = "; ".join(x_values) + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "cnet_stack": cnet, + } + + if "advanced: ControlNet" in y_axis: + y_value = y_values + y_values = [] + cnet = [] + for index, value in enumerate(y_value): + cnet.append(value) + y_values.append(str(index)) + y_values = "; ".join(y_values) + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "cnet_stack": cnet, + } + + if "advanced: Pos Condition" in x_axis: + x_values = "; ".join(x_values) + cond = X.get('cond') + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "positive_cond_stack": cond, + } + if "advanced: Pos Condition" in y_axis: + y_values = "; ".join(y_values) + cond = Y.get('cond') + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "positive_cond_stack": cond, + } + + if "advanced: Neg Condition" in x_axis: + x_values = "; ".join(x_values) + cond = X.get('cond') + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "negative_cond_stack": cond, + } + if "advanced: Neg Condition" in y_axis: + y_values = "; ".join(y_values) + cond = Y.get('cond') + new_pipe['loader_settings'] = { + **pipe['loader_settings'], + "negative_cond_stack": cond, + } + + del pipe + + return pipeXYPlot().plot(grid_spacing, output_individuals, flip_xy, x_axis, x_values, y_axis, y_values, new_pipe, font_path) + +#---------------------------------------------------------------节点束 结束---------------------------------------------------------------------- + +# 显示推理时间 +class showSpentTime: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "spent_time": ("INFO", {"default": 'Time will be displayed when reasoning is complete', "forceInput": False}), + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + FUNCTION = "notify" + OUTPUT_NODE = True + RETURN_TYPES = () + RETURN_NAMES = () + + CATEGORY = "EasyUse/Util" + + def notify(self, pipe, spent_time=None, unique_id=None, extra_pnginfo=None): + if unique_id and extra_pnginfo and "workflow" in extra_pnginfo: + workflow = extra_pnginfo["workflow"] + node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None) + if node: + spent_time = pipe['loader_settings']['spent_time'] if 'spent_time' in pipe['loader_settings'] else '' + node["widgets_values"] = [spent_time] + + return {"ui": {"text": spent_time}, "result": {}} + +# 显示加载器参数中的各种名称 +class showLoaderSettingsNames: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "pipe": ("PIPE_LINE",), + "names": ("INFO", {"default": '', "forceInput": False}), + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + RETURN_TYPES = ("STRING", "STRING", "STRING",) + RETURN_NAMES = ("ckpt_name", "vae_name", "lora_name") + + FUNCTION = "notify" + OUTPUT_NODE = True + + CATEGORY = "EasyUse/Util" + + def notify(self, pipe, names=None, unique_id=None, extra_pnginfo=None): + if unique_id and extra_pnginfo and "workflow" in extra_pnginfo: + workflow = extra_pnginfo["workflow"] + node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id), None) + if node: + ckpt_name = pipe['loader_settings']['ckpt_name'] if 'ckpt_name' in pipe['loader_settings'] else '' + vae_name = pipe['loader_settings']['vae_name'] if 'vae_name' in pipe['loader_settings'] else '' + lora_name = pipe['loader_settings']['lora_name'] if 'lora_name' in pipe['loader_settings'] else '' + + if ckpt_name: + ckpt_name = os.path.basename(os.path.splitext(ckpt_name)[0]) + if vae_name: + vae_name = os.path.basename(os.path.splitext(vae_name)[0]) + if lora_name: + lora_name = os.path.basename(os.path.splitext(lora_name)[0]) + + names = "ckpt_name: " + ckpt_name + '\n' + "vae_name: " + vae_name + '\n' + "lora_name: " + lora_name + node["widgets_values"] = names + + return {"ui": {"text": names}, "result": (ckpt_name, vae_name, lora_name)} + + +class sliderControl: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mode": (['ipadapter layer weights'],), + "model_type": (['sdxl', 'sd1'],), + }, + "hidden": { + "prompt": "PROMPT", + "my_unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("layer_weights",) + + FUNCTION = "control" + + CATEGORY = "EasyUse/Util" + + def control(self, mode, model_type, prompt=None, my_unique_id=None, extra_pnginfo=None): + values = '' + if my_unique_id in prompt: + if 'values' in prompt[my_unique_id]["inputs"]: + values = prompt[my_unique_id]["inputs"]['values'] + + return (values,) + +#---------------------------------------------------------------API 开始----------------------------------------------------------------------# +from .libs.stability import stableAPI +class stableDiffusion3API: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "positive": ("STRING", {"default": "", "placeholder": "Positive", "multiline": True}), + "negative": ("STRING", {"default": "", "placeholder": "Negative", "multiline": True}), + "model": (["sd3", "sd3-turbo"],), + "aspect_ratio": (['16:9', '1:1', '21:9', '2:3', '3:2', '4:5', '5:4', '9:16', '9:21'],), + "seed": ("INT", {"default": 0, "min": 0, "max": 4294967294}), + "denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}), + }, + "optional": { + "optional_image": ("IMAGE",), + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + + FUNCTION = "generate" + OUTPUT_NODE = False + + CATEGORY = "EasyUse/API" + + def generate(self, positive, negative, model, aspect_ratio, seed, denoise, optional_image=None, unique_id=None, extra_pnginfo=None): + mode = 'text-to-image' + if optional_image is not None: + mode = 'image-to-image' + output_image = stableAPI.generate_sd3_image(positive, negative, aspect_ratio, seed=seed, mode=mode, model=model, strength=denoise, image=optional_image) + return (output_image,) + +from .libs.fluxai import fluxaiAPI + +class fluxPromptGenAPI: + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "describe": ("STRING", {"default": "", "placeholder": "Describe your image idea (you can use any language)", "multiline": True}), + }, + "optional": { + "cookie_override": ("STRING", {"default": "", "forceInput": True}), + }, + "hidden": { + "prompt": "PROMPT", + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("prompt",) + + FUNCTION = "generate" + OUTPUT_NODE = False + + CATEGORY = "EasyUse/API" + + def generate(self, describe, cookie_override=None, prompt=None, unique_id=None, extra_pnginfo=None): + prompt = fluxaiAPI.promptGenerate(describe, cookie_override) + return (prompt,) + + +#---------------------------------------------------------------API 结束---------------------------------------------------------------------- + +NODE_CLASS_MAPPINGS = { + # seed 随机种 + "easy seed": easySeed, + "easy globalSeed": globalSeed, + # prompt 提示词 + "easy positive": positivePrompt, + "easy negative": negativePrompt, + "easy wildcards": wildcardsPrompt, + "easy prompt": prompt, + "easy promptList": promptList, + "easy promptLine": promptLine, + "easy promptConcat": promptConcat, + "easy promptReplace": promptReplace, + "easy stylesSelector": stylesPromptSelector, + "easy portraitMaster": portraitMaster, + # loaders 加载器 + "easy fullLoader": fullLoader, + "easy a1111Loader": a1111Loader, + "easy comfyLoader": comfyLoader, + "easy hunyuanDiTLoader": hunyuanDiTLoader, + "easy svdLoader": svdLoader, + "easy sv3dLoader": sv3DLoader, + "easy zero123Loader": zero123Loader, + "easy dynamiCrafterLoader": dynamiCrafterLoader, + "easy cascadeLoader": cascadeLoader, + "easy kolorsLoader": kolorsLoader, + "easy fluxLoader": fluxLoader, + "easy pixArtLoader": pixArtLoader, + "easy mochiLoader": mochiLoader, + "easy loraStack": loraStack, + "easy controlnetStack": controlnetStack, + "easy controlnetLoader": controlnetSimple, + "easy controlnetLoaderADV": controlnetAdvanced, + "easy controlnetLoader++": controlnetPlusPlus, + "easy LLLiteLoader": LLLiteLoader, + # Adapter 适配器 + "easy loraStackApply": applyLoraStack, + "easy controlnetStackApply": applyControlnetStack, + "easy ipadapterApply": ipadapterApply, + "easy ipadapterApplyADV": ipadapterApplyAdvanced, + "easy ipadapterApplyFaceIDKolors": ipadapterApplyFaceIDKolors, + "easy ipadapterApplyEncoder": ipadapterApplyEncoder, + "easy ipadapterApplyEmbeds": ipadapterApplyEmbeds, + "easy ipadapterApplyRegional": ipadapterApplyRegional, + "easy ipadapterApplyFromParams": ipadapterApplyFromParams, + "easy ipadapterStyleComposition": ipadapterStyleComposition, + "easy instantIDApply": instantIDApply, + "easy instantIDApplyADV": instantIDApplyAdvanced, + "easy pulIDApply": applyPulID, + "easy pulIDApplyADV": applyPulIDADV, + "easy styleAlignedBatchAlign": styleAlignedBatchAlign, + "easy icLightApply": icLightApply, + # "easy ominiControlApply": applyOminiControl, + # Inpaint 内补 + "easy applyFooocusInpaint": applyFooocusInpaint, + "easy applyBrushNet": applyBrushNet, + "easy applyPowerPaint": applyPowerPaint, + "easy applyInpaint": applyInpaint, + # latent 潜空间 + "easy latentNoisy": latentNoisy, + "easy latentCompositeMaskedWithCond": latentCompositeMaskedWithCond, + "easy injectNoiseToLatent": injectNoiseToLatent, + # preSampling 预采样处理 + "easy preSampling": samplerSettings, + "easy preSamplingAdvanced": samplerSettingsAdvanced, + "easy preSamplingNoiseIn": samplerSettingsNoiseIn, + "easy preSamplingCustom": samplerCustomSettings, + "easy preSamplingSdTurbo": sdTurboSettings, + "easy preSamplingDynamicCFG": dynamicCFGSettings, + "easy preSamplingCascade": cascadeSettings, + "easy preSamplingLayerDiffusion": layerDiffusionSettings, + "easy preSamplingLayerDiffusionADDTL": layerDiffusionSettingsADDTL, + # kSampler k采样器 + "easy fullkSampler": samplerFull, + "easy kSampler": samplerSimple, + "easy kSamplerCustom": samplerSimpleCustom, + "easy kSamplerTiled": samplerSimpleTiled, + "easy kSamplerLayerDiffusion": samplerSimpleLayerDiffusion, + "easy kSamplerInpainting": samplerSimpleInpainting, + "easy kSamplerDownscaleUnet": samplerSimpleDownscaleUnet, + "easy kSamplerSDTurbo": samplerSDTurbo, + "easy fullCascadeKSampler": samplerCascadeFull, + "easy cascadeKSampler": samplerCascadeSimple, + "easy unSampler": unsampler, + # fix 修复相关 + "easy hiresFix": hiresFix, + "easy preDetailerFix": preDetailerFix, + "easy preMaskDetailerFix": preMaskDetailerFix, + "easy ultralyticsDetectorPipe": ultralyticsDetectorForDetailerFix, + "easy samLoaderPipe": samLoaderForDetailerFix, + "easy detailerFix": detailerFix, + # pipe 管道(节点束) + "easy pipeIn": pipeIn, + "easy pipeOut": pipeOut, + "easy pipeEdit": pipeEdit, + "easy pipeEditPrompt": pipeEditPrompt, + "easy pipeToBasicPipe": pipeToBasicPipe, + "easy pipeBatchIndex": pipeBatchIndex, + "easy XYPlot": pipeXYPlot, + "easy XYPlotAdvanced": pipeXYPlotAdvanced, + # XY Inputs + "easy XYInputs: Seeds++ Batch": XYplot_SeedsBatch, + "easy XYInputs: Steps": XYplot_Steps, + "easy XYInputs: CFG Scale": XYplot_CFG, + "easy XYInputs: FluxGuidance": XYplot_FluxGuidance, + "easy XYInputs: Sampler/Scheduler": XYplot_Sampler_Scheduler, + "easy XYInputs: Denoise": XYplot_Denoise, + "easy XYInputs: Checkpoint": XYplot_Checkpoint, + "easy XYInputs: Lora": XYplot_Lora, + "easy XYInputs: ModelMergeBlocks": XYplot_ModelMergeBlocks, + "easy XYInputs: PromptSR": XYplot_PromptSR, + "easy XYInputs: ControlNet": XYplot_Control_Net, + "easy XYInputs: PositiveCond": XYplot_Positive_Cond, + "easy XYInputs: PositiveCondList": XYplot_Positive_Cond_List, + "easy XYInputs: NegativeCond": XYplot_Negative_Cond, + "easy XYInputs: NegativeCondList": XYplot_Negative_Cond_List, + # others 其他 + "easy showSpentTime": showSpentTime, + "easy showLoaderSettingsNames": showLoaderSettingsNames, + "easy sliderControl": sliderControl, + "dynamicThresholdingFull": dynamicThresholdingFull, + # api 相关 + "easy stableDiffusion3API": stableDiffusion3API, + "easy fluxPromptGenAPI": fluxPromptGenAPI, + # utils + "easy ckptNames": setCkptName, + "easy controlnetNames": setControlName, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + # seed 随机种 + "easy seed": "EasySeed", + "easy globalSeed": "EasyGlobalSeed", + # prompt 提示词 + "easy positive": "Positive", + "easy negative": "Negative", + "easy wildcards": "Wildcards", + "easy prompt": "Prompt", + "easy promptList": "PromptList", + "easy promptLine": "PromptLine", + "easy promptConcat": "PromptConcat", + "easy promptReplace": "PromptReplace", + "easy stylesSelector": "Styles Selector", + "easy portraitMaster": "Portrait Master", + # loaders 加载器 + "easy fullLoader": "EasyLoader (Full)", + "easy a1111Loader": "EasyLoader (A1111)", + "easy comfyLoader": "EasyLoader (Comfy)", + "easy svdLoader": "EasyLoader (SVD)", + "easy sv3dLoader": "EasyLoader (SV3D)", + "easy zero123Loader": "EasyLoader (Zero123)", + "easy dynamiCrafterLoader": "EasyLoader (DynamiCrafter)", + "easy cascadeLoader": "EasyCascadeLoader", + "easy kolorsLoader": "EasyLoader (Kolors)", + "easy fluxLoader": "EasyLoader (Flux)", + "easy hunyuanDiTLoader": "EasyLoader (HunyuanDiT)", + "easy pixArtLoader": "EasyLoader (PixArt)", + "easy mochiLoader": "EasyLoader (Mochi)", + "easy loraStack": "EasyLoraStack", + "easy controlnetStack": "EasyControlnetStack", + "easy controlnetLoader": "EasyControlnet", + "easy controlnetLoaderADV": "EasyControlnet (Advanced)", + "easy controlnetLoader++": "EasyControlnet++", + "easy LLLiteLoader": "EasyLLLite", + # Adapter 适配器 + "easy loraStackApply": "Easy Apply LoraStack", + "easy controlnetStackApply": "Easy Apply CnetStack", + "easy ipadapterApply": "Easy Apply IPAdapter", + "easy ipadapterApplyADV": "Easy Apply IPAdapter (Advanced)", + "easy ipadapterApplyFaceIDKolors": "Easy Apply IPAdapter (FaceID Kolors)", + "easy ipadapterStyleComposition": "Easy Apply IPAdapter (StyleComposition)", + "easy ipadapterApplyEncoder": "Easy Apply IPAdapter (Encoder)", + "easy ipadapterApplyRegional": "Easy Apply IPAdapter (Regional)", + "easy ipadapterApplyEmbeds": "Easy Apply IPAdapter (Embeds)", + "easy ipadapterApplyFromParams": "Easy Apply IPAdapter (From Params)", + "easy instantIDApply": "Easy Apply InstantID", + "easy instantIDApplyADV": "Easy Apply InstantID (Advanced)", + "easy pulIDApply": "Easy Apply PuLID", + "easy pulIDApplyADV": "Easy Apply PuLID (Advanced)", + "easy styleAlignedBatchAlign": "Easy Apply StyleAlign", + "easy icLightApply": "Easy Apply ICLight", + "easy ominiControlApply": "Easy Apply OminiContol", + # Inpaint 内补 + "easy applyFooocusInpaint": "Easy Apply Fooocus Inpaint", + "easy applyBrushNet": "Easy Apply BrushNet", + "easy applyPowerPaint": "Easy Apply PowerPaint", + "easy applyInpaint": "Easy Apply Inpaint", + # latent 潜空间 + "easy latentNoisy": "LatentNoisy", + "easy latentCompositeMaskedWithCond": "LatentCompositeMaskedWithCond", + "easy injectNoiseToLatent": "InjectNoiseToLatent", + # preSampling 预采样处理 + "easy preSampling": "PreSampling", + "easy preSamplingAdvanced": "PreSampling (Advanced)", + "easy preSamplingNoiseIn": "PreSampling (NoiseIn)", + "easy preSamplingCustom": "PreSampling (Custom)", + "easy preSamplingSdTurbo": "PreSampling (SDTurbo)", + "easy preSamplingDynamicCFG": "PreSampling (DynamicCFG)", + "easy preSamplingCascade": "PreSampling (Cascade)", + "easy preSamplingLayerDiffusion": "PreSampling (LayerDiffuse)", + "easy preSamplingLayerDiffusionADDTL": "PreSampling (LayerDiffuse ADDTL)", + # kSampler k采样器 + "easy kSampler": "EasyKSampler", + "easy kSamplerCustom": "EasyKSampler (Custom)", + "easy fullkSampler": "EasyKSampler (Full)", + "easy kSamplerTiled": "EasyKSampler (Tiled Decode)", + "easy kSamplerLayerDiffusion": "EasyKSampler (LayerDiffuse)", + "easy kSamplerInpainting": "EasyKSampler (Inpainting)", + "easy kSamplerDownscaleUnet": "EasyKsampler (Downscale Unet)", + "easy kSamplerSDTurbo": "EasyKSampler (SDTurbo)", + "easy cascadeKSampler": "EasyCascadeKsampler", + "easy fullCascadeKSampler": "EasyCascadeKsampler (Full)", + "easy unSampler": "EasyUnSampler", + # fix 修复相关 + "easy hiresFix": "HiresFix", + "easy preDetailerFix": "PreDetailerFix", + "easy preMaskDetailerFix": "preMaskDetailerFix", + "easy ultralyticsDetectorPipe": "UltralyticsDetector (Pipe)", + "easy samLoaderPipe": "SAMLoader (Pipe)", + "easy detailerFix": "DetailerFix", + # pipe 管道(节点束) + "easy pipeIn": "Pipe In", + "easy pipeOut": "Pipe Out", + "easy pipeEdit": "Pipe Edit", + "easy pipeEditPrompt": "Pipe Edit Prompt", + "easy pipeBatchIndex": "Pipe Batch Index", + "easy pipeToBasicPipe": "Pipe -> BasicPipe", + "easy XYPlot": "XY Plot", + "easy XYPlotAdvanced": "XY Plot Advanced", + # XY Inputs + "easy XYInputs: Seeds++ Batch": "XY Inputs: Seeds++ Batch //EasyUse", + "easy XYInputs: Steps": "XY Inputs: Steps //EasyUse", + "easy XYInputs: CFG Scale": "XY Inputs: CFG Scale //EasyUse", + "easy XYInputs: FluxGuidance": "XY Inputs: Flux Guidance //EasyUse", + "easy XYInputs: Sampler/Scheduler": "XY Inputs: Sampler/Scheduler //EasyUse", + "easy XYInputs: Denoise": "XY Inputs: Denoise //EasyUse", + "easy XYInputs: Checkpoint": "XY Inputs: Checkpoint //EasyUse", + "easy XYInputs: Lora": "XY Inputs: Lora //EasyUse", + "easy XYInputs: ModelMergeBlocks": "XY Inputs: ModelMergeBlocks //EasyUse", + "easy XYInputs: PromptSR": "XY Inputs: PromptSR //EasyUse", + "easy XYInputs: ControlNet": "XY Inputs: Controlnet //EasyUse", + "easy XYInputs: PositiveCond": "XY Inputs: PosCond //EasyUse", + "easy XYInputs: PositiveCondList": "XY Inputs: PosCondList //EasyUse", + "easy XYInputs: NegativeCond": "XY Inputs: NegCond //EasyUse", + "easy XYInputs: NegativeCondList": "XY Inputs: NegCondList //EasyUse", + # others 其他 + "easy showSpentTime": "Show Spent Time", + "easy showLoaderSettingsNames": "Show Loader Settings Names", + "easy sliderControl": "Easy Slider Control", + "dynamicThresholdingFull": "DynamicThresholdingFull", + # api 相关 + "easy stableDiffusion3API": "Stable Diffusion 3 (API)", + "easy fluxPromptGenAPI": "Flux Prompt Gen (API)", + # utils + "easy ckptNames": "Ckpt Names", + "easy controlnetNames": "ControlNet Names", } \ No newline at end of file diff --git a/py/fooocus/__init__.py b/py/fooocus/__init__.py index dd60c0b..68414d9 100644 --- a/py/fooocus/__init__.py +++ b/py/fooocus/__init__.py @@ -48,7 +48,7 @@ def calculate_weight_patched(self, patches, weight, key, intermediate_dtype=torc ) if len(remaining) > 0: - return original_calculate_weight(remaining, weight, key, intermediate_dtype) + return self.original_calculate_weight(remaining, weight, key, intermediate_dtype) return weight def __enter__(self): diff --git a/py/image.py b/py/image.py index 16fd265..d2a79a8 100644 --- a/py/image.py +++ b/py/image.py @@ -798,7 +798,7 @@ def INPUT_TYPES(self): return { "required": { "images": ("IMAGE",), - "rem_mode": (("RMBG-1.4","Inspyrenet"),), + "rem_mode": (("RMBG-2.0", "RMBG-1.4","Inspyrenet"), {"default": "RMBG-1.4"}), "image_output": (["Hide", "Preview", "Save", "Hide/Save"], {"default": "Preview"}), "save_prefix": ("STRING", {"default": "ComfyUI"}), @@ -819,7 +819,50 @@ def INPUT_TYPES(self): def remove(self, rem_mode, images, image_output, save_prefix, torchscript_jit=False, prompt=None, extra_pnginfo=None): new_images = list() masks = list() - if rem_mode == "RMBG-1.4": + if rem_mode == "RMBG-2.0": + repo_id = REMBG_MODELS[rem_mode]['model_url'] + model_path = os.path.join(REMBG_DIR, 'RMBG-2.0') + if not os.path.exists(model_path): + from huggingface_hub import snapshot_download + snapshot_download(repo_id=repo_id, local_dir=model_path, ignore_patterns=["*.md", "*.txt"]) + from transformers import AutoModelForImageSegmentation + model = AutoModelForImageSegmentation.from_pretrained(model_path, trust_remote_code=True) + torch.set_float32_matmul_precision('high') + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model.to(device) + model.eval() + + from torchvision import transforms + transform_image = transforms.Compose([ + transforms.Resize((1024, 1024)), + transforms.ToTensor(), + transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) + ]) + for image in images: + orig_im = tensor2pil(image) + input_tensor = transform_image(orig_im).unsqueeze(0).to(device) + + with torch.no_grad(): + preds = model(input_tensor)[-1].sigmoid().cpu() + pred = preds[0].squeeze() + + mask = transforms.ToPILImage()(pred) + mask = mask.resize(orig_im.size) + + new_im = orig_im.copy() + new_im.putalpha(mask) + + new_im_tensor = pil2tensor(new_im) + mask_tensor = pil2tensor(mask) + + new_images.append(new_im_tensor) + masks.append(mask_tensor) + + torch.cuda.empty_cache() + new_images = torch.cat(new_images, dim=0) + masks = torch.cat(masks, dim=0) + + elif rem_mode == "RMBG-1.4": # load model model_url = REMBG_MODELS[rem_mode]['model_url'] suffix = model_url.split(".")[-1] @@ -916,6 +959,7 @@ def tensor_bundle(self, tensor_in: torch.Tensor, picks): def chooser(self, prompt=None, my_unique_id=None, extra_pnginfo=None, **kwargs): id = my_unique_id[0] + id = id.split('.')[len(id.split('.')) - 1] if "." in id else id if id not in ChooserMessage.stash: ChooserMessage.stash[id] = {} my_stash = ChooserMessage.stash[id] @@ -1286,12 +1330,18 @@ def parsing(self, image, confidence, method, crop_multi, prompt=None, my_unique_ model_path = get_local_filepath(HUMANPARSING_MODELS['human-parts']['model_url'], human_parts_path) parsing = HumanParts(model_path=model_path) - mask, = parsing(image, mask_components) - - alpha = 1.0 - mask + ret_images = [] + ret_masks = [] + for img in image: + mask, = parsing(img, mask_components) + _mask = tensor2pil(mask).convert('L') - output_image, = JoinImageWithAlpha().join_image_with_alpha(image, alpha) + ret_image = RGB2RGBA(tensor2pil(img).convert('RGB'), _mask.convert('L')) + ret_images.append(pil2tensor(ret_image)) + ret_masks.append(image2mask(_mask)) + output_image = torch.cat(ret_images, dim=0) + mask = torch.cat(ret_masks, dim=0) # use crop bbox = [[0, 0, 0, 0]] @@ -1861,6 +1911,107 @@ def save(self, images, filename_prefix, save_metadata, prompt=None, extra_pnginf return {"ui": {"images": results} , "result": (images,)} + +class makeImageForICRepaint: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image_1": ("IMAGE",), + "direction": (["top-bottom", "left-right"], {"default": "left-right"}), + "pixels": ("INT", {"default": 0, "max": MAX_RESOLUTION, "min": 0, "step": 8, "tooltip": "The pixel of the output image is not set when it is 0"}), + }, + "optional": { + "image_2": ("IMAGE",), + "mask_1": ("MASK",), + "mask_2": ("MASK",), + }, + } + + DESCRIPTION = "make Image for ICLora to Re-paint" + CATEGORY = "EasyUse/Image" + FUNCTION = "make" + + RETURN_TYPES = ("IMAGE", "MASK", "MASK", "INT", "INT", "INT", "INT") + RETURN_NAMES = ("image", "mask", "context_mask", "width", "height", "x", "y") + + def fillMask(self, width, height, mask, box=(0, 0), color=0): + bg = Image.new("L", (width, height), color) + bg.paste(mask, box, mask) + return bg + + def emptyImage(self, width, height, batch_size=1, color=0): + r = torch.full([batch_size, height, width, 1], ((color >> 16) & 0xFF) / 0xFF) + g = torch.full([batch_size, height, width, 1], ((color >> 8) & 0xFF) / 0xFF) + b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF) + return torch.cat((r, g, b), dim=-1) + + def make(self, image_1, direction, pixels=0, image_2=None, mask_1=None, mask_2=None): + if image_2 is None: + image_2 = self.emptyImage(image_1.shape[2], image_1.shape[1]) + mask_2 = torch.full((1, image_1.shape[1], image_1.shape[2]), 1, dtype=torch.float32, device="cpu") + + elif image_2 is not None and mask_2 is None: + raise ValueError("mask_2 is required when image_2 is provided") + if pixels > 0: + _, img2_h, img2_w, _ = image_2.shape + h = pixels if direction == 'left-right' else int(img2_h * (pixels / img2_w)) + w = pixels if direction == 'top-bottom' else int(img2_w * (pixels / img2_h)) + + image_2 = image_2.movedim(-1, 1) + image_2 = comfy.utils.common_upscale(image_2, w, h, 'bicubic', 'disabled') + image_2 = image_2.movedim(1, -1) + + orig_image_2 = tensor2pil(image_2) + orig_mask_2 = tensor2pil(mask_2).convert('L') + orig_mask_2 = orig_mask_2.resize(orig_image_2.size) + mask_2 = pil2tensor(orig_mask_2) + + _, img1_h, img1_w, _ = image_1.shape + _, img2_h, img2_w, _ = image_2.shape + + image, mask, context_mask = None, None, None + + # resize + if img1_h != img2_h and img1_w != img2_w: + width, height = img2_w, img2_h + if direction == 'left-right' and img1_h != img2_h: + scale_factor = img2_h / img1_h + width = round(img1_w * scale_factor) + elif direction == 'top-bottom' and img1_w != img2_w: + scale_factor = img2_w / img1_w + height = round(img1_h * scale_factor) + + image_1 = image_1.movedim(-1, 1) + image_1 = comfy.utils.common_upscale(image_1, width, height, 'bicubic', 'disabled') + image_1 = image_1.movedim(1, -1) + + if mask_1 is None: + mask_1 = torch.full((1, image_1.shape[1], image_1.shape[2]), 0, dtype=torch.float32, device="cpu") + + orig_image_1 = tensor2pil(image_1) + orig_mask_1 = tensor2pil(mask_1).convert('L') + + if orig_mask_1.size != orig_image_1.size: + orig_mask_1 = orig_mask_1.resize(orig_image_1.size) + + img1_w, img1_h = orig_image_1.size + image_1 = pil2tensor(orig_image_1) + image = torch.cat((image_1, image_2), dim=2) if direction == 'left-right' else torch.cat((image_1, image_2), + dim=1) + + context_mask = self.fillMask(image.shape[2], image.shape[1], orig_mask_1) + context_mask = pil2tensor(context_mask) + + orig_mask_2 = tensor2pil(mask_2).convert('L') + x = img1_w if direction == 'left-right' else 0 + y = img1_h if direction == 'top-bottom' else 0 + mask = self.fillMask(image.shape[2], image.shape[1], orig_mask_2, (x, y)) + mask = pil2tensor(mask) + + return (image, mask, context_mask, img2_w, img2_h, x, y) + + NODE_CLASS_MAPPINGS = { "easy imageInsetCrop": imageInsetCrop, "easy imageCount": imageCount, @@ -1896,6 +2047,7 @@ def save(self, images, filename_prefix, save_metadata, prompt=None, extra_pnginf "easy humanSegmentation": humanSegmentation, "easy removeLocalImage": removeLocalImage, "easy saveImageLazy": saveImageLazy, + "easy makeImageForICLora": makeImageForICRepaint } NODE_DISPLAY_NAME_MAPPINGS = { @@ -1934,4 +2086,5 @@ def save(self, images, filename_prefix, save_metadata, prompt=None, extra_pnginf "easy humanSegmentation": "Human Segmentation", "easy removeLocalImage": "Remove Local Image", "easy saveImageLazy": "Save Image (Lazy)", + "easy makeImageForICLora": "Make Image For ICLora" } \ No newline at end of file diff --git a/py/ipadapter/__init__.py b/py/ipadapter/__init__.py new file mode 100644 index 0000000..01f1d98 --- /dev/null +++ b/py/ipadapter/__init__.py @@ -0,0 +1,268 @@ +#credit to shakker-labs and instantX for this module +#from https://github.com/Shakker-Labs/ComfyUI-IPAdapter-Flux +import torch +from PIL import Image +import numpy as np +from .attention_processor import IPAFluxAttnProcessor2_0 +from .utils import is_model_pathched, FluxUpdateModules +from .sd3.resampler import TimeResampler +from .sd3.joinblock import JointBlockIPWrapper, IPAttnProcessor + +image_proj_model = None +class MLPProjModel(torch.nn.Module): + def __init__(self, cross_attention_dim=768, id_embeddings_dim=512, num_tokens=4): + super().__init__() + + self.cross_attention_dim = cross_attention_dim + self.num_tokens = num_tokens + + self.proj = torch.nn.Sequential( + torch.nn.Linear(id_embeddings_dim, id_embeddings_dim * 2), + torch.nn.GELU(), + torch.nn.Linear(id_embeddings_dim * 2, cross_attention_dim * num_tokens), + ) + self.norm = torch.nn.LayerNorm(cross_attention_dim) + + def forward(self, id_embeds): + x = self.proj(id_embeds) + x = x.reshape(-1, self.num_tokens, self.cross_attention_dim) + x = self.norm(x) + return x + +class InstantXFluxIpadapterApply: + def __init__(self, num_tokens=128): + self.device = None + self.dtype = torch.float16 + self.num_tokens = num_tokens + self.ip_ckpt = None + self.clip_vision = None + self.image_encoder = None + self.clip_image_processor = None + # state_dict + self.state_dict = None + self.joint_attention_dim = 4096 + self.hidden_size = 3072 + + def set_ip_adapter(self, flux_model, weight, timestep_percent_range=(0.0, 1.0)): + s = flux_model.model_sampling + percent_to_timestep_function = lambda a: s.percent_to_sigma(a) + timestep_range = (percent_to_timestep_function(timestep_percent_range[0]), + percent_to_timestep_function(timestep_percent_range[1])) + ip_attn_procs = {} # 19+38=57 + dsb_count = len(flux_model.diffusion_model.double_blocks) + for i in range(dsb_count): + name = f"double_blocks.{i}" + ip_attn_procs[name] = IPAFluxAttnProcessor2_0( + hidden_size=self.hidden_size, + cross_attention_dim=self.joint_attention_dim, + num_tokens=self.num_tokens, + scale=weight, + timestep_range=timestep_range + ).to(self.device, dtype=self.dtype) + ssb_count = len(flux_model.diffusion_model.single_blocks) + for i in range(ssb_count): + name = f"single_blocks.{i}" + ip_attn_procs[name] = IPAFluxAttnProcessor2_0( + hidden_size=self.hidden_size, + cross_attention_dim=self.joint_attention_dim, + num_tokens=self.num_tokens, + scale=weight, + timestep_range=timestep_range + ).to(self.device, dtype=self.dtype) + return ip_attn_procs + + def load_ip_adapter(self, flux_model, weight, timestep_percent_range=(0.0, 1.0)): + global image_proj_model + image_proj_model.load_state_dict(self.state_dict["image_proj"], strict=True) + ip_attn_procs = self.set_ip_adapter(flux_model, weight, timestep_percent_range) + ip_layers = torch.nn.ModuleList(ip_attn_procs.values()) + ip_layers.load_state_dict(self.state_dict["ip_adapter"], strict=True) + return ip_attn_procs + + def get_image_embeds(self, pil_image=None, clip_image_embeds=None): + # outputs = self.clip_vision.encode_image(pil_image) + # clip_image_embeds = outputs['image_embeds'] + # clip_image_embeds = clip_image_embeds.to(self.device, dtype=self.dtype) + # image_prompt_embeds = self.image_proj_model(clip_image_embeds) + if pil_image is not None: + if isinstance(pil_image, Image.Image): + pil_image = [pil_image] + clip_image = self.clip_image_processor(images=pil_image, return_tensors="pt").pixel_values + clip_image_embeds = self.image_encoder( + clip_image.to(self.device, dtype=self.image_encoder.dtype)).pooler_output + clip_image_embeds = clip_image_embeds.to(dtype=self.dtype) + else: + clip_image_embeds = clip_image_embeds.to(self.device, dtype=self.dtype) + global image_proj_model + image_prompt_embeds = image_proj_model(clip_image_embeds) + return image_prompt_embeds + + def apply_ipadapter(self, model, ipadapter, image, weight, start_at, end_at, provider=None, use_tiled=False): + self.device = provider.lower() + if "clipvision" in ipadapter: + # self.clip_vision = ipadapter["clipvision"]['model'] + self.image_encoder = ipadapter["clipvision"]['model']['image_encoder'].to(self.device, dtype=self.dtype) + self.clip_image_processor = ipadapter["clipvision"]['model']['clip_image_processor'] + if "ipadapter" in ipadapter: + self.ip_ckpt = ipadapter["ipadapter"]['file'] + self.state_dict = ipadapter["ipadapter"]['model'] + + # process image + pil_image = image.numpy()[0] * 255.0 + pil_image = Image.fromarray(pil_image.astype(np.uint8)) + # initialize ipadapter + global image_proj_model + if image_proj_model is None: + image_proj_model = MLPProjModel( + cross_attention_dim=self.joint_attention_dim, # 4096 + id_embeddings_dim=1152, + num_tokens=self.num_tokens, + ) + image_proj_model.to(self.device, dtype=self.dtype) + ip_attn_procs = self.load_ip_adapter(model.model, weight, (start_at, end_at)) + # process control image + image_prompt_embeds = self.get_image_embeds(pil_image=pil_image, clip_image_embeds=None) + # set model + is_patched = is_model_pathched(model.model) + bi = model.clone() + FluxUpdateModules(bi, ip_attn_procs, image_prompt_embeds, is_patched) + + return (bi, image) + + +def patch_sd3( + patcher, + ip_procs, + resampler: TimeResampler, + clip_embeds, + weight=1.0, + start=0.0, + end=1.0, +): + """ + Patches a model_sampler to add the ipadapter + """ + mmdit = patcher.model.diffusion_model + timestep_schedule_max = patcher.model.model_config.sampling_settings.get( + "timesteps", 1000 + ) + # hook the model's forward function + # so that when it gets called, we can grab the timestep and send it to the resampler + ip_options = { + "hidden_states": None, + "t_emb": None, + "weight": weight, + } + + def ddit_wrapper(forward, args): + # this is between 0 and 1, so the adapters can calculate start_point and end_point + # actually, do we need to get the sigma value instead? + t_percent = 1 - args["timestep"].flatten()[0].cpu().item() + if start <= t_percent <= end: + batch_size = args["input"].shape[0] // len(args["cond_or_uncond"]) + # if we're only doing cond or only doing uncond, only pass one of them through the resampler + embeds = clip_embeds[args["cond_or_uncond"]] + # slight efficiency optimization todo: pass the embeds through and then afterwards + # repeat to the batch size + embeds = torch.repeat_interleave(embeds, batch_size, dim=0) + # the resampler wants between 0 and MAX_STEPS + timestep = args["timestep"] * timestep_schedule_max + image_emb, t_emb = resampler(embeds, timestep, need_temb=True) + # these will need to be accessible to the IPAdapters + ip_options["hidden_states"] = image_emb + ip_options["t_emb"] = t_emb + else: + ip_options["hidden_states"] = None + ip_options["t_emb"] = None + + return forward(args["input"], args["timestep"], **args["c"]) + + patcher.set_model_unet_function_wrapper(ddit_wrapper) + # patch each dit block + for i, block in enumerate(mmdit.joint_blocks): + wrapper = JointBlockIPWrapper(block, ip_procs[i], ip_options) + patcher.set_model_patch_replace(wrapper, "dit", "double_block", i) + +class InstantXSD3IpadapterApply: + def __init__(self): + self.device = None + self.dtype = torch.float16 + self.clip_image_processor = None + self.image_encoder = None + self.resampler = None + self.procs = None + + @torch.inference_mode() + def encode(self, image): + clip_image = self.clip_image_processor.image_processor(image, return_tensors="pt", do_rescale=False).pixel_values + clip_image_embeds = self.image_encoder( + clip_image.to(self.device, dtype=self.image_encoder.dtype), + output_hidden_states=True, + ).hidden_states[-2] + clip_image_embeds = torch.cat( + [clip_image_embeds, torch.zeros_like(clip_image_embeds)], dim=0 + ) + clip_image_embeds = clip_image_embeds.to(dtype=torch.float16) + return clip_image_embeds + + def apply_ipadapter(self, model, ipadapter, image, weight, start_at, end_at, provider=None, use_tiled=False): + self.device = provider.lower() + if "clipvision" in ipadapter: + self.image_encoder = ipadapter["clipvision"]['model']['image_encoder'].to(self.device, dtype=self.dtype) + self.clip_image_processor = ipadapter["clipvision"]['model']['clip_image_processor'] + if "ipadapter" in ipadapter: + self.ip_ckpt = ipadapter["ipadapter"]['file'] + self.state_dict = ipadapter["ipadapter"]['model'] + + self.resampler = TimeResampler( + dim=1280, + depth=4, + dim_head=64, + heads=20, + num_queries=64, + embedding_dim=1152, + output_dim=2432, + ff_mult=4, + timestep_in_dim=320, + timestep_flip_sin_to_cos=True, + timestep_freq_shift=0, + ) + self.resampler.eval() + self.resampler.to(self.device, dtype=self.dtype) + self.resampler.load_state_dict(self.state_dict["image_proj"]) + + # now we'll create the attention processors + # ip_adapter.keys looks like [0.proj, 0.to_k, ..., 1.proj, 1.to_k, ...] + n_procs = len( + set(x.split(".")[0] for x in self.state_dict["ip_adapter"].keys()) + ) + self.procs = torch.nn.ModuleList( + [ + # this is hardcoded for SD3.5L + IPAttnProcessor( + hidden_size=2432, + cross_attention_dim=2432, + ip_hidden_states_dim=2432, + ip_encoder_hidden_states_dim=2432, + head_dim=64, + timesteps_emb_dim=1280, + ).to(self.device, dtype=torch.float16) + for _ in range(n_procs) + ] + ) + self.procs.load_state_dict(self.state_dict["ip_adapter"]) + + work_model = model.clone() + embeds = self.encode(image) + + patch_sd3( + work_model, + self.procs, + self.resampler, + embeds, + weight, + start_at, + end_at, + ) + + return (work_model, image) \ No newline at end of file diff --git a/py/ipadapter/attention_processor.py b/py/ipadapter/attention_processor.py new file mode 100644 index 0000000..4eb1b15 --- /dev/null +++ b/py/ipadapter/attention_processor.py @@ -0,0 +1,87 @@ +import numbers +from typing import Dict, Optional, Tuple +import torch +import torch.nn as nn +import torch.nn.functional as F +from einops import rearrange + +class RMSNorm(nn.Module): + def __init__(self, dim, eps: float, elementwise_affine: bool = True): + super().__init__() + + self.eps = eps + + if isinstance(dim, numbers.Integral): + dim = (dim,) + + self.dim = torch.Size(dim) + + if elementwise_affine: + self.weight = nn.Parameter(torch.ones(dim)) + else: + self.weight = None + + def forward(self, hidden_states): + input_dtype = hidden_states.dtype + variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True) + hidden_states = hidden_states * torch.rsqrt(variance + self.eps) + + if self.weight is not None: + # convert into half-precision if necessary + if self.weight.dtype in [torch.float16, torch.bfloat16]: + hidden_states = hidden_states.to(self.weight.dtype) + hidden_states = hidden_states * self.weight + else: + hidden_states = hidden_states.to(input_dtype) + + return hidden_states + +class IPAFluxAttnProcessor2_0(nn.Module): + """Attention processor used typically in processing the SD3-like self-attention projections.""" + + def __init__(self, hidden_size, cross_attention_dim=None, scale=1.0, num_tokens=4, timestep_range=None): + super().__init__() + + self.hidden_size = hidden_size # 3072 + self.cross_attention_dim = cross_attention_dim # 4096 + self.scale = scale + self.num_tokens = num_tokens + + self.to_k_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + self.to_v_ip = nn.Linear(cross_attention_dim or hidden_size, hidden_size, bias=False) + + self.norm_added_k = RMSNorm(128, eps=1e-5, elementwise_affine=False) + self.norm_added_v = RMSNorm(128, eps=1e-5, elementwise_affine=False) + self.timestep_range = timestep_range + + def __call__( + self, + num_heads, + query, + image_emb: torch.FloatTensor, + t: torch.FloatTensor + ) -> torch.FloatTensor: + # only apply IPA if timestep is within range + if self.timestep_range is not None: + if t[0] > self.timestep_range[0] or t[0] < self.timestep_range[1]: + return None + # `ip-adapter` projections + ip_hidden_states = image_emb + ip_hidden_states_key_proj = self.to_k_ip(ip_hidden_states) + ip_hidden_states_value_proj = self.to_v_ip(ip_hidden_states) + + ip_hidden_states_key_proj = rearrange(ip_hidden_states_key_proj, 'B L (H D) -> B H L D', H=num_heads) + ip_hidden_states_value_proj = rearrange(ip_hidden_states_value_proj, 'B L (H D) -> B H L D', H=num_heads) + + ip_hidden_states_key_proj = self.norm_added_k(ip_hidden_states_key_proj) + ip_hidden_states_value_proj = self.norm_added_v(ip_hidden_states_value_proj) + + ip_hidden_states = F.scaled_dot_product_attention(query.to(image_emb.device).to(image_emb.dtype), + ip_hidden_states_key_proj, + ip_hidden_states_value_proj, + dropout_p=0.0, is_causal=False) + + ip_hidden_states = rearrange(ip_hidden_states, "B H L D -> B L (H D)", H=num_heads) + ip_hidden_states = ip_hidden_states.to(query.dtype).to(query.device) + + return self.scale * ip_hidden_states \ No newline at end of file diff --git a/py/ipadapter/flux/layers.py b/py/ipadapter/flux/layers.py new file mode 100644 index 0000000..7b5d174 --- /dev/null +++ b/py/ipadapter/flux/layers.py @@ -0,0 +1,132 @@ +import torch +from torch import Tensor, nn + +from .math import attention +from comfy.ldm.flux.layers import DoubleStreamBlock, SingleStreamBlock +from comfy import model_management as mm + +class DoubleStreamBlockIPA(nn.Module): + def __init__(self, original_block: DoubleStreamBlock, ip_adapter, image_emb): + super().__init__() + + mlp_hidden_dim = original_block.img_mlp[0].out_features + mlp_ratio = mlp_hidden_dim / original_block.hidden_size + mlp_hidden_dim = int(original_block.hidden_size * mlp_ratio) + self.num_heads = original_block.num_heads + self.hidden_size = original_block.hidden_size + self.img_mod = original_block.img_mod + self.img_norm1 = original_block.img_norm1 + self.img_attn = original_block.img_attn + + self.img_norm2 = original_block.img_norm2 + self.img_mlp = original_block.img_mlp + + self.txt_mod = original_block.txt_mod + self.txt_norm1 = original_block.txt_norm1 + self.txt_attn = original_block.txt_attn + + self.txt_norm2 = original_block.txt_norm2 + self.txt_mlp = original_block.txt_mlp + + self.ip_adapter = ip_adapter + self.image_emb = image_emb + self.device = mm.get_torch_device() + + def forward(self, img: Tensor, txt: Tensor, vec: Tensor, pe: Tensor, t: Tensor): + img_mod1, img_mod2 = self.img_mod(vec) + txt_mod1, txt_mod2 = self.txt_mod(vec) + + # prepare image for attention + img_modulated = self.img_norm1(img) + img_modulated = (1 + img_mod1.scale) * img_modulated + img_mod1.shift + img_qkv = self.img_attn.qkv(img_modulated) + img_q, img_k, img_v = img_qkv.view(img_qkv.shape[0], img_qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, + 1, 4) + img_q, img_k = self.img_attn.norm(img_q, img_k, img_v) + + # prepare txt for attention + txt_modulated = self.txt_norm1(txt) + txt_modulated = (1 + txt_mod1.scale) * txt_modulated + txt_mod1.shift + txt_qkv = self.txt_attn.qkv(txt_modulated) + txt_q, txt_k, txt_v = txt_qkv.view(txt_qkv.shape[0], txt_qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, + 1, 4) + txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k, txt_v) + + # run actual attention + attn = attention(torch.cat((txt_q, img_q), dim=2), + torch.cat((txt_k, img_k), dim=2), + torch.cat((txt_v, img_v), dim=2), pe=pe) + + txt_attn, img_attn = attn[:, : txt.shape[1]], attn[:, txt.shape[1]:] + + ip_hidden_states = self.ip_adapter(self.num_heads, img_q, self.image_emb, t) + if ip_hidden_states is not None: + ip_hidden_states.to(device=self.device) + img_attn = img_attn + ip_hidden_states + + + # calculate the img bloks + img = img + img_mod1.gate * self.img_attn.proj(img_attn) + img = img + img_mod2.gate * self.img_mlp((1 + img_mod2.scale) * self.img_norm2(img) + img_mod2.shift) + + # calculate the txt bloks + txt += txt_mod1.gate * self.txt_attn.proj(txt_attn) + txt += txt_mod2.gate * self.txt_mlp((1 + txt_mod2.scale) * self.txt_norm2(txt) + txt_mod2.shift) + + if txt.dtype == torch.float16: + txt = torch.nan_to_num(txt, nan=0.0, posinf=65504, neginf=-65504) + + return img, txt + + +class SingleStreamBlockIPA(nn.Module): + """ + A DiT block with parallel linear layers as described in + https://arxiv.org/abs/2302.05442 and adapted modulation interface. + """ + + def __init__(self, original_block: SingleStreamBlock, ip_adapter, image_emb): + super().__init__() + self.hidden_dim = original_block.hidden_size + self.num_heads = original_block.num_heads + self.scale = original_block.scale + + self.mlp_hidden_dim = original_block.mlp_hidden_dim + # qkv and mlp_in + self.linear1 = original_block.linear1 + # proj and mlp_out + self.linear2 = original_block.linear2 + + self.norm = original_block.norm + + self.hidden_size = original_block.hidden_size + self.pre_norm = original_block.pre_norm + + self.mlp_act = original_block.mlp_act + self.modulation = original_block.modulation + + self.ip_adapter = ip_adapter + self.image_emb = image_emb + self.device = mm.get_torch_device() + + def forward(self, x: Tensor, vec: Tensor, pe: Tensor, t: Tensor) -> Tensor: + mod, _ = self.modulation(vec) + x_mod = (1 + mod.scale) * self.pre_norm(x) + mod.shift + qkv, mlp = torch.split(self.linear1(x_mod), [3 * self.hidden_size, self.mlp_hidden_dim], dim=-1) + + q, k, v = qkv.view(qkv.shape[0], qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, 1, 4) + q, k = self.norm(q, k, v) + + # compute attention + attn = attention(q, k, v, pe=pe) + + ip_hidden_states = self.ip_adapter(self.num_heads, q, self.image_emb, t) + if ip_hidden_states is not None: + ip_hidden_states.to(device=self.device) + attn = attn + ip_hidden_states + # compute activation in mlp stream, cat again and run second linear layer + output = self.linear2(torch.cat((attn, self.mlp_act(mlp)), 2)) + x += mod.gate * output + if x.dtype == torch.float16: + x = torch.nan_to_num(x, nan=0.0, posinf=65504, neginf=-65504) + return x \ No newline at end of file diff --git a/py/ipadapter/flux/math.py b/py/ipadapter/flux/math.py new file mode 100644 index 0000000..5038f73 --- /dev/null +++ b/py/ipadapter/flux/math.py @@ -0,0 +1,35 @@ +import torch +from einops import rearrange +from torch import Tensor +from comfy.ldm.modules.attention import optimized_attention +import comfy.model_management + +def attention(q: Tensor, k: Tensor, v: Tensor, pe: Tensor) -> Tensor: + q, k = apply_rope(q, k, pe) + + heads = q.shape[1] + x = optimized_attention(q, k, v, heads, skip_reshape=True) + return x + + +def rope(pos: Tensor, dim: int, theta: int) -> Tensor: + assert dim % 2 == 0 + if comfy.model_management.is_device_mps(pos.device) or comfy.model_management.is_intel_xpu(): + device = torch.device("cpu") + else: + device = pos.device + + scale = torch.linspace(0, (dim - 2) / dim, steps=dim//2, dtype=torch.float64, device=device) + omega = 1.0 / (theta**scale) + out = torch.einsum("...n,d->...nd", pos.to(dtype=torch.float32, device=device), omega) + out = torch.stack([torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1) + out = rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2) + return out.to(dtype=torch.float32, device=pos.device) + + +def apply_rope(xq: Tensor, xk: Tensor, freqs_cis: Tensor): + xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2) + xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2) + xq_out = freqs_cis[..., 0] * xq_[..., 0] + freqs_cis[..., 1] * xq_[..., 1] + xk_out = freqs_cis[..., 0] * xk_[..., 0] + freqs_cis[..., 1] * xk_[..., 1] + return xq_out.reshape(*xq.shape).type_as(xq), xk_out.reshape(*xk.shape).type_as(xk) \ No newline at end of file diff --git a/py/ipadapter/sd3/joinblock.py b/py/ipadapter/sd3/joinblock.py new file mode 100644 index 0000000..95bbf26 --- /dev/null +++ b/py/ipadapter/sd3/joinblock.py @@ -0,0 +1,219 @@ +import torch +from torch import nn +from torch.nn import functional as F +from einops import rearrange + +from comfy.ldm.modules.attention import optimized_attention +from comfy.ldm.modules.diffusionmodules.mmdit import (RMSNorm, JointBlock,) + + +class AdaLayerNorm(nn.Module): + """ + Norm layer adaptive layer norm zero (adaLN-Zero). + + Parameters: + embedding_dim (`int`): The size of each embedding vector. + num_embeddings (`int`): The size of the embeddings dictionary. + """ + + def __init__(self, embedding_dim: int, time_embedding_dim=None, mode="normal"): + super().__init__() + + self.silu = nn.SiLU() + num_params_dict = dict( + zero=6, + normal=2, + ) + num_params = num_params_dict[mode] + self.linear = nn.Linear( + time_embedding_dim or embedding_dim, num_params * embedding_dim, bias=True + ) + self.norm = nn.LayerNorm(embedding_dim, elementwise_affine=False, eps=1e-6) + self.mode = mode + + def forward( + self, + x, + hidden_dtype=None, + emb=None, + ): + emb = self.linear(self.silu(emb)) + if self.mode == "normal": + shift_msa, scale_msa = emb.chunk(2, dim=1) + x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None] + return x + + elif self.mode == "zero": + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk( + 6, dim=1 + ) + x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None] + return x, gate_msa, shift_mlp, scale_mlp, gate_mlp + + +class IPAttnProcessor(nn.Module): + + def __init__( + self, + hidden_size=None, + cross_attention_dim=None, + ip_hidden_states_dim=None, + ip_encoder_hidden_states_dim=None, + head_dim=None, + timesteps_emb_dim=1280, + ): + super().__init__() + + self.norm_ip = AdaLayerNorm( + ip_hidden_states_dim, time_embedding_dim=timesteps_emb_dim + ) + self.to_k_ip = nn.Linear(ip_hidden_states_dim, hidden_size, bias=False) + self.to_v_ip = nn.Linear(ip_hidden_states_dim, hidden_size, bias=False) + self.norm_q = RMSNorm(head_dim, 1e-6) + self.norm_k = RMSNorm(head_dim, 1e-6) + self.norm_ip_k = RMSNorm(head_dim, 1e-6) + + def forward( + self, + ip_hidden_states, + img_query, + img_key=None, + img_value=None, + t_emb=None, + n_heads=1, + ): + if ip_hidden_states is None: + return None + + if not hasattr(self, "to_k_ip") or not hasattr(self, "to_v_ip"): + return None + + # norm ip input + norm_ip_hidden_states = self.norm_ip(ip_hidden_states, emb=t_emb) + + # to k and v + ip_key = self.to_k_ip(norm_ip_hidden_states) + ip_value = self.to_v_ip(norm_ip_hidden_states) + + # reshape + img_query = rearrange(img_query, "b l (h d) -> b h l d", h=n_heads) + img_key = rearrange(img_key, "b l (h d) -> b h l d", h=n_heads) + # note that the image is in a different shape: b l h d + # so we transpose to b h l d + # or do we have to transpose here? + img_value = torch.transpose(img_value, 1, 2) + ip_key = rearrange(ip_key, "b l (h d) -> b h l d", h=n_heads) + ip_value = rearrange(ip_value, "b l (h d) -> b h l d", h=n_heads) + + # norm + img_query = self.norm_q(img_query) + img_key = self.norm_k(img_key) + ip_key = self.norm_ip_k(ip_key) + + # cat img + key = torch.cat([img_key, ip_key], dim=2) + value = torch.cat([img_value, ip_value], dim=2) + + # + ip_hidden_states = F.scaled_dot_product_attention( + img_query, key, value, dropout_p=0.0, is_causal=False + ) + ip_hidden_states = rearrange(ip_hidden_states, "b h l d -> b l (h d)") + ip_hidden_states = ip_hidden_states.to(img_query.dtype) + return ip_hidden_states + + +class JointBlockIPWrapper: + """To be used as a patch_replace with Comfy""" + + def __init__( + self, + original_block: JointBlock, + adapter: IPAttnProcessor, + ip_options=None, + ): + self.original_block = original_block + self.adapter = adapter + if ip_options is None: + ip_options = {} + self.ip_options = ip_options + + def block_mixing(self, context, x, context_block, x_block, c): + """ + Comes from mmdit.py. Modified to add ipadapter attention. + """ + context_qkv, context_intermediates = context_block.pre_attention(context, c) + + if x_block.x_block_self_attn: + x_qkv, x_qkv2, x_intermediates = x_block.pre_attention_x(x, c) + else: + x_qkv, x_intermediates = x_block.pre_attention(x, c) + + qkv = tuple(torch.cat((context_qkv[j], x_qkv[j]), dim=1) for j in range(3)) + + attn = optimized_attention( + qkv[0], + qkv[1], + qkv[2], + heads=x_block.attn.num_heads, + ) + context_attn, x_attn = ( + attn[:, : context_qkv[0].shape[1]], + attn[:, context_qkv[0].shape[1] :], + ) + # if the current timestep is not in the ipadapter enabling range, then the resampler wasn't run + # and the hidden states will be None + if ( + self.ip_options["hidden_states"] is not None + and self.ip_options["t_emb"] is not None + ): + # IP-Adapter + ip_attn = self.adapter( + self.ip_options["hidden_states"], + *x_qkv, + self.ip_options["t_emb"], + x_block.attn.num_heads, + ) + x_attn = x_attn + ip_attn * self.ip_options["weight"] + + # Everything else is unchanged + if not context_block.pre_only: + context = context_block.post_attention(context_attn, *context_intermediates) + + else: + context = None + if x_block.x_block_self_attn: + attn2 = optimized_attention( + x_qkv2[0], + x_qkv2[1], + x_qkv2[2], + heads=x_block.attn2.num_heads, + ) + x = x_block.post_attention_x(x_attn, attn2, *x_intermediates) + else: + x = x_block.post_attention(x_attn, *x_intermediates) + return context, x + + def __call__(self, args, _): + # Code from mmdit.py: + # in this case, we're blocks_replace[("double_block", i)] + # note that although we're passed the original block, + # we can't actually get it from inside its wrapper + # (which would simplify the whole code...) + # ``` + # def block_wrap(args): + # out = {} + # out["txt"], out["img"] = self.joint_blocks[i](args["txt"], args["img"], c=args["vec"]) + # return out + # out = blocks_replace[("double_block", i)]({"img": x, "txt": context, "vec": c_mod}, {"original_block": block_wrap}) + # context = out["txt"] + # x = out["img"] + # ``` + c, x = self.block_mixing( + args["txt"], + args["img"], + self.original_block.context_block, + self.original_block.x_block, + c=args["vec"], + ) + return {"txt": c, "img": x} \ No newline at end of file diff --git a/py/ipadapter/sd3/resampler.py b/py/ipadapter/sd3/resampler.py new file mode 100644 index 0000000..d8b68a0 --- /dev/null +++ b/py/ipadapter/sd3/resampler.py @@ -0,0 +1,385 @@ +# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py +import math + +import torch +import torch.nn as nn +from typing import Optional + + +ACTIVATION_FUNCTIONS = { + "swish": nn.SiLU(), + "silu": nn.SiLU(), + "mish": nn.Mish(), + "gelu": nn.GELU(), + "relu": nn.ReLU(), +} +def get_activation(act_fn: str) -> nn.Module: + """Helper function to get activation function from string. + + Args: + act_fn (str): Name of activation function. + + Returns: + nn.Module: Activation function. + """ + + act_fn = act_fn.lower() + if act_fn in ACTIVATION_FUNCTIONS: + return ACTIVATION_FUNCTIONS[act_fn] + else: + raise ValueError(f"Unsupported activation function: {act_fn}") + +def get_timestep_embedding( + timesteps: torch.Tensor, + embedding_dim: int, + flip_sin_to_cos: bool = False, + downscale_freq_shift: float = 1, + scale: float = 1, + max_period: int = 10000, +): + """ + This matches the implementation in Denoising Diffusion Probabilistic Models: Create sinusoidal timestep embeddings. + + Args + timesteps (torch.Tensor): + a 1-D Tensor of N indices, one per batch element. These may be fractional. + embedding_dim (int): + the dimension of the output. + flip_sin_to_cos (bool): + Whether the embedding order should be `cos, sin` (if True) or `sin, cos` (if False) + downscale_freq_shift (float): + Controls the delta between frequencies between dimensions + scale (float): + Scaling factor applied to the embeddings. + max_period (int): + Controls the maximum frequency of the embeddings + Returns + torch.Tensor: an [N x dim] Tensor of positional embeddings. + """ + assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array" + + half_dim = embedding_dim // 2 + exponent = -math.log(max_period) * torch.arange( + start=0, end=half_dim, dtype=torch.float32, device=timesteps.device + ) + exponent = exponent / (half_dim - downscale_freq_shift) + + emb = torch.exp(exponent) + emb = timesteps[:, None].float() * emb[None, :] + + # scale embeddings + emb = scale * emb + + # concat sine and cosine embeddings + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1) + + # flip sine and cosine embeddings + if flip_sin_to_cos: + emb = torch.cat([emb[:, half_dim:], emb[:, :half_dim]], dim=-1) + + # zero pad + if embedding_dim % 2 == 1: + emb = torch.nn.functional.pad(emb, (0, 1, 0, 0)) + return emb + +class Timesteps(nn.Module): + def __init__(self, num_channels: int, flip_sin_to_cos: bool, downscale_freq_shift: float, scale: int = 1): + super().__init__() + self.num_channels = num_channels + self.flip_sin_to_cos = flip_sin_to_cos + self.downscale_freq_shift = downscale_freq_shift + self.scale = scale + + def forward(self, timesteps): + t_emb = get_timestep_embedding( + timesteps, + self.num_channels, + flip_sin_to_cos=self.flip_sin_to_cos, + downscale_freq_shift=self.downscale_freq_shift, + scale=self.scale, + ) + return t_emb + +class TimestepEmbedding(nn.Module): + def __init__( + self, + in_channels: int, + time_embed_dim: int, + act_fn: str = "silu", + out_dim: int = None, + post_act_fn: Optional[str] = None, + cond_proj_dim=None, + sample_proj_bias=True, + ): + super().__init__() + + self.linear_1 = nn.Linear(in_channels, time_embed_dim, sample_proj_bias) + + if cond_proj_dim is not None: + self.cond_proj = nn.Linear(cond_proj_dim, in_channels, bias=False) + else: + self.cond_proj = None + + self.act = get_activation(act_fn) + + if out_dim is not None: + time_embed_dim_out = out_dim + else: + time_embed_dim_out = time_embed_dim + self.linear_2 = nn.Linear(time_embed_dim, time_embed_dim_out, sample_proj_bias) + + if post_act_fn is None: + self.post_act = None + else: + self.post_act = get_activation(post_act_fn) + + def forward(self, sample, condition=None): + if condition is not None: + sample = sample + self.cond_proj(condition) + sample = self.linear_1(sample) + + if self.act is not None: + sample = self.act(sample) + + sample = self.linear_2(sample) + + if self.post_act is not None: + sample = self.post_act(sample) + return sample + + +# FFN +def FeedForward(dim, mult=4): + inner_dim = int(dim * mult) + return nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, inner_dim, bias=False), + nn.GELU(), + nn.Linear(inner_dim, dim, bias=False), + ) + + +def reshape_tensor(x, heads): + bs, length, width = x.shape + # (bs, length, width) --> (bs, length, n_heads, dim_per_head) + x = x.view(bs, length, heads, -1) + # (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head) + x = x.transpose(1, 2) + # (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head) + x = x.reshape(bs, heads, length, -1) + return x + + +class PerceiverAttention(nn.Module): + def __init__(self, *, dim, dim_head=64, heads=8): + super().__init__() + self.scale = dim_head**-0.5 + self.dim_head = dim_head + self.heads = heads + inner_dim = dim_head * heads + + self.norm1 = nn.LayerNorm(dim) + self.norm2 = nn.LayerNorm(dim) + + self.to_q = nn.Linear(dim, inner_dim, bias=False) + self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False) + self.to_out = nn.Linear(inner_dim, dim, bias=False) + + def forward(self, x, latents, shift=None, scale=None): + """ + Args: + x (torch.Tensor): image features + shape (b, n1, D) + latent (torch.Tensor): latent features + shape (b, n2, D) + """ + x = self.norm1(x) + latents = self.norm2(latents) + + if shift is not None and scale is not None: + latents = latents * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) + + b, l, _ = latents.shape + + q = self.to_q(latents) + kv_input = torch.cat((x, latents), dim=-2) + k, v = self.to_kv(kv_input).chunk(2, dim=-1) + + q = reshape_tensor(q, self.heads) + k = reshape_tensor(k, self.heads) + v = reshape_tensor(v, self.heads) + + # attention + scale = 1 / math.sqrt(math.sqrt(self.dim_head)) + weight = (q * scale) @ (k * scale).transpose( + -2, -1 + ) # More stable with f16 than dividing afterwards + weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype) + out = weight @ v + + out = out.permute(0, 2, 1, 3).reshape(b, l, -1) + + return self.to_out(out) + + +class Resampler(nn.Module): + def __init__( + self, + dim=1024, + depth=8, + dim_head=64, + heads=16, + num_queries=8, + embedding_dim=768, + output_dim=1024, + ff_mult=4, + *args, + **kwargs, + ): + super().__init__() + + self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5) + + self.proj_in = nn.Linear(embedding_dim, dim) + + self.proj_out = nn.Linear(dim, output_dim) + self.norm_out = nn.LayerNorm(output_dim) + + self.layers = nn.ModuleList([]) + for _ in range(depth): + self.layers.append( + nn.ModuleList( + [ + PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads), + FeedForward(dim=dim, mult=ff_mult), + ] + ) + ) + + def forward(self, x): + + latents = self.latents.repeat(x.size(0), 1, 1) + + x = self.proj_in(x) + + for attn, ff in self.layers: + latents = attn(x, latents) + latents + latents = ff(latents) + latents + + latents = self.proj_out(latents) + return self.norm_out(latents) + + +class TimeResampler(nn.Module): + def __init__( + self, + dim=1024, + depth=8, + dim_head=64, + heads=16, + num_queries=8, + embedding_dim=768, + output_dim=1024, + ff_mult=4, + timestep_in_dim=320, + timestep_flip_sin_to_cos=True, + timestep_freq_shift=0, + ): + super().__init__() + + self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5) + + self.proj_in = nn.Linear(embedding_dim, dim) + + self.proj_out = nn.Linear(dim, output_dim) + self.norm_out = nn.LayerNorm(output_dim) + + self.layers = nn.ModuleList([]) + for _ in range(depth): + self.layers.append( + nn.ModuleList( + [ + # msa + PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads), + # ff + FeedForward(dim=dim, mult=ff_mult), + # adaLN + nn.Sequential(nn.SiLU(), nn.Linear(dim, 4 * dim, bias=True)), + ] + ) + ) + + # time + self.time_proj = Timesteps( + timestep_in_dim, timestep_flip_sin_to_cos, timestep_freq_shift + ) + self.time_embedding = TimestepEmbedding(timestep_in_dim, dim, act_fn="silu") + + # adaLN + # self.adaLN_modulation = nn.Sequential( + # nn.SiLU(), + # nn.Linear(timestep_out_dim, 6 * timestep_out_dim, bias=True) + # ) + + def forward(self, x, timestep, need_temb=False): + timestep_emb = self.embedding_time(x, timestep) # bs, dim + + latents = self.latents.repeat(x.size(0), 1, 1) + + x = self.proj_in(x) + x = x + timestep_emb[:, None] + + for attn, ff, adaLN_modulation in self.layers: + shift_msa, scale_msa, shift_mlp, scale_mlp = adaLN_modulation( + timestep_emb + ).chunk(4, dim=1) + latents = attn(x, latents, shift_msa, scale_msa) + latents + + res = latents + for idx_ff in range(len(ff)): + layer_ff = ff[idx_ff] + latents = layer_ff(latents) + if idx_ff == 0 and isinstance(layer_ff, nn.LayerNorm): # adaLN + latents = latents * ( + 1 + scale_mlp.unsqueeze(1) + ) + shift_mlp.unsqueeze(1) + latents = latents + res + + # latents = ff(latents) + latents + + latents = self.proj_out(latents) + latents = self.norm_out(latents) + + if need_temb: + return latents, timestep_emb + else: + return latents + + def embedding_time(self, sample, timestep): + + # 1. time + timesteps = timestep + if not torch.is_tensor(timesteps): + # TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can + # This would be a good case for the `match` statement (Python 3.10+) + is_mps = sample.device.type == "mps" + if isinstance(timestep, float): + dtype = torch.float32 if is_mps else torch.float64 + else: + dtype = torch.int32 if is_mps else torch.int64 + timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device) + elif len(timesteps.shape) == 0: + timesteps = timesteps[None].to(sample.device) + + # broadcast to batch dimension in a way that's compatible with ONNX/Core ML + timesteps = timesteps.expand(sample.shape[0]) + + t_emb = self.time_proj(timesteps) + + # timesteps does not contain any weights and will always return f32 tensors + # but time_embedding might actually be running in fp16. so we need to cast here. + # there might be better ways to encapsulate this. + t_emb = t_emb.to(dtype=sample.dtype) + + emb = self.time_embedding(t_emb, None) + return emb \ No newline at end of file diff --git a/py/ipadapter/utils.py b/py/ipadapter/utils.py new file mode 100644 index 0000000..153effb --- /dev/null +++ b/py/ipadapter/utils.py @@ -0,0 +1,120 @@ +import torch +from torch import Tensor +from .flux.layers import DoubleStreamBlockIPA, SingleStreamBlockIPA +from comfy.ldm.flux.layers import timestep_embedding +from types import MethodType + +def FluxUpdateModules(bi, ip_attn_procs, image_emb, is_patched): + flux_model = bi.model + bi.add_object_patch(f"diffusion_model.forward_orig", MethodType(forward_orig_ipa, flux_model.diffusion_model)) + dsb_count = len(flux_model.diffusion_model.double_blocks) + ssb_count = len(flux_model.diffusion_model.single_blocks) + for i in range(dsb_count): + temp_layer = DoubleStreamBlockIPA( + flux_model.diffusion_model.double_blocks[i], ip_attn_procs[f"double_blocks.{i}"], image_emb) + bi.add_object_patch(f"diffusion_model.double_blocks.{i}",temp_layer) + for i in range(ssb_count): + temp_layer = SingleStreamBlockIPA( + flux_model.diffusion_model.single_blocks[i], ip_attn_procs[f"single_blocks.{i}"], image_emb) + bi.add_object_patch(f"diffusion_model.single_blocks.{i}", temp_layer) + +def is_model_pathched(model): + def test(mod): + if isinstance(mod, DoubleStreamBlockIPA): + return True + else: + for p in mod.children(): + if test(p): + return True + return False + + result = test(model) + return result + +def forward_orig_ipa( + self, + img: Tensor, + img_ids: Tensor, + txt: Tensor, + txt_ids: Tensor, + timesteps: Tensor, + y: Tensor, + guidance: Tensor = None, + control=None, + transformer_options={}, +) -> Tensor: + patches_replace = transformer_options.get("patches_replace", {}) + if img.ndim != 3 or txt.ndim != 3: + raise ValueError("Input img and txt tensors must have 3 dimensions.") + + # running on sequences img + img = self.img_in(img) + vec = self.time_in(timestep_embedding(timesteps, 256).to(img.dtype)) + if self.params.guidance_embed: + if guidance is None: + raise ValueError("Didn't get guidance strength for guidance distilled model.") + vec = vec + self.guidance_in(timestep_embedding(guidance, 256).to(img.dtype)) + + vec = vec + self.vector_in(y[:,:self.params.vec_in_dim]) + txt = self.txt_in(txt) + + ids = torch.cat((txt_ids, img_ids), dim=1) + pe = self.pe_embedder(ids) + + blocks_replace = patches_replace.get("dit", {}) + for i, block in enumerate(self.double_blocks): + if ("double_block", i) in blocks_replace: + def block_wrap(args): + out = {} + if isinstance(block, DoubleStreamBlockIPA): # ipadaper + out["img"], out["txt"] = block(img=args["img"], txt=args["txt"], vec=args["vec"], pe=args["pe"], t=args["timesteps"]) + else: + out["img"], out["txt"] = block(img=args["img"], txt=args["txt"], vec=args["vec"], pe=args["pe"]) + return out + out = blocks_replace[("double_block", i)]({"img": img, "txt": txt, "vec": vec, "pe": pe, "timesteps": timesteps}, {"original_block": block_wrap}) + txt = out["txt"] + img = out["img"] + else: + if isinstance(block, DoubleStreamBlockIPA): # ipadaper + img, txt = block(img=img, txt=txt, vec=vec, pe=pe, t=timesteps) + else: + img, txt = block(img=img, txt=txt, vec=vec, pe=pe) + + if control is not None: # Controlnet + control_i = control.get("input") + if i < len(control_i): + add = control_i[i] + if add is not None: + img += add + + img = torch.cat((txt, img), 1) + + for i, block in enumerate(self.single_blocks): + if ("single_block", i) in blocks_replace: + def block_wrap(args): + out = {} + if isinstance(block, SingleStreamBlockIPA): # ipadaper + out["img"] = block(args["img"], vec=args["vec"], pe=args["pe"], t=args["timesteps"]) + else: + out["img"] = block(args["img"], vec=args["vec"], pe=args["pe"]) + return out + + out = blocks_replace[("single_block", i)]({"img": img, "vec": vec, "pe": pe, "timesteps": timesteps}, {"original_block": block_wrap}) + img = out["img"] + else: + if isinstance(block, SingleStreamBlockIPA): # ipadaper + img = block(img, vec=vec, pe=pe, t=timesteps) + else: + img = block(img, vec=vec, pe=pe) + + if control is not None: # Controlnet + control_o = control.get("output") + if i < len(control_o): + add = control_o[i] + if add is not None: + img[:, txt.shape[1] :, ...] += add + + img = img[:, txt.shape[1] :, ...] + + img = self.final_layer(img, vec) # (N, T, patch_size ** 2 * out_channels) + return img \ No newline at end of file diff --git a/py/libs/sampler.py b/py/libs/sampler.py index da0f2ad..d10baed 100644 --- a/py/libs/sampler.py +++ b/py/libs/sampler.py @@ -1,1044 +1,1053 @@ -import comfy -import comfy.model_management -import comfy.samplers -import torch -import numpy as np -import latent_preview -from nodes import MAX_RESOLUTION -from PIL import Image -from typing import Dict, List, Optional, Tuple, Union, Any -from ..brushnet.model_patch import add_model_patch - -class easySampler: - def __init__(self): - self.last_helds: dict[str, list] = { - "results": [], - "pipe_line": [], - } - self.device = comfy.model_management.intermediate_device() - - @staticmethod - def tensor2pil(image: torch.Tensor) -> Image.Image: - """Convert a torch tensor to a PIL image.""" - return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) - - @staticmethod - def pil2tensor(image: Image.Image) -> torch.Tensor: - """Convert a PIL image to a torch tensor.""" - return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) - - @staticmethod - def enforce_mul_of_64(d): - d = int(d) - if d <= 7: - d = 8 - leftover = d % 8 # 8 is the number of pixels per byte - if leftover != 0: # if the number of pixels is not a multiple of 8 - if (leftover < 4): # if the number of pixels is less than 4 - d -= leftover # remove the leftover pixels - else: # if the number of pixels is more than 4 - d += 8 - leftover # add the leftover pixels - - return int(d) - - @staticmethod - def safe_split(to_split: str, delimiter: str) -> List[str]: - """Split the input string and return a list of non-empty parts.""" - parts = to_split.split(delimiter) - parts = [part for part in parts if part not in ('', ' ', ' ')] - - while len(parts) < 2: - parts.append('None') - return parts - - def emptyLatent(self, resolution, empty_latent_width, empty_latent_height, batch_size=1, compression=0, model_type='sd', video_length=25): - if resolution not in ["自定义 x 自定义", 'width x height (custom)']: - try: - width, height = map(int, resolution.split(' x ')) - empty_latent_width = width - empty_latent_height = height - except ValueError: - raise ValueError("Invalid base_resolution format.") - if model_type == 'sd3': - latent = torch.ones([batch_size, 16, empty_latent_height // 8, empty_latent_width // 8], device=self.device) * 0.0609 - samples = {"samples": latent} - elif model_type == 'mochi': - latent = torch.zeros([batch_size, 12, ((video_length - 1) // 6) + 1, empty_latent_height // 8, empty_latent_width // 8], device=self.device) - samples = {"samples": latent} - elif compression == 0: - latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8], device=self.device) - samples = {"samples": latent} - else: - latent_c = torch.zeros( - [batch_size, 16, empty_latent_height // compression, empty_latent_width // compression]) - latent_b = torch.zeros([batch_size, 4, empty_latent_height // 4, empty_latent_width // 4]) - - samples = ({"samples": latent_c}, {"samples": latent_b}) - return samples - - def prepare_noise(self, latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", - variation_seed=None, variation_strength=None): - """ - creates random noise given a latent image and a seed. - optional arg skip can be used to skip and discard x number of noise generations for a given seed - """ - - latent_size = latent_image.size() - latent_size_1batch = [1, latent_size[1], latent_size[2], latent_size[3]] - - if variation_strength is not None and variation_strength > 0 or incremental_seed_mode.startswith( - "variation str inc"): - if noise_device == "cpu": - variation_generator = torch.manual_seed(variation_seed) - else: - torch.cuda.manual_seed(variation_seed) - variation_generator = None - - variation_latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, - generator=variation_generator, device=noise_device) - else: - variation_latent = None - - def apply_variation(input_latent, strength_up=None): - if variation_latent is None: - return input_latent - else: - strength = variation_strength - - if strength_up is not None: - strength += strength_up - - variation_noise = variation_latent.expand(input_latent.size()[0], -1, -1, -1) - result = (1 - strength) * input_latent + strength * variation_noise - return result - - # method: incremental seed batch noise - if noise_inds is None and incremental_seed_mode == "incremental": - batch_cnt = latent_size[0] - - latents = None - for i in range(batch_cnt): - if noise_device == "cpu": - generator = torch.manual_seed(seed + i) - else: - torch.cuda.manual_seed(seed + i) - generator = None - - latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, - generator=generator, device=noise_device) - - latent = apply_variation(latent) - - if latents is None: - latents = latent - else: - latents = torch.cat((latents, latent), dim=0) - - return latents - - # method: incremental variation batch noise - elif noise_inds is None and incremental_seed_mode.startswith("variation str inc"): - batch_cnt = latent_size[0] - - latents = None - for i in range(batch_cnt): - if noise_device == "cpu": - generator = torch.manual_seed(seed) - else: - torch.cuda.manual_seed(seed) - generator = None - - latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, - generator=generator, device=noise_device) - - step = float(incremental_seed_mode[18:]) - latent = apply_variation(latent, step * i) - - if latents is None: - latents = latent - else: - latents = torch.cat((latents, latent), dim=0) - - return latents - - # method: comfy batch noise - if noise_device == "cpu": - generator = torch.manual_seed(seed) - else: - torch.cuda.manual_seed(seed) - generator = None - - if noise_inds is None: - latents = torch.randn(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, - generator=generator, device=noise_device) - latents = apply_variation(latents) - return latents - - unique_inds, inverse = np.unique(noise_inds, return_inverse=True) - noises = [] - for i in range(unique_inds[-1] + 1): - noise = torch.randn([1] + list(latent_image.size())[1:], dtype=latent_image.dtype, - layout=latent_image.layout, - generator=generator, device=noise_device) - if i in unique_inds: - noises.append(noise) - noises = [noises[i] for i in inverse] - noises = torch.cat(noises, axis=0) - return noises - - def common_ksampler(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0, - disable_noise=False, start_step=None, last_step=None, force_full_denoise=False, - preview_latent=True, disable_pbar=False, noise_device='CPU'): - device = comfy.model_management.get_torch_device() - noise_device = 'cpu' if noise_device == 'CPU' else device - latent_image = latent["samples"] - latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image) - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - preview_format = "JPEG" - if preview_format not in ["JPEG", "PNG"]: - preview_format = "JPEG" - - previewer = False - - if preview_latent: - previewer = latent_preview.get_previewer(device, model.model.latent_format) - - pbar = comfy.utils.ProgressBar(steps) - - def callback(step, x0, x, total_steps): - preview_bytes = None - if previewer: - preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0) - pbar.update_absolute(step + 1, total_steps, preview_bytes) - - if disable_noise: - noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, - device=noise_device) - else: - batch_inds = latent["batch_index"] if "batch_index" in latent else None - noise = self.prepare_noise(latent_image, seed, batch_inds, noise_device=noise_device) - - ####################################################################################### - # add model patch - # brushnet - add_model_patch(model) - ####################################################################################### - samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, - latent_image, - denoise=denoise, disable_noise=disable_noise, start_step=start_step, - last_step=last_step, - force_full_denoise=force_full_denoise, noise_mask=noise_mask, - callback=callback, - disable_pbar=disable_pbar, seed=seed) - out = latent.copy() - out["samples"] = samples - return out - - def custom_ksampler(self, model, seed, steps, cfg, _sampler, sigmas, positive, negative, latent, - disable_noise=False, preview_latent=True, disable_pbar=False, noise_device='CPU'): - - device = comfy.model_management.get_torch_device() - noise_device = 'cpu' if noise_device == 'CPU' else device - - latent_image = latent["samples"] - - if disable_noise: - noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device=noise_device) - else: - batch_inds = latent["batch_index"] if "batch_index" in latent else None - noise = self.prepare_noise(latent_image, seed, batch_inds, noise_device=noise_device) - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - preview_format = "JPEG" - if preview_format not in ["JPEG", "PNG"]: - preview_format = "JPEG" - - previewer = False - - if preview_latent: - previewer = latent_preview.get_previewer(device, model.model.latent_format) - - pbar = comfy.utils.ProgressBar(steps) - - def callback(step, x0, x, total_steps): - preview_bytes = None - if previewer: - preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0) - pbar.update_absolute(step + 1, total_steps, preview_bytes) - - samples = comfy.samplers.sample(model, noise, positive, negative, cfg, device, _sampler, sigmas, latent_image=latent_image, model_options=model.model_options, - denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=seed) - - out = latent.copy() - out["samples"] = samples - return out - - def custom_advanced_ksampler(self, noise, guider, sampler, sigmas, latent_image, preview_latent=False): - latent = latent_image - latent_image = latent["samples"] - latent = latent.copy() - latent_image = comfy.sample.fix_empty_latent_channels(guider.model_patcher, latent_image) - latent["samples"] = latent_image - - noise_mask = None - if "noise_mask" in latent: - noise_mask = latent["noise_mask"] - - x0_output = {} - previewer = False - - model = guider.model_patcher - steps = sigmas.shape[-1] - 1 - if preview_latent: - previewer = latent_preview.get_previewer(model.load_device, model.model.latent_format) - - pbar = comfy.utils.ProgressBar(steps) - - preview_format = "JPEG" - if preview_format not in ["JPEG", "PNG"]: - preview_format = "JPEG" - def callback(step, x0, x, total_steps): - if x0_output is not None: - x0_output["x0"] = x0 - - preview_bytes = None - if previewer: - preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0) - pbar.update_absolute(step + 1, total_steps, preview_bytes) - - disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED - samples = guider.sample(noise.generate_noise(latent), latent_image, sampler, sigmas, denoise_mask=noise_mask, - callback=callback, disable_pbar=disable_pbar, seed=noise.seed) - samples = samples.to(comfy.model_management.intermediate_device()) - - out = latent.copy() - out["samples"] = samples - if "x0" in x0_output: - out_denoised = latent.copy() - out_denoised["samples"] = guider.model_patcher.model.process_latent_out(x0_output["x0"].cpu()) - else: - out_denoised = out - return (out, out_denoised) - - def get_value_by_id(self, key: str, my_unique_id: Any) -> Optional[Any]: - """Retrieve value by its associated ID.""" - try: - for value, id_ in self.last_helds[key]: - if id_ == my_unique_id: - return value - except KeyError: - return None - - def update_value_by_id(self, key: str, my_unique_id: Any, new_value: Any) -> Union[bool, None]: - """Update the value associated with a given ID. Return True if updated, False if appended, None if key doesn't exist.""" - try: - for i, (value, id_) in enumerate(self.last_helds[key]): - if id_ == my_unique_id: - self.last_helds[key][i] = (new_value, id_) - return True - self.last_helds[key].append((new_value, my_unique_id)) - return False - except KeyError: - return False - - def upscale(self, samples, upscale_method, scale_by, crop): - s = samples.copy() - width = self.enforce_mul_of_64(round(samples["samples"].shape[3] * scale_by)) - height = self.enforce_mul_of_64(round(samples["samples"].shape[2] * scale_by)) - - if (width > MAX_RESOLUTION): - width = MAX_RESOLUTION - if (height > MAX_RESOLUTION): - height = MAX_RESOLUTION - - s["samples"] = comfy.utils.common_upscale(samples["samples"], width, height, upscale_method, crop) - return (s,) - - def handle_upscale(self, samples: dict, upscale_method: str, factor: float, crop: bool) -> dict: - """Upscale the samples if the upscale_method is not set to 'None'.""" - if upscale_method != "None": - samples = self.upscale(samples, upscale_method, factor, crop)[0] - return samples - - def init_state(self, my_unique_id: Any, key: str, default: Any) -> Any: - """Initialize the state by either fetching the stored value or setting a default.""" - value = self.get_value_by_id(key, my_unique_id) - if value is not None: - return value - return default - - def get_output(self, pipe: dict,) -> Tuple: - """Return a tuple of various elements fetched from the input pipe dictionary.""" - return ( - pipe, - pipe.get("images"), - pipe.get("model"), - pipe.get("positive"), - pipe.get("negative"), - pipe.get("samples"), - pipe.get("vae"), - pipe.get("clip"), - pipe.get("seed"), - ) - - def get_output_sdxl(self, sdxl_pipe: dict) -> Tuple: - """Return a tuple of various elements fetched from the input sdxl_pipe dictionary.""" - return ( - sdxl_pipe, - sdxl_pipe.get("model"), - sdxl_pipe.get("positive"), - sdxl_pipe.get("negative"), - sdxl_pipe.get("vae"), - sdxl_pipe.get("refiner_model"), - sdxl_pipe.get("refiner_positive"), - sdxl_pipe.get("refiner_negative"), - sdxl_pipe.get("refiner_vae"), - sdxl_pipe.get("samples"), - sdxl_pipe.get("clip"), - sdxl_pipe.get("images"), - sdxl_pipe.get("seed") - ) - -def loglinear_interp(t_steps, num_steps): - """ - Performs log-linear interpolation of a given array of decreasing numbers. - """ - xs = np.linspace(0, 1, len(t_steps)) - ys = np.log(t_steps[::-1]) - - new_xs = np.linspace(0, 1, num_steps) - new_ys = np.interp(new_xs, xs, ys) - - interped_ys = np.exp(new_ys)[::-1].copy() - return interped_ys - -class alignYourStepsScheduler: - - NOISE_LEVELS = { - "SD1": [14.6146412293, 6.4745760956, 3.8636745985, 2.6946151520, 1.8841921177, 1.3943805092, 0.9642583904, - 0.6523686016, 0.3977456272, 0.1515232662, 0.0291671582], - "SDXL": [14.6146412293, 6.3184485287, 3.7681790315, 2.1811480769, 1.3405244945, 0.8620721141, 0.5550693289, - 0.3798540708, 0.2332364134, 0.1114188177, 0.0291671582], - "SVD": [700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002]} - - def get_sigmas(self, model_type, steps, denoise): - - total_steps = steps - if denoise < 1.0: - if denoise <= 0.0: - return (torch.FloatTensor([]),) - total_steps = round(steps * denoise) - - sigmas = self.NOISE_LEVELS[model_type][:] - if (steps + 1) != len(sigmas): - sigmas = loglinear_interp(sigmas, steps + 1) - - sigmas = sigmas[-(total_steps + 1):] - sigmas[-1] = 0 - return (torch.FloatTensor(sigmas),) - - -class gitsScheduler: - - NOISE_LEVELS = { - 0.80: [ - [14.61464119, 7.49001646, 0.02916753], - [14.61464119, 11.54541874, 6.77309084, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 3.07277966, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 2.05039096, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 2.05039096, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, - 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 3.07277966, - 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, - 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, - 5.85520077, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, - 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, - 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, - 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, - 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, - 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.1956799, 1.98035145, 0.86115354, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, - 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.1956799, 1.98035145, 0.86115354, - 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, - 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.07277966, 1.84880662, - 0.83188516, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, - 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.07277966, - 1.84880662, 0.83188516, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, - 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.75677586, - 2.84484982, 1.78698075, 0.803307, 0.02916753], - ], - 0.85: [ - [14.61464119, 7.49001646, 0.02916753], - [14.61464119, 7.49001646, 1.84880662, 0.02916753], - [14.61464119, 11.54541874, 6.77309084, 1.56271636, 0.02916753], - [14.61464119, 11.54541874, 7.11996698, 3.07277966, 1.24153244, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, - 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 4.65472794, 3.07277966, - 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.65472794, - 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, - 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, - 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, - 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, - 7.49001646, 6.14220476, 4.86714602, 3.60512662, 2.6383388, 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, - 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, - 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, - 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, - 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, - 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, - 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, - 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, - 1.56271636, 0.72133851, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, - 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, - 2.45070267, 1.56271636, 0.72133851, 0.02916753], - ], - 0.90: [ - [14.61464119, 6.77309084, 0.02916753], - [14.61464119, 7.49001646, 1.56271636, 0.02916753], - [14.61464119, 7.49001646, 3.07277966, 0.95350921, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.07277966, 1.61558151, 0.69515091, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.11996698, 4.86714602, 3.07277966, 1.61558151, 0.69515091, - 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 2.95596409, 1.61558151, - 0.69515091, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, - 0.57119018, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, - 2.19988537, 1.24153244, 0.57119018, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 5.85520077, 4.45427561, - 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, - 3.75677586, 2.84484982, 1.84880662, 1.08895338, 0.52423614, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, - 4.86714602, 3.75677586, 2.84484982, 1.84880662, 1.08895338, 0.52423614, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.44769001, - 5.58536053, 4.45427561, 3.32507086, 2.45070267, 1.61558151, 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, - 6.44769001, 5.58536053, 4.45427561, 3.32507086, 2.45070267, 1.61558151, 0.95350921, 0.45573691, - 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, - 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, 0.45573691, - 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, - 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, - 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, - 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, - 0.95350921, 0.45573691, 0.02916753], - [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, - 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.19988537, - 1.51179266, 0.89115214, 0.43325692, 0.02916753], - ], - 0.95: [ - [14.61464119, 6.77309084, 0.02916753], - [14.61464119, 6.77309084, 1.56271636, 0.02916753], - [14.61464119, 7.49001646, 2.84484982, 0.89115214, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.36326075, 0.803307, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.91321158, 1.08895338, 0.50118381, - 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, 1.08895338, - 0.50118381, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, - 1.08895338, 0.50118381, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.41535246, - 0.803307, 0.38853383, 0.02916753], - [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.46139455, 2.6383388, 1.84880662, - 1.24153244, 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.46139455, - 2.6383388, 1.84880662, 1.24153244, 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.86714602, 3.75677586, - 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], - [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 6.44769001, 5.58536053, 4.65472794, - 3.60512662, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, - 4.65472794, 3.60512662, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, - 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, - 4.65472794, 3.75677586, 3.07277966, 2.45070267, 1.78698075, 1.24153244, 0.83188516, 0.50118381, 0.22545385, - 0.02916753], - [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, - 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.36326075, 1.72759056, 1.24153244, 0.83188516, 0.50118381, - 0.22545385, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, - 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.36326075, 1.72759056, 1.24153244, 0.83188516, - 0.50118381, 0.22545385, 0.02916753], - [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, - 5.85520077, 5.09240818, 4.45427561, 3.75677586, 3.07277966, 2.45070267, 1.91321158, 1.46270394, 1.05362725, - 0.72133851, 0.43325692, 0.19894916, 0.02916753], - ], - 1.00: [ - [14.61464119, 1.56271636, 0.02916753], - [14.61464119, 6.77309084, 0.95350921, 0.02916753], - [14.61464119, 6.77309084, 2.36326075, 0.803307, 0.02916753], - [14.61464119, 7.11996698, 3.07277966, 1.56271636, 0.59516323, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.41535246, 0.57119018, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, - 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.72133851, - 0.34370604, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.98035145, 1.24153244, - 0.72133851, 0.34370604, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.27973175, 1.51179266, - 0.95350921, 0.54755926, 0.25053367, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, - 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, - 1.61558151, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, - 2.12350607, 1.56271636, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, - 2.19988537, 1.61558151, 1.162866, 0.803307, 0.50118381, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.75677586, 3.07277966, - 2.45070267, 1.84880662, 1.36964464, 1.01931262, 0.72133851, 0.45573691, 0.25053367, 0.09824532, - 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.46139455, - 2.84484982, 2.19988537, 1.67050016, 1.24153244, 0.92192322, 0.64427125, 0.43325692, 0.25053367, 0.09824532, - 0.02916753], - [14.61464119, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.60512662, - 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, 0.22545385, - 0.09824532, 0.02916753], - [14.61464119, 12.2308979, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 5.09240818, 4.26497746, - 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, - 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 12.2308979, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, - 4.26497746, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, - 0.38853383, 0.22545385, 0.09824532, 0.02916753], - ], - 1.05: [ - [14.61464119, 0.95350921, 0.02916753], - [14.61464119, 6.77309084, 0.89115214, 0.02916753], - [14.61464119, 6.77309084, 2.05039096, 0.72133851, 0.02916753], - [14.61464119, 6.77309084, 2.84484982, 1.28281462, 0.52423614, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.803307, 0.34370604, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.56271636, 0.803307, 0.34370604, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.52423614, 0.22545385, - 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.74807048, 0.41087446, - 0.17026083, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.51179266, 0.95350921, 0.59516323, 0.34370604, - 0.13792117, 0.02916753], - [14.61464119, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, 0.72133851, - 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, - 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, - 1.08895338, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.72759056, - 1.24153244, 0.86115354, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, - 1.61558151, 1.162866, 0.83188516, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, - 1.67050016, 1.28281462, 0.95350921, 0.72133851, 0.52423614, 0.34370604, 0.19894916, 0.09824532, - 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.36326075, - 1.84880662, 1.41535246, 1.08895338, 0.83188516, 0.61951244, 0.45573691, 0.32104823, 0.19894916, 0.09824532, - 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.45070267, - 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.57119018, 0.43325692, 0.29807833, 0.19894916, - 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.30717278, 7.11996698, 5.85520077, 4.65472794, 3.60512662, 2.95596409, - 2.45070267, 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.57119018, 0.43325692, 0.29807833, - 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 8.30717278, 7.11996698, 5.85520077, 4.65472794, 3.60512662, 2.95596409, - 2.45070267, 1.98035145, 1.61558151, 1.32549286, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.41087446, - 0.29807833, 0.19894916, 0.09824532, 0.02916753], - ], - 1.10: [ - [14.61464119, 0.89115214, 0.02916753], - [14.61464119, 2.36326075, 0.72133851, 0.02916753], - [14.61464119, 5.85520077, 1.61558151, 0.57119018, 0.02916753], - [14.61464119, 6.77309084, 2.45070267, 1.08895338, 0.45573691, 0.02916753], - [14.61464119, 6.77309084, 2.95596409, 1.56271636, 0.803307, 0.34370604, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.89115214, 0.4783645, 0.19894916, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.08895338, 0.64427125, 0.34370604, 0.13792117, - 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.54755926, 0.27464288, - 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.4783645, 0.25053367, - 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.41535246, 0.95350921, 0.64427125, - 0.41087446, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.61558151, 1.12534678, 0.803307, 0.54755926, - 0.36617002, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.32507086, 2.45070267, 1.72759056, 1.24153244, 0.89115214, - 0.64427125, 0.45573691, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.09240818, 3.60512662, 2.84484982, 2.05039096, 1.51179266, 1.08895338, 0.803307, - 0.59516323, 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.09240818, 3.60512662, 2.84484982, 2.12350607, 1.61558151, 1.24153244, - 0.95350921, 0.72133851, 0.54755926, 0.41087446, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.08895338, - 0.83188516, 0.64427125, 0.50118381, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.91321158, 1.51179266, 1.20157266, - 0.95350921, 0.74807048, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, - 0.02916753], - [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, 1.72759056, - 1.36964464, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.43325692, 0.34370604, 0.25053367, 0.17026083, - 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, - 1.72759056, 1.36964464, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.43325692, 0.34370604, 0.25053367, - 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, - 1.72759056, 1.36964464, 1.08895338, 0.89115214, 0.72133851, 0.59516323, 0.4783645, 0.38853383, 0.29807833, - 0.22545385, 0.17026083, 0.09824532, 0.02916753], - ], - 1.15: [ - [14.61464119, 0.83188516, 0.02916753], - [14.61464119, 1.84880662, 0.59516323, 0.02916753], - [14.61464119, 5.85520077, 1.56271636, 0.52423614, 0.02916753], - [14.61464119, 5.85520077, 1.91321158, 0.83188516, 0.34370604, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.59516323, 0.25053367, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.51179266, 0.803307, 0.41087446, 0.17026083, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.56271636, 0.89115214, 0.50118381, 0.25053367, 0.09824532, - 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.12534678, 0.72133851, 0.43325692, 0.22545385, - 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.07277966, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, 0.19894916, - 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, - 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.36964464, 0.95350921, 0.69515091, 0.4783645, - 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.803307, 0.59516323, - 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.803307, 0.59516323, - 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, - 0.74807048, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.78698075, 1.32549286, 1.01931262, 0.803307, - 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.78698075, 1.32549286, 1.01931262, 0.803307, - 0.64427125, 0.52423614, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, - 0.72133851, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, - 0.72133851, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, - 0.72133851, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - ], - 1.20: [ - [14.61464119, 0.803307, 0.02916753], - [14.61464119, 1.56271636, 0.52423614, 0.02916753], - [14.61464119, 2.36326075, 0.92192322, 0.36617002, 0.02916753], - [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.25053367, 0.02916753], - [14.61464119, 5.85520077, 2.05039096, 0.95350921, 0.45573691, 0.17026083, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.64427125, 0.29807833, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.803307, 0.45573691, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.95350921, 0.59516323, 0.36617002, 0.19894916, - 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.50118381, 0.32104823, - 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.83188516, 0.59516323, 0.41087446, - 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 1.98035145, 1.36964464, 0.95350921, 0.69515091, 0.50118381, - 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.46139455, 2.36326075, 1.56271636, 1.08895338, 0.803307, 0.59516323, 0.45573691, - 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 6.77309084, 3.46139455, 2.45070267, 1.61558151, 1.162866, 0.86115354, 0.64427125, 0.50118381, - 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, - 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, - 0.64427125, 0.50118381, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, - 0.64427125, 0.50118381, 0.41087446, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.20157266, 0.92192322, - 0.72133851, 0.57119018, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, - 0.74807048, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, - 0.74807048, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, - 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.25: [ - [14.61464119, 0.72133851, 0.02916753], - [14.61464119, 1.56271636, 0.50118381, 0.02916753], - [14.61464119, 2.05039096, 0.803307, 0.32104823, 0.02916753], - [14.61464119, 2.36326075, 0.95350921, 0.43325692, 0.17026083, 0.02916753], - [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.51179266, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.36326075, 1.24153244, 0.72133851, 0.41087446, 0.22545385, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.52423614, 0.34370604, 0.19894916, - 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.98595673, 0.64427125, 0.43325692, 0.27464288, - 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.52423614, 0.36617002, - 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.803307, 0.59516323, 0.45573691, 0.34370604, - 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.86115354, 0.64427125, 0.4783645, 0.36617002, - 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.28281462, 0.92192322, 0.69515091, 0.52423614, - 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.72133851, 0.54755926, - 0.43325692, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.72133851, 0.57119018, - 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.74807048, 0.59516323, 0.4783645, - 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.41535246, 1.05362725, 0.803307, 0.61951244, 0.50118381, - 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.41535246, 1.05362725, 0.803307, 0.64427125, 0.52423614, - 0.43325692, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.46270394, 1.08895338, 0.83188516, 0.66947293, - 0.54755926, 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, - 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.30: [ - [14.61464119, 0.72133851, 0.02916753], - [14.61464119, 1.24153244, 0.43325692, 0.02916753], - [14.61464119, 1.56271636, 0.59516323, 0.22545385, 0.02916753], - [14.61464119, 1.84880662, 0.803307, 0.36617002, 0.13792117, 0.02916753], - [14.61464119, 2.36326075, 1.01931262, 0.52423614, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.36964464, 0.74807048, 0.41087446, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.56271636, 0.89115214, 0.54755926, 0.34370604, 0.19894916, 0.09824532, - 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 0.95350921, 0.61951244, 0.41087446, 0.27464288, 0.17026083, - 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.54755926, 0.36617002, 0.25053367, - 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.41535246, 0.92192322, 0.64427125, 0.45573691, 0.34370604, - 0.25053367, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.56271636, 1.01931262, 0.72133851, 0.50118381, 0.36617002, 0.27464288, - 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.05362725, 0.74807048, 0.54755926, 0.41087446, - 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.77538133, 0.57119018, 0.43325692, - 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.59516323, 0.45573691, 0.36617002, - 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.59516323, 0.4783645, 0.38853383, - 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.83188516, 0.64427125, 0.50118381, 0.41087446, - 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.83188516, 0.64427125, 0.52423614, 0.43325692, - 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.78698075, 1.24153244, 0.92192322, 0.72133851, 0.57119018, - 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.84484982, 1.78698075, 1.24153244, 0.92192322, 0.72133851, 0.57119018, 0.4783645, - 0.41087446, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - ], - 1.35: [ - [14.61464119, 0.69515091, 0.02916753], - [14.61464119, 0.95350921, 0.34370604, 0.02916753], - [14.61464119, 1.56271636, 0.57119018, 0.19894916, 0.02916753], - [14.61464119, 1.61558151, 0.69515091, 0.29807833, 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.83188516, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.162866, 0.64427125, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.36964464, 0.803307, 0.50118381, 0.32104823, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.41535246, 0.83188516, 0.54755926, 0.36617002, 0.25053367, 0.17026083, - 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.32104823, 0.22545385, - 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.25053367, - 0.19894916, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 1.01931262, 0.72133851, 0.52423614, 0.38853383, 0.29807833, - 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 1.01931262, 0.72133851, 0.52423614, 0.41087446, 0.32104823, - 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.61558151, 1.05362725, 0.74807048, 0.54755926, 0.43325692, 0.34370604, - 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.72759056, 1.12534678, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.29807833, - 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 3.07277966, 1.72759056, 1.12534678, 0.803307, 0.59516323, 0.4783645, 0.38853383, 0.32104823, - 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.45070267, 1.51179266, 1.01931262, 0.74807048, 0.57119018, 0.45573691, - 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.41087446, - 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.43325692, - 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.45573691, - 0.38853383, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - ], - 1.40: [ - [14.61464119, 0.59516323, 0.02916753], - [14.61464119, 0.95350921, 0.34370604, 0.02916753], - [14.61464119, 1.08895338, 0.43325692, 0.13792117, 0.02916753], - [14.61464119, 1.56271636, 0.64427125, 0.27464288, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753], - [14.61464119, 2.05039096, 0.95350921, 0.54755926, 0.34370604, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.72133851, 0.43325692, 0.27464288, 0.17026083, 0.09824532, - 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.17026083, - 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.52423614, 0.36617002, 0.27464288, 0.19894916, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.38853383, 0.29807833, 0.22545385, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.41535246, 0.86115354, 0.59516323, 0.43325692, 0.32104823, 0.25053367, - 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.27464288, - 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.64427125, 0.4783645, 0.36617002, 0.29807833, 0.25053367, - 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.98595673, 0.69515091, 0.52423614, 0.41087446, 0.34370604, - 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.72133851, 0.54755926, 0.43325692, 0.36617002, - 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.05362725, 0.74807048, 0.57119018, 0.45573691, 0.38853383, - 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.41087446, 0.36617002, - 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.43325692, 0.38853383, - 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.45573691, 0.41087446, - 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - ], - 1.45: [ - [14.61464119, 0.59516323, 0.02916753], - [14.61464119, 0.803307, 0.25053367, 0.02916753], - [14.61464119, 0.95350921, 0.34370604, 0.09824532, 0.02916753], - [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 1.91321158, 0.95350921, 0.57119018, 0.36617002, 0.25053367, 0.17026083, 0.09824532, - 0.02916753], - [14.61464119, 2.19988537, 1.08895338, 0.64427125, 0.41087446, 0.27464288, 0.19894916, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.19894916, - 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.36617002, 0.27464288, 0.22545385, - 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.41087446, 0.32104823, 0.25053367, 0.19894916, - 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.27464288, 0.22545385, - 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.83188516, 0.59516323, 0.45573691, 0.36617002, 0.29807833, - 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.28281462, 0.83188516, 0.59516323, 0.45573691, 0.36617002, 0.32104823, - 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.69515091, 0.52423614, 0.41087446, 0.34370604, - 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.69515091, 0.52423614, 0.43325692, 0.36617002, - 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 0.98595673, 0.72133851, 0.54755926, 0.45573691, 0.38853383, - 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.74807048, 0.57119018, 0.4783645, 0.41087446, 0.36617002, - 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.74807048, 0.59516323, 0.50118381, 0.43325692, - 0.38853383, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, - 0.17026083, 0.13792117, 0.09824532, 0.02916753], - ], - 1.50: [ - [14.61464119, 0.54755926, 0.02916753], - [14.61464119, 0.803307, 0.25053367, 0.02916753], - [14.61464119, 0.86115354, 0.32104823, 0.09824532, 0.02916753], - [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753], - [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753], - [14.61464119, 1.61558151, 0.83188516, 0.52423614, 0.34370604, 0.25053367, 0.17026083, 0.09824532, - 0.02916753], - [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.38853383, 0.27464288, 0.19894916, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.41087446, 0.29807833, 0.22545385, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 1.84880662, 0.95350921, 0.61951244, 0.43325692, 0.32104823, 0.25053367, 0.19894916, - 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.19988537, 1.12534678, 0.72133851, 0.50118381, 0.36617002, 0.27464288, 0.22545385, - 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.19988537, 1.12534678, 0.72133851, 0.50118381, 0.36617002, 0.29807833, 0.25053367, - 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.29807833, 0.25053367, - 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.29807833, 0.27464288, - 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.32104823, 0.29807833, - 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.59516323, 0.45573691, 0.38853383, 0.34370604, 0.32104823, - 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, - 0.02916753], - [14.61464119, 2.45070267, 1.32549286, 0.86115354, 0.64427125, 0.50118381, 0.41087446, 0.36617002, - 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, - 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.36964464, 0.92192322, 0.69515091, 0.54755926, 0.45573691, 0.41087446, - 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - [14.61464119, 2.45070267, 1.41535246, 0.95350921, 0.72133851, 0.57119018, 0.4783645, 0.43325692, 0.38853383, - 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, - 0.13792117, 0.09824532, 0.02916753], - ], - } - - def get_sigmas(self, coeff, steps, denoise): - total_steps = steps - if denoise < 1.0: - if denoise <= 0.0: - return (torch.FloatTensor([]),) - total_steps = round(steps * denoise) - - if steps <= 20: - sigmas = self.NOISE_LEVELS[round(coeff, 2)][steps-2][:] - else: - sigmas = self.NOISE_LEVELS[round(coeff, 2)][-1][:] - sigmas = loglinear_interp(sigmas, steps + 1) - - sigmas = sigmas[-(total_steps + 1):] - sigmas[-1] = 0 +import comfy +import comfy.model_management +import comfy.samplers +import torch +import numpy as np +import latent_preview +from nodes import MAX_RESOLUTION +from PIL import Image +from typing import Dict, List, Optional, Tuple, Union, Any +from ..brushnet.model_patch import add_model_patch + +class easySampler: + def __init__(self): + self.last_helds: dict[str, list] = { + "results": [], + "pipe_line": [], + } + self.device = comfy.model_management.intermediate_device() + + @staticmethod + def tensor2pil(image: torch.Tensor) -> Image.Image: + """Convert a torch tensor to a PIL image.""" + return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) + + @staticmethod + def pil2tensor(image: Image.Image) -> torch.Tensor: + """Convert a PIL image to a torch tensor.""" + return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) + + @staticmethod + def enforce_mul_of_64(d): + d = int(d) + if d <= 7: + d = 8 + leftover = d % 8 # 8 is the number of pixels per byte + if leftover != 0: # if the number of pixels is not a multiple of 8 + if (leftover < 4): # if the number of pixels is less than 4 + d -= leftover # remove the leftover pixels + else: # if the number of pixels is more than 4 + d += 8 - leftover # add the leftover pixels + + return int(d) + + @staticmethod + def safe_split(to_split: str, delimiter: str) -> List[str]: + """Split the input string and return a list of non-empty parts.""" + parts = to_split.split(delimiter) + parts = [part for part in parts if part not in ('', ' ', ' ')] + + while len(parts) < 2: + parts.append('None') + return parts + + def emptyLatent(self, resolution, empty_latent_width, empty_latent_height, batch_size=1, compression=0, model_type='sd', video_length=25): + if resolution not in ["自定义 x 自定义", 'width x height (custom)']: + try: + width, height = map(int, resolution.split(' x ')) + empty_latent_width = width + empty_latent_height = height + except ValueError: + raise ValueError("Invalid base_resolution format.") + if model_type == 'sd3': + latent = torch.ones([batch_size, 16, empty_latent_height // 8, empty_latent_width // 8], device=self.device) * 0.0609 + samples = {"samples": latent} + elif model_type == 'mochi': + latent = torch.zeros([batch_size, 12, ((video_length - 1) // 6) + 1, empty_latent_height // 8, empty_latent_width // 8], device=self.device) + samples = {"samples": latent} + elif compression == 0: + latent = torch.zeros([batch_size, 4, empty_latent_height // 8, empty_latent_width // 8], device=self.device) + samples = {"samples": latent} + else: + latent_c = torch.zeros( + [batch_size, 16, empty_latent_height // compression, empty_latent_width // compression]) + latent_b = torch.zeros([batch_size, 4, empty_latent_height // 4, empty_latent_width // 4]) + + samples = ({"samples": latent_c}, {"samples": latent_b}) + return samples + + def prepare_noise(self, latent_image, seed, noise_inds=None, noise_device="cpu", incremental_seed_mode="comfy", + variation_seed=None, variation_strength=None): + """ + creates random noise given a latent image and a seed. + optional arg skip can be used to skip and discard x number of noise generations for a given seed + """ + + latent_size = latent_image.size() + latent_size_1batch = [1, latent_size[1], latent_size[2], latent_size[3]] + + if variation_strength is not None and variation_strength > 0 or incremental_seed_mode.startswith( + "variation str inc"): + if noise_device == "cpu": + variation_generator = torch.manual_seed(variation_seed) + else: + torch.cuda.manual_seed(variation_seed) + variation_generator = None + + variation_latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, + generator=variation_generator, device=noise_device) + else: + variation_latent = None + + def apply_variation(input_latent, strength_up=None): + if variation_latent is None: + return input_latent + else: + strength = variation_strength + + if strength_up is not None: + strength += strength_up + + variation_noise = variation_latent.expand(input_latent.size()[0], -1, -1, -1) + result = (1 - strength) * input_latent + strength * variation_noise + return result + + # method: incremental seed batch noise + if noise_inds is None and incremental_seed_mode == "incremental": + batch_cnt = latent_size[0] + + latents = None + for i in range(batch_cnt): + if noise_device == "cpu": + generator = torch.manual_seed(seed + i) + else: + torch.cuda.manual_seed(seed + i) + generator = None + + latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, + generator=generator, device=noise_device) + + latent = apply_variation(latent) + + if latents is None: + latents = latent + else: + latents = torch.cat((latents, latent), dim=0) + + return latents + + # method: incremental variation batch noise + elif noise_inds is None and incremental_seed_mode.startswith("variation str inc"): + batch_cnt = latent_size[0] + + latents = None + for i in range(batch_cnt): + if noise_device == "cpu": + generator = torch.manual_seed(seed) + else: + torch.cuda.manual_seed(seed) + generator = None + + latent = torch.randn(latent_size_1batch, dtype=latent_image.dtype, layout=latent_image.layout, + generator=generator, device=noise_device) + + step = float(incremental_seed_mode[18:]) + latent = apply_variation(latent, step * i) + + if latents is None: + latents = latent + else: + latents = torch.cat((latents, latent), dim=0) + + return latents + + # method: comfy batch noise + if noise_device == "cpu": + generator = torch.manual_seed(seed) + else: + torch.cuda.manual_seed(seed) + generator = None + + if noise_inds is None: + latents = torch.randn(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, + generator=generator, device=noise_device) + latents = apply_variation(latents) + return latents + + unique_inds, inverse = np.unique(noise_inds, return_inverse=True) + noises = [] + for i in range(unique_inds[-1] + 1): + noise = torch.randn([1] + list(latent_image.size())[1:], dtype=latent_image.dtype, + layout=latent_image.layout, + generator=generator, device=noise_device) + if i in unique_inds: + noises.append(noise) + noises = [noises[i] for i in inverse] + noises = torch.cat(noises, axis=0) + return noises + + def common_ksampler(self, model, seed, steps, cfg, sampler_name, scheduler, positive, negative, latent, denoise=1.0, + disable_noise=False, start_step=None, last_step=None, force_full_denoise=False, + preview_latent=True, disable_pbar=False, noise_device='CPU'): + device = comfy.model_management.get_torch_device() + noise_device = 'cpu' if noise_device == 'CPU' else device + latent_image = latent["samples"] + latent_image = comfy.sample.fix_empty_latent_channels(model, latent_image) + + noise_mask = None + if "noise_mask" in latent: + noise_mask = latent["noise_mask"] + + preview_format = "JPEG" + if preview_format not in ["JPEG", "PNG"]: + preview_format = "JPEG" + + previewer = False + + if preview_latent: + previewer = latent_preview.get_previewer(device, model.model.latent_format) + + pbar = comfy.utils.ProgressBar(steps) + + def callback(step, x0, x, total_steps): + preview_bytes = None + if previewer: + preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0) + pbar.update_absolute(step + 1, total_steps, preview_bytes) + + if disable_noise: + noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, + device=noise_device) + else: + batch_inds = latent["batch_index"] if "batch_index" in latent else None + noise = self.prepare_noise(latent_image, seed, batch_inds, noise_device=noise_device) + + ####################################################################################### + # add model patch + # brushnet + add_model_patch(model) + ####################################################################################### + samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, + latent_image, + denoise=denoise, disable_noise=disable_noise, start_step=start_step, + last_step=last_step, + force_full_denoise=force_full_denoise, noise_mask=noise_mask, + callback=callback, + disable_pbar=disable_pbar, seed=seed) + out = latent.copy() + out["samples"] = samples + return out + + def custom_ksampler(self, model, seed, steps, cfg, _sampler, sigmas, positive, negative, latent, + disable_noise=False, preview_latent=True, disable_pbar=False, noise_device='CPU'): + + device = comfy.model_management.get_torch_device() + noise_device = 'cpu' if noise_device == 'CPU' else device + + latent_image = latent["samples"] + + if disable_noise: + noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device=noise_device) + else: + batch_inds = latent["batch_index"] if "batch_index" in latent else None + noise = self.prepare_noise(latent_image, seed, batch_inds, noise_device=noise_device) + + noise_mask = None + if "noise_mask" in latent: + noise_mask = latent["noise_mask"] + + preview_format = "JPEG" + if preview_format not in ["JPEG", "PNG"]: + preview_format = "JPEG" + + previewer = False + + if preview_latent: + previewer = latent_preview.get_previewer(device, model.model.latent_format) + + pbar = comfy.utils.ProgressBar(steps) + + def callback(step, x0, x, total_steps): + preview_bytes = None + if previewer: + preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0) + pbar.update_absolute(step + 1, total_steps, preview_bytes) + + samples = comfy.samplers.sample(model, noise, positive, negative, cfg, device, _sampler, sigmas, latent_image=latent_image, model_options=model.model_options, + denoise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=seed) + + out = latent.copy() + out["samples"] = samples + return out + + def custom_advanced_ksampler(self, guider, sampler, sigmas, latent_image, add_noise='enable', seed=0, preview_latent=False): + latent = latent_image + latent_image = latent["samples"] + latent = latent.copy() + latent_image = comfy.sample.fix_empty_latent_channels(guider.model_patcher, latent_image) + latent["samples"] = latent_image + + device = comfy.model_management.get_torch_device() + noise_device = device if add_noise == 'enable (GPU=A1111)' else 'cpu' + + if add_noise == 'disable': + noise = torch.zeros(latent_image.shape, dtype=latent_image.dtype, layout=latent_image.layout, device="cpu") + else: + batch_inds = latent["batch_index"] if "batch_index" in latent else None + noise = self.prepare_noise(latent_image, seed, batch_inds, noise_device=noise_device) + + noise_mask = None + if "noise_mask" in latent: + noise_mask = latent["noise_mask"] + + x0_output = {} + previewer = False + + model = guider.model_patcher + steps = sigmas.shape[-1] - 1 + if preview_latent: + previewer = latent_preview.get_previewer(model.load_device, model.model.latent_format) + + pbar = comfy.utils.ProgressBar(steps) + + preview_format = "JPEG" + if preview_format not in ["JPEG", "PNG"]: + preview_format = "JPEG" + def callback(step, x0, x, total_steps): + if x0_output is not None: + x0_output["x0"] = x0 + + preview_bytes = None + if previewer: + preview_bytes = previewer.decode_latent_to_preview_image(preview_format, x0) + pbar.update_absolute(step + 1, total_steps, preview_bytes) + + disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED + samples = guider.sample(noise, latent_image, sampler, sigmas, denoise_mask=noise_mask, + callback=callback, disable_pbar=disable_pbar, seed=seed) + samples = samples.to(comfy.model_management.intermediate_device()) + + out = latent.copy() + out["samples"] = samples + if "x0" in x0_output: + out_denoised = latent.copy() + out_denoised["samples"] = guider.model_patcher.model.process_latent_out(x0_output["x0"].cpu()) + else: + out_denoised = out + return (out, out_denoised) + + def get_value_by_id(self, key: str, my_unique_id: Any) -> Optional[Any]: + """Retrieve value by its associated ID.""" + try: + for value, id_ in self.last_helds[key]: + if id_ == my_unique_id: + return value + except KeyError: + return None + + def update_value_by_id(self, key: str, my_unique_id: Any, new_value: Any) -> Union[bool, None]: + """Update the value associated with a given ID. Return True if updated, False if appended, None if key doesn't exist.""" + try: + for i, (value, id_) in enumerate(self.last_helds[key]): + if id_ == my_unique_id: + self.last_helds[key][i] = (new_value, id_) + return True + self.last_helds[key].append((new_value, my_unique_id)) + return False + except KeyError: + return False + + def upscale(self, samples, upscale_method, scale_by, crop): + s = samples.copy() + width = self.enforce_mul_of_64(round(samples["samples"].shape[3] * scale_by)) + height = self.enforce_mul_of_64(round(samples["samples"].shape[2] * scale_by)) + + if (width > MAX_RESOLUTION): + width = MAX_RESOLUTION + if (height > MAX_RESOLUTION): + height = MAX_RESOLUTION + + s["samples"] = comfy.utils.common_upscale(samples["samples"], width, height, upscale_method, crop) + return (s,) + + def handle_upscale(self, samples: dict, upscale_method: str, factor: float, crop: bool) -> dict: + """Upscale the samples if the upscale_method is not set to 'None'.""" + if upscale_method != "None": + samples = self.upscale(samples, upscale_method, factor, crop)[0] + return samples + + def init_state(self, my_unique_id: Any, key: str, default: Any) -> Any: + """Initialize the state by either fetching the stored value or setting a default.""" + value = self.get_value_by_id(key, my_unique_id) + if value is not None: + return value + return default + + def get_output(self, pipe: dict,) -> Tuple: + """Return a tuple of various elements fetched from the input pipe dictionary.""" + return ( + pipe, + pipe.get("images"), + pipe.get("model"), + pipe.get("positive"), + pipe.get("negative"), + pipe.get("samples"), + pipe.get("vae"), + pipe.get("clip"), + pipe.get("seed"), + ) + + def get_output_sdxl(self, sdxl_pipe: dict) -> Tuple: + """Return a tuple of various elements fetched from the input sdxl_pipe dictionary.""" + return ( + sdxl_pipe, + sdxl_pipe.get("model"), + sdxl_pipe.get("positive"), + sdxl_pipe.get("negative"), + sdxl_pipe.get("vae"), + sdxl_pipe.get("refiner_model"), + sdxl_pipe.get("refiner_positive"), + sdxl_pipe.get("refiner_negative"), + sdxl_pipe.get("refiner_vae"), + sdxl_pipe.get("samples"), + sdxl_pipe.get("clip"), + sdxl_pipe.get("images"), + sdxl_pipe.get("seed") + ) + +def loglinear_interp(t_steps, num_steps): + """ + Performs log-linear interpolation of a given array of decreasing numbers. + """ + xs = np.linspace(0, 1, len(t_steps)) + ys = np.log(t_steps[::-1]) + + new_xs = np.linspace(0, 1, num_steps) + new_ys = np.interp(new_xs, xs, ys) + + interped_ys = np.exp(new_ys)[::-1].copy() + return interped_ys + +class alignYourStepsScheduler: + + NOISE_LEVELS = { + "SD1": [14.6146412293, 6.4745760956, 3.8636745985, 2.6946151520, 1.8841921177, 1.3943805092, 0.9642583904, + 0.6523686016, 0.3977456272, 0.1515232662, 0.0291671582], + "SDXL": [14.6146412293, 6.3184485287, 3.7681790315, 2.1811480769, 1.3405244945, 0.8620721141, 0.5550693289, + 0.3798540708, 0.2332364134, 0.1114188177, 0.0291671582], + "SVD": [700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002]} + + def get_sigmas(self, model_type, steps, denoise): + + total_steps = steps + if denoise < 1.0: + if denoise <= 0.0: + return (torch.FloatTensor([]),) + total_steps = round(steps * denoise) + + sigmas = self.NOISE_LEVELS[model_type][:] + if (steps + 1) != len(sigmas): + sigmas = loglinear_interp(sigmas, steps + 1) + + sigmas = sigmas[-(total_steps + 1):] + sigmas[-1] = 0 + return (torch.FloatTensor(sigmas),) + + +class gitsScheduler: + + NOISE_LEVELS = { + 0.80: [ + [14.61464119, 7.49001646, 0.02916753], + [14.61464119, 11.54541874, 6.77309084, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 3.07277966, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 2.05039096, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 2.05039096, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 3.07277966, 1.56271636, + 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 3.07277966, + 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 5.85520077, + 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, + 5.85520077, 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, + 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, + 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, + 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, + 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.07277966, 1.56271636, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, + 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.1956799, 1.98035145, 0.86115354, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, + 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.14220476, 4.86714602, 3.1956799, 1.98035145, 0.86115354, + 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, + 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.07277966, 1.84880662, + 0.83188516, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, + 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.07277966, + 1.84880662, 0.83188516, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, + 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.75677586, + 2.84484982, 1.78698075, 0.803307, 0.02916753], + ], + 0.85: [ + [14.61464119, 7.49001646, 0.02916753], + [14.61464119, 7.49001646, 1.84880662, 0.02916753], + [14.61464119, 11.54541874, 6.77309084, 1.56271636, 0.02916753], + [14.61464119, 11.54541874, 7.11996698, 3.07277966, 1.24153244, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.09240818, 2.84484982, 0.95350921, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 5.58536053, 3.1956799, 1.84880662, 0.803307, + 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 4.65472794, 3.07277966, + 1.84880662, 0.803307, 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.65472794, + 3.07277966, 1.84880662, 0.803307, 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, + 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, + 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, + 7.49001646, 6.14220476, 4.65472794, 3.07277966, 1.84880662, 0.803307, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, + 7.49001646, 6.14220476, 4.86714602, 3.60512662, 2.6383388, 1.56271636, 0.72133851, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, + 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, + 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, + 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, 0.72133851, + 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.24142551, + 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, 1.56271636, + 0.72133851, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, + 9.24142551, 8.75849152, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, 2.45070267, + 1.56271636, 0.72133851, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.90732002, 10.31284904, 9.75859547, + 9.24142551, 8.75849152, 8.30717278, 7.88507891, 7.49001646, 6.77309084, 5.85520077, 4.65472794, 3.46139455, + 2.45070267, 1.56271636, 0.72133851, 0.02916753], + ], + 0.90: [ + [14.61464119, 6.77309084, 0.02916753], + [14.61464119, 7.49001646, 1.56271636, 0.02916753], + [14.61464119, 7.49001646, 3.07277966, 0.95350921, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.54230714, 0.89115214, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.07277966, 1.61558151, 0.69515091, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.11996698, 4.86714602, 3.07277966, 1.61558151, 0.69515091, + 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 2.95596409, 1.61558151, + 0.69515091, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.24153244, + 0.57119018, 0.02916753], + [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, + 2.19988537, 1.24153244, 0.57119018, 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 5.85520077, 4.45427561, + 3.1956799, 2.19988537, 1.24153244, 0.57119018, 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 4.86714602, + 3.75677586, 2.84484982, 1.84880662, 1.08895338, 0.52423614, 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.14220476, + 4.86714602, 3.75677586, 2.84484982, 1.84880662, 1.08895338, 0.52423614, 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.44769001, + 5.58536053, 4.45427561, 3.32507086, 2.45070267, 1.61558151, 0.95350921, 0.45573691, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, + 6.44769001, 5.58536053, 4.45427561, 3.32507086, 2.45070267, 1.61558151, 0.95350921, 0.45573691, + 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, + 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, 0.45573691, + 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.30717278, + 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, 0.95350921, + 0.45573691, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, + 8.30717278, 7.49001646, 6.77309084, 5.85520077, 4.86714602, 3.91689563, 3.07277966, 2.27973175, 1.56271636, + 0.95350921, 0.45573691, 0.02916753], + [14.61464119, 13.76078796, 12.96784878, 12.2308979, 11.54541874, 10.31284904, 9.24142551, 8.75849152, + 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.19988537, + 1.51179266, 0.89115214, 0.43325692, 0.02916753], + ], + 0.95: [ + [14.61464119, 6.77309084, 0.02916753], + [14.61464119, 6.77309084, 1.56271636, 0.02916753], + [14.61464119, 7.49001646, 2.84484982, 0.89115214, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.36326075, 0.803307, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.95596409, 1.56271636, 0.64427125, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.91321158, 1.08895338, 0.50118381, + 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, 1.08895338, + 0.50118381, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.91321158, + 1.08895338, 0.50118381, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.19988537, 1.41535246, + 0.803307, 0.38853383, 0.02916753], + [14.61464119, 12.2308979, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.46139455, 2.6383388, 1.84880662, + 1.24153244, 0.72133851, 0.34370604, 0.02916753], + [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.46139455, + 2.6383388, 1.84880662, 1.24153244, 0.72133851, 0.34370604, 0.02916753], + [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 6.14220476, 4.86714602, 3.75677586, + 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], + [14.61464119, 12.96784878, 10.90732002, 8.75849152, 7.49001646, 6.44769001, 5.58536053, 4.65472794, + 3.60512662, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, + 4.65472794, 3.60512662, 2.95596409, 2.19988537, 1.56271636, 1.05362725, 0.64427125, 0.32104823, + 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.44769001, 5.58536053, + 4.65472794, 3.75677586, 3.07277966, 2.45070267, 1.78698075, 1.24153244, 0.83188516, 0.50118381, 0.22545385, + 0.02916753], + [14.61464119, 12.96784878, 11.54541874, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, + 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.36326075, 1.72759056, 1.24153244, 0.83188516, 0.50118381, + 0.22545385, 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, + 5.85520077, 5.09240818, 4.45427561, 3.60512662, 2.95596409, 2.36326075, 1.72759056, 1.24153244, 0.83188516, + 0.50118381, 0.22545385, 0.02916753], + [14.61464119, 13.76078796, 12.2308979, 10.90732002, 9.24142551, 8.30717278, 7.49001646, 6.77309084, + 5.85520077, 5.09240818, 4.45427561, 3.75677586, 3.07277966, 2.45070267, 1.91321158, 1.46270394, 1.05362725, + 0.72133851, 0.43325692, 0.19894916, 0.02916753], + ], + 1.00: [ + [14.61464119, 1.56271636, 0.02916753], + [14.61464119, 6.77309084, 0.95350921, 0.02916753], + [14.61464119, 6.77309084, 2.36326075, 0.803307, 0.02916753], + [14.61464119, 7.11996698, 3.07277966, 1.56271636, 0.59516323, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.41535246, 0.57119018, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.86115354, 0.38853383, + 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.72133851, + 0.34370604, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.07277966, 1.98035145, 1.24153244, + 0.72133851, 0.34370604, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.27973175, 1.51179266, + 0.95350921, 0.54755926, 0.25053367, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, + 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], + [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, + 1.61558151, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], + [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, + 2.12350607, 1.56271636, 1.08895338, 0.72133851, 0.41087446, 0.17026083, 0.02916753], + [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, + 2.19988537, 1.61558151, 1.162866, 0.803307, 0.50118381, 0.27464288, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 8.75849152, 7.49001646, 5.85520077, 4.65472794, 3.75677586, 3.07277966, + 2.45070267, 1.84880662, 1.36964464, 1.01931262, 0.72133851, 0.45573691, 0.25053367, 0.09824532, + 0.02916753], + [14.61464119, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.46139455, + 2.84484982, 2.19988537, 1.67050016, 1.24153244, 0.92192322, 0.64427125, 0.43325692, 0.25053367, 0.09824532, + 0.02916753], + [14.61464119, 11.54541874, 8.75849152, 7.49001646, 6.14220476, 5.09240818, 4.26497746, 3.60512662, + 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, 0.22545385, + 0.09824532, 0.02916753], + [14.61464119, 12.2308979, 9.24142551, 8.30717278, 7.49001646, 6.14220476, 5.09240818, 4.26497746, + 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, 0.38853383, + 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 12.2308979, 9.24142551, 8.30717278, 7.49001646, 6.77309084, 5.85520077, 5.09240818, + 4.26497746, 3.60512662, 2.95596409, 2.45070267, 1.91321158, 1.51179266, 1.12534678, 0.83188516, 0.59516323, + 0.38853383, 0.22545385, 0.09824532, 0.02916753], + ], + 1.05: [ + [14.61464119, 0.95350921, 0.02916753], + [14.61464119, 6.77309084, 0.89115214, 0.02916753], + [14.61464119, 6.77309084, 2.05039096, 0.72133851, 0.02916753], + [14.61464119, 6.77309084, 2.84484982, 1.28281462, 0.52423614, 0.02916753], + [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.803307, 0.34370604, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.56271636, 0.803307, 0.34370604, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.52423614, 0.22545385, + 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.07277966, 1.98035145, 1.24153244, 0.74807048, 0.41087446, + 0.17026083, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.51179266, 0.95350921, 0.59516323, 0.34370604, + 0.13792117, 0.02916753], + [14.61464119, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, 0.72133851, + 0.45573691, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.09240818, 3.46139455, 2.45070267, 1.61558151, 1.08895338, + 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.36326075, 1.61558151, + 1.08895338, 0.72133851, 0.45573691, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.72759056, + 1.24153244, 0.86115354, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, + 1.61558151, 1.162866, 0.83188516, 0.59516323, 0.38853383, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.84484982, 2.19988537, + 1.67050016, 1.28281462, 0.95350921, 0.72133851, 0.52423614, 0.34370604, 0.19894916, 0.09824532, + 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.36326075, + 1.84880662, 1.41535246, 1.08895338, 0.83188516, 0.61951244, 0.45573691, 0.32104823, 0.19894916, 0.09824532, + 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.65472794, 3.60512662, 2.95596409, 2.45070267, + 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.57119018, 0.43325692, 0.29807833, 0.19894916, + 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 8.30717278, 7.11996698, 5.85520077, 4.65472794, 3.60512662, 2.95596409, + 2.45070267, 1.91321158, 1.51179266, 1.20157266, 0.95350921, 0.74807048, 0.57119018, 0.43325692, 0.29807833, + 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 8.30717278, 7.11996698, 5.85520077, 4.65472794, 3.60512662, 2.95596409, + 2.45070267, 1.98035145, 1.61558151, 1.32549286, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.41087446, + 0.29807833, 0.19894916, 0.09824532, 0.02916753], + ], + 1.10: [ + [14.61464119, 0.89115214, 0.02916753], + [14.61464119, 2.36326075, 0.72133851, 0.02916753], + [14.61464119, 5.85520077, 1.61558151, 0.57119018, 0.02916753], + [14.61464119, 6.77309084, 2.45070267, 1.08895338, 0.45573691, 0.02916753], + [14.61464119, 6.77309084, 2.95596409, 1.56271636, 0.803307, 0.34370604, 0.02916753], + [14.61464119, 6.77309084, 3.07277966, 1.61558151, 0.89115214, 0.4783645, 0.19894916, 0.02916753], + [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.08895338, 0.64427125, 0.34370604, 0.13792117, + 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.84484982, 1.61558151, 0.95350921, 0.54755926, 0.27464288, + 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.4783645, 0.25053367, + 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.41535246, 0.95350921, 0.64427125, + 0.41087446, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.27973175, 1.61558151, 1.12534678, 0.803307, 0.54755926, + 0.36617002, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.32507086, 2.45070267, 1.72759056, 1.24153244, 0.89115214, + 0.64427125, 0.45573691, 0.32104823, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 5.09240818, 3.60512662, 2.84484982, 2.05039096, 1.51179266, 1.08895338, 0.803307, + 0.59516323, 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 5.09240818, 3.60512662, 2.84484982, 2.12350607, 1.61558151, 1.24153244, + 0.95350921, 0.72133851, 0.54755926, 0.41087446, 0.29807833, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.08895338, + 0.83188516, 0.64427125, 0.50118381, 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.1956799, 2.45070267, 1.91321158, 1.51179266, 1.20157266, + 0.95350921, 0.74807048, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, + 0.02916753], + [14.61464119, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, 1.72759056, + 1.36964464, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.43325692, 0.34370604, 0.25053367, 0.17026083, + 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, + 1.72759056, 1.36964464, 1.08895338, 0.86115354, 0.69515091, 0.54755926, 0.43325692, 0.34370604, 0.25053367, + 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 11.54541874, 7.49001646, 5.85520077, 4.45427561, 3.46139455, 2.84484982, 2.19988537, + 1.72759056, 1.36964464, 1.08895338, 0.89115214, 0.72133851, 0.59516323, 0.4783645, 0.38853383, 0.29807833, + 0.22545385, 0.17026083, 0.09824532, 0.02916753], + ], + 1.15: [ + [14.61464119, 0.83188516, 0.02916753], + [14.61464119, 1.84880662, 0.59516323, 0.02916753], + [14.61464119, 5.85520077, 1.56271636, 0.52423614, 0.02916753], + [14.61464119, 5.85520077, 1.91321158, 0.83188516, 0.34370604, 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.59516323, 0.25053367, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.51179266, 0.803307, 0.41087446, 0.17026083, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.56271636, 0.89115214, 0.50118381, 0.25053367, 0.09824532, + 0.02916753], + [14.61464119, 6.77309084, 3.07277966, 1.84880662, 1.12534678, 0.72133851, 0.43325692, 0.22545385, + 0.09824532, 0.02916753], + [14.61464119, 6.77309084, 3.07277966, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, 0.19894916, + 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 2.95596409, 1.91321158, 1.24153244, 0.803307, 0.52423614, 0.34370604, + 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.05039096, 1.36964464, 0.95350921, 0.69515091, 0.4783645, + 0.32104823, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.803307, 0.59516323, + 0.43325692, 0.29807833, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.803307, 0.59516323, + 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, + 0.74807048, 0.59516323, 0.45573691, 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.78698075, 1.32549286, 1.01931262, 0.803307, + 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.78698075, 1.32549286, 1.01931262, 0.803307, + 0.64427125, 0.52423614, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, + 0.72133851, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, + 0.72133851, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.86714602, 3.1956799, 2.45070267, 1.84880662, 1.41535246, 1.12534678, 0.89115214, + 0.72133851, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + ], + 1.20: [ + [14.61464119, 0.803307, 0.02916753], + [14.61464119, 1.56271636, 0.52423614, 0.02916753], + [14.61464119, 2.36326075, 0.92192322, 0.36617002, 0.02916753], + [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.25053367, 0.02916753], + [14.61464119, 5.85520077, 2.05039096, 0.95350921, 0.45573691, 0.17026083, 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.24153244, 0.64427125, 0.29807833, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.803307, 0.45573691, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.95350921, 0.59516323, 0.36617002, 0.19894916, + 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.50118381, 0.32104823, + 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.83188516, 0.59516323, 0.41087446, + 0.27464288, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 3.07277966, 1.98035145, 1.36964464, 0.95350921, 0.69515091, 0.50118381, + 0.36617002, 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 6.77309084, 3.46139455, 2.36326075, 1.56271636, 1.08895338, 0.803307, 0.59516323, 0.45573691, + 0.34370604, 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 6.77309084, 3.46139455, 2.45070267, 1.61558151, 1.162866, 0.86115354, 0.64427125, 0.50118381, + 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, + 0.64427125, 0.50118381, 0.38853383, 0.29807833, 0.22545385, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, + 0.64427125, 0.50118381, 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.12350607, 1.51179266, 1.08895338, 0.83188516, + 0.64427125, 0.50118381, 0.41087446, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.20157266, 0.92192322, + 0.72133851, 0.57119018, 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, + 0.74807048, 0.59516323, 0.4783645, 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 7.49001646, 4.65472794, 3.07277966, 2.19988537, 1.61558151, 1.24153244, 0.95350921, + 0.74807048, 0.59516323, 0.50118381, 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, + 0.17026083, 0.13792117, 0.09824532, 0.02916753], + ], + 1.25: [ + [14.61464119, 0.72133851, 0.02916753], + [14.61464119, 1.56271636, 0.50118381, 0.02916753], + [14.61464119, 2.05039096, 0.803307, 0.32104823, 0.02916753], + [14.61464119, 2.36326075, 0.95350921, 0.43325692, 0.17026083, 0.02916753], + [14.61464119, 2.84484982, 1.24153244, 0.59516323, 0.27464288, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.51179266, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.36326075, 1.24153244, 0.72133851, 0.41087446, 0.22545385, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.52423614, 0.34370604, 0.19894916, + 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.61558151, 0.98595673, 0.64427125, 0.43325692, 0.27464288, + 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.67050016, 1.08895338, 0.74807048, 0.52423614, 0.36617002, + 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.803307, 0.59516323, 0.45573691, 0.34370604, + 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.24153244, 0.86115354, 0.64427125, 0.4783645, 0.36617002, + 0.27464288, 0.19894916, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.95596409, 1.84880662, 1.28281462, 0.92192322, 0.69515091, 0.52423614, + 0.41087446, 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.72133851, 0.54755926, + 0.43325692, 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.72133851, 0.57119018, + 0.45573691, 0.36617002, 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 2.95596409, 1.91321158, 1.32549286, 0.95350921, 0.74807048, 0.59516323, 0.4783645, + 0.38853383, 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.41535246, 1.05362725, 0.803307, 0.61951244, 0.50118381, + 0.41087446, 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.41535246, 1.05362725, 0.803307, 0.64427125, 0.52423614, + 0.43325692, 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 3.07277966, 2.05039096, 1.46270394, 1.08895338, 0.83188516, 0.66947293, + 0.54755926, 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, + 0.17026083, 0.13792117, 0.09824532, 0.02916753], + ], + 1.30: [ + [14.61464119, 0.72133851, 0.02916753], + [14.61464119, 1.24153244, 0.43325692, 0.02916753], + [14.61464119, 1.56271636, 0.59516323, 0.22545385, 0.02916753], + [14.61464119, 1.84880662, 0.803307, 0.36617002, 0.13792117, 0.02916753], + [14.61464119, 2.36326075, 1.01931262, 0.52423614, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.36964464, 0.74807048, 0.41087446, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.56271636, 0.89115214, 0.54755926, 0.34370604, 0.19894916, 0.09824532, + 0.02916753], + [14.61464119, 3.07277966, 1.61558151, 0.95350921, 0.61951244, 0.41087446, 0.27464288, 0.17026083, + 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.36964464, 0.83188516, 0.54755926, 0.36617002, 0.25053367, + 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.41535246, 0.92192322, 0.64427125, 0.45573691, 0.34370604, + 0.25053367, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.6383388, 1.56271636, 1.01931262, 0.72133851, 0.50118381, 0.36617002, 0.27464288, + 0.19894916, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.05362725, 0.74807048, 0.54755926, 0.41087446, + 0.32104823, 0.25053367, 0.19894916, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.77538133, 0.57119018, 0.43325692, + 0.34370604, 0.27464288, 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.59516323, 0.45573691, 0.36617002, + 0.29807833, 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.59516323, 0.4783645, 0.38853383, + 0.32104823, 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.83188516, 0.64427125, 0.50118381, 0.41087446, + 0.34370604, 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.72759056, 1.162866, 0.83188516, 0.64427125, 0.52423614, 0.43325692, + 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.78698075, 1.24153244, 0.92192322, 0.72133851, 0.57119018, + 0.45573691, 0.38853383, 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.84484982, 1.78698075, 1.24153244, 0.92192322, 0.72133851, 0.57119018, 0.4783645, + 0.41087446, 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + ], + 1.35: [ + [14.61464119, 0.69515091, 0.02916753], + [14.61464119, 0.95350921, 0.34370604, 0.02916753], + [14.61464119, 1.56271636, 0.57119018, 0.19894916, 0.02916753], + [14.61464119, 1.61558151, 0.69515091, 0.29807833, 0.09824532, 0.02916753], + [14.61464119, 1.84880662, 0.83188516, 0.43325692, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.162866, 0.64427125, 0.36617002, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.36964464, 0.803307, 0.50118381, 0.32104823, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.41535246, 0.83188516, 0.54755926, 0.36617002, 0.25053367, 0.17026083, + 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.32104823, 0.22545385, + 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.25053367, + 0.19894916, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.61558151, 1.01931262, 0.72133851, 0.52423614, 0.38853383, 0.29807833, + 0.22545385, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.61558151, 1.01931262, 0.72133851, 0.52423614, 0.41087446, 0.32104823, + 0.25053367, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.61558151, 1.05362725, 0.74807048, 0.54755926, 0.43325692, 0.34370604, + 0.27464288, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.72759056, 1.12534678, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.29807833, + 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 3.07277966, 1.72759056, 1.12534678, 0.803307, 0.59516323, 0.4783645, 0.38853383, 0.32104823, + 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.45070267, 1.51179266, 1.01931262, 0.74807048, 0.57119018, 0.45573691, + 0.36617002, 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.41087446, + 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.43325692, + 0.36617002, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 5.85520077, 2.6383388, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.45573691, + 0.38853383, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + ], + 1.40: [ + [14.61464119, 0.59516323, 0.02916753], + [14.61464119, 0.95350921, 0.34370604, 0.02916753], + [14.61464119, 1.08895338, 0.43325692, 0.13792117, 0.02916753], + [14.61464119, 1.56271636, 0.64427125, 0.27464288, 0.09824532, 0.02916753], + [14.61464119, 1.61558151, 0.803307, 0.43325692, 0.22545385, 0.09824532, 0.02916753], + [14.61464119, 2.05039096, 0.95350921, 0.54755926, 0.34370604, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.24153244, 0.72133851, 0.43325692, 0.27464288, 0.17026083, 0.09824532, + 0.02916753], + [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.17026083, + 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.52423614, 0.36617002, 0.27464288, 0.19894916, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.38853383, 0.29807833, 0.22545385, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.41535246, 0.86115354, 0.59516323, 0.43325692, 0.32104823, 0.25053367, + 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.64427125, 0.45573691, 0.34370604, 0.27464288, + 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.64427125, 0.4783645, 0.36617002, 0.29807833, 0.25053367, + 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 0.98595673, 0.69515091, 0.52423614, 0.41087446, 0.34370604, + 0.29807833, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.72133851, 0.54755926, 0.43325692, 0.36617002, + 0.32104823, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 2.84484982, 1.61558151, 1.05362725, 0.74807048, 0.57119018, 0.45573691, 0.38853383, + 0.34370604, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.41087446, 0.36617002, + 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.61951244, 0.50118381, 0.43325692, 0.38853383, + 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.61558151, 1.08895338, 0.803307, 0.64427125, 0.52423614, 0.45573691, 0.41087446, + 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + ], + 1.45: [ + [14.61464119, 0.59516323, 0.02916753], + [14.61464119, 0.803307, 0.25053367, 0.02916753], + [14.61464119, 0.95350921, 0.34370604, 0.09824532, 0.02916753], + [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 1.91321158, 0.95350921, 0.57119018, 0.36617002, 0.25053367, 0.17026083, 0.09824532, + 0.02916753], + [14.61464119, 2.19988537, 1.08895338, 0.64427125, 0.41087446, 0.27464288, 0.19894916, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.34370604, 0.25053367, 0.19894916, + 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.24153244, 0.74807048, 0.50118381, 0.36617002, 0.27464288, 0.22545385, + 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.54755926, 0.41087446, 0.32104823, 0.25053367, 0.19894916, + 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.28281462, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.27464288, 0.22545385, + 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.28281462, 0.83188516, 0.59516323, 0.45573691, 0.36617002, 0.29807833, + 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.28281462, 0.83188516, 0.59516323, 0.45573691, 0.36617002, 0.32104823, + 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.69515091, 0.52423614, 0.41087446, 0.34370604, + 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 2.84484982, 1.51179266, 0.95350921, 0.69515091, 0.52423614, 0.43325692, 0.36617002, + 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 0.98595673, 0.72133851, 0.54755926, 0.45573691, 0.38853383, + 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.74807048, 0.57119018, 0.4783645, 0.41087446, 0.36617002, + 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 2.84484982, 1.56271636, 1.01931262, 0.74807048, 0.59516323, 0.50118381, 0.43325692, + 0.38853383, 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, + 0.17026083, 0.13792117, 0.09824532, 0.02916753], + ], + 1.50: [ + [14.61464119, 0.54755926, 0.02916753], + [14.61464119, 0.803307, 0.25053367, 0.02916753], + [14.61464119, 0.86115354, 0.32104823, 0.09824532, 0.02916753], + [14.61464119, 1.24153244, 0.54755926, 0.25053367, 0.09824532, 0.02916753], + [14.61464119, 1.56271636, 0.72133851, 0.36617002, 0.19894916, 0.09824532, 0.02916753], + [14.61464119, 1.61558151, 0.803307, 0.45573691, 0.27464288, 0.17026083, 0.09824532, 0.02916753], + [14.61464119, 1.61558151, 0.83188516, 0.52423614, 0.34370604, 0.25053367, 0.17026083, 0.09824532, + 0.02916753], + [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.38853383, 0.27464288, 0.19894916, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 1.84880662, 0.95350921, 0.59516323, 0.41087446, 0.29807833, 0.22545385, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 1.84880662, 0.95350921, 0.61951244, 0.43325692, 0.32104823, 0.25053367, 0.19894916, + 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.19988537, 1.12534678, 0.72133851, 0.50118381, 0.36617002, 0.27464288, 0.22545385, + 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.19988537, 1.12534678, 0.72133851, 0.50118381, 0.36617002, 0.29807833, 0.25053367, + 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.29807833, 0.25053367, + 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.57119018, 0.43325692, 0.34370604, 0.29807833, 0.27464288, + 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.59516323, 0.45573691, 0.36617002, 0.32104823, 0.29807833, + 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.36326075, 1.24153244, 0.803307, 0.59516323, 0.45573691, 0.38853383, 0.34370604, 0.32104823, + 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, 0.09824532, + 0.02916753], + [14.61464119, 2.45070267, 1.32549286, 0.86115354, 0.64427125, 0.50118381, 0.41087446, 0.36617002, + 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, 0.13792117, + 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.36964464, 0.92192322, 0.69515091, 0.54755926, 0.45573691, 0.41087446, + 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + [14.61464119, 2.45070267, 1.41535246, 0.95350921, 0.72133851, 0.57119018, 0.4783645, 0.43325692, 0.38853383, + 0.36617002, 0.34370604, 0.32104823, 0.29807833, 0.27464288, 0.25053367, 0.22545385, 0.19894916, 0.17026083, + 0.13792117, 0.09824532, 0.02916753], + ], + } + + def get_sigmas(self, coeff, steps, denoise): + total_steps = steps + if denoise < 1.0: + if denoise <= 0.0: + return (torch.FloatTensor([]),) + total_steps = round(steps * denoise) + + if steps <= 20: + sigmas = self.NOISE_LEVELS[round(coeff, 2)][steps-2][:] + else: + sigmas = self.NOISE_LEVELS[round(coeff, 2)][-1][:] + sigmas = loglinear_interp(sigmas, steps + 1) + + sigmas = sigmas[-(total_steps + 1):] + sigmas[-1] = 0 return (torch.FloatTensor(sigmas), ) \ No newline at end of file diff --git a/py/libs/xyplot.py b/py/libs/xyplot.py index 2a2ff51..a3e9ce0 100644 --- a/py/libs/xyplot.py +++ b/py/libs/xyplot.py @@ -62,7 +62,7 @@ def define_variable(plot_image_vars, value_type, value, index): if value_type in ['Lora', 'Checkpoint']: arr = value.split(',') model_name = os.path.basename(os.path.splitext(arr[0])[0]) - trigger_words = ' ' + arr[3] if len(arr[3]) > 2 else '' + trigger_words = ' ' + arr[3] if value_type == 'Lora' and len(arr[3]) > 2 else '' value_label = f"{model_name}{trigger_words}" if value_type in ["ModelMergeBlocks"]: diff --git a/py/logic.py b/py/logic.py index 95d0389..40b3b15 100644 --- a/py/logic.py +++ b/py/logic.py @@ -991,6 +991,29 @@ def execute(self, **kwargs): # 是否为SDXL from comfy.sdxl_clip import SDXLClipModel, SDXLRefinerClipModel, SDXLClipG +class isMaskEmpty: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "mask": ("MASK",), + }, + "optional": { + } + } + + RETURN_TYPES = ("BOOLEAN",) + RETURN_NAMES = ("boolean",) + FUNCTION = "execute" + CATEGORY = "EasyUse/Logic" + + def execute(self, mask): + if mask is None: + return (True,) + if torch.all(mask == 0): + return (True,) + return (False,) + class isNone: @classmethod @@ -1324,6 +1347,8 @@ def log_input(self, unique_id=None, extra_pnginfo=None, **kwargs): try: if type(val) is str: values.append(val) + elif type(val) is list: + values = val else: val = json.dumps(val) values.append(str(val)) @@ -1780,6 +1805,7 @@ def execute(self, any, delay): "easy forLoopEnd": forLoopEnd, "easy blocker": Blocker, "easy ifElse": IfElse, + "easy isMaskEmpty": isMaskEmpty, "easy isNone": isNone, "easy isSDXL": isSDXL, "easy isFileExist": isFileExist, @@ -1828,6 +1854,7 @@ def execute(self, any, delay): "easy forLoopEnd": "For Loop End", "easy ifElse": "If else", "easy blocker": "Blocker", + "easy isMaskEmpty": "Is Mask Empty", "easy isNone": "Is None", "easy isSDXL": "Is SDXL", "easy isFileExist": "Is File Exist", diff --git a/pyproject.toml b/pyproject.toml index 9a89254..7b6bdb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-easy-use" description = "To enhance the usability of ComfyUI, optimizations and integrations have been implemented for several commonly used nodes." -version = "1.2.4" +version = "1.2.5" license = { file = "LICENSE" } dependencies = ["diffusers", "accelerate", "clip_interrogator>=0.6.0", "sentencepiece", "lark-parser", "onnxruntime", "spandrel", "opencv-python"] diff --git a/web_version/v2/assets/extensions-29B2pS6_.js b/web_version/v2/assets/extensions-29B2pS6_.js new file mode 100644 index 0000000..18eeefa --- /dev/null +++ b/web_version/v2/assets/extensions-29B2pS6_.js @@ -0,0 +1 @@ +var e,t,s,n,i,o,a,l,r,d,u,c,p,h,m=Object.defineProperty,g=(e,t,s)=>((e,t,s)=>t in e?m(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s)(e,"symbol"!=typeof t?t+"":t,s);import{d as f,h as y}from"./vendor-DT1J-jWa.js";import{c as _}from"./lodash-CZi7izHi.js";let v=(null==(t=null==(e=window.comfyAPI)?void 0:e.app)?void 0:t.app)||null,b=(null==(n=null==(s=window.comfyAPI)?void 0:s.api)?void 0:n.api)||null,w=(null==(o=null==(i=window.comfyAPI)?void 0:i.ui)?void 0:o.$el)||null,L=(null==(l=null==(a=window.comfyAPI)?void 0:a.dialog)?void 0:l.ComfyDialog)||null,S=(null==(d=null==(r=window.comfyAPI)?void 0:r.widgets)?void 0:d.ComfyWidgets)||null,E=(null==(c=null==(u=window.comfyAPI)?void 0:u.utils)?void 0:c.applyTextReplacements)||null,C=(null==(h=null==(p=window.comfyAPI)?void 0:p.groupNode)?void 0:h.GroupNodeConfig)||null;const k=(e,t=void 0)=>{var s,n;return e?null==(n=null==(s=null==v?void 0:v.ui)?void 0:s.settings)?void 0:n.getSettingValue(e,t):null};function A(e,t=null,s=void 0){try{let n=e?k(e,s):null;return null==n&&(n=t?localStorage[t]:localStorage[e]||null),n}catch(n){return null}}function x(e,t=e=>{}){var s;const n=null==(s=v.ui.settings.settingsLookup)?void 0:s[e];n&&(n.onChange=e=>t(e))}async function I(e,t,s=null){var n,i;try{(null==(i=null==(n=null==v?void 0:v.ui)?void 0:n.settings)?void 0:i.setSettingValue)?v.ui.settings.setSettingValue(e,t):await b.storeSetting(e,t),s&&(localStorage[s]="object"==typeof t?JSON.stringify(t):t)}catch(o){}}const N="comfyui-easyuse-",T="dark-theme",O="#236692",D={PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd",FLOW_CONTROL:"#373780"},R=0x4000000000000,G=["loaders","latent","image","mask","sampling","_for_testing","advanced","utils","api"],M={ALWAYS:0,NEVER:2,BYPASS:4},P="easyuse_nodes_map",F=LGraphCanvas.node_colors.bgcolor,U={ColorPalette:{version:105,id:"obsidian",name:"Obsidian",colors:{node_slot:{CLIP:"#FFD500",CLIP_VISION:"#A8DADC",CLIP_VISION_OUTPUT:"#ad7452",CONDITIONING:"#FFA931",CONTROL_NET:"#6EE7B7",IMAGE:"#64B5F6",LATENT:"#FF9CF9",MASK:"#81C784",MODEL:"#B39DDB",STYLE_MODEL:"#C2FFAE",VAE:"#FF6E6E",TAESD:"#DCC274",PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd"},litegraph_base:{BACKGROUND_IMAGE:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",CLEAR_BACKGROUND_COLOR:"#222222",NODE_TITLE_COLOR:"#d4d4d8",NODE_SELECTED_TITLE_COLOR:"#ffffff",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#ffffff",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#09090b",NODE_DEFAULT_BGCOLOR:"rgba(24,24,27,.9)",NODE_DEFAULT_BOXCOLOR:"rgba(255,255,255,.75)",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:O,DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#242427",WIDGET_OUTLINE_COLOR:"#3f3f46",WIDGET_TEXT_COLOR:"#d4d4d8",WIDGET_SECONDARY_TEXT_COLOR:"#d4d4d8",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#A86",CONNECTING_LINK_COLOR:"#AFA"},comfy_base:{"fg-color":"#fff","bg-color":"#09090b","comfy-menu-bg":"rgba(24,24,24,.9)","comfy-input-bg":"#262626","input-text":"#ddd","descrip-text":"#999","drag-text":"#ccc","error-text":"#ff4444","border-color":"#29292c","tr-even-bg-color":"rgba(28,28,28,.9)","tr-odd-bg-color":"rgba(19,19,19,.9)"}}},NODE_COLORS:{red:{color:"#af3535",bgcolor:F,groupcolor:"#A88"},brown:{color:"#38291f",bgcolor:F,groupcolor:"#b06634"},green:{color:"#346434",bgcolor:F,groupcolor:"#8A8"},blue:{color:"#1f1f48",bgcolor:F,groupcolor:"#88A"},pale_blue:{color:"#006691",bgcolor:F,groupcolor:"#3f789e"},cyan:{color:"#008181",bgcolor:F,groupcolor:"#8AA"},purple:{color:"#422342",bgcolor:F,groupcolor:"#a1309b"},yellow:{color:"#c09430",bgcolor:F,groupcolor:"#b58b2a"},black:{color:"rgba(0,0,0,.8)",bgcolor:F,groupcolor:"#444"}}};let B=JSON.parse(JSON.stringify(U));delete B.NODE_COLORS,B.ColorPalette.id="obsidian_dark",B.ColorPalette.name="Obsidian Dark",B.ColorPalette.colors.litegraph_base.BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==",B.ColorPalette.colors.litegraph_base.CLEAR_BACKGROUND_COLOR="#09090b";const z=LGraphCanvas.node_colors.bgcolor,W={ColorPalette:{id:"milk_white",name:"Milk White",colors:{node_slot:{CLIP:"#FFA726",CLIP_VISION:"#5C6BC0",CLIP_VISION_OUTPUT:"#8D6E63",CONDITIONING:"#EF5350",CONTROL_NET:"#66BB6A",IMAGE:"#42A5F5",LATENT:"#AB47BC",MASK:"#9CCC65",MODEL:"#7E57C2",STYLE_MODEL:"#D4E157",VAE:"#FF7043",PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd"},litegraph_base:{BACKGROUND_IMAGE:"data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==",CLEAR_BACKGROUND_COLOR:"lightgray",NODE_TITLE_COLOR:"#222",NODE_SELECTED_TITLE_COLOR:"#000",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#444",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#F7F7F7",NODE_DEFAULT_BGCOLOR:"#F5F5F5",NODE_DEFAULT_BOXCOLOR:"#555",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:"#000",DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0.1)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#D4D4D4",WIDGET_OUTLINE_COLOR:"#999",WIDGET_TEXT_COLOR:"#222",WIDGET_SECONDARY_TEXT_COLOR:"#555",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#FF9800",CONNECTING_LINK_COLOR:"#222"},comfy_base:{"fg-color":"#222","bg-color":"#DDD","comfy-menu-bg":"#F5F5F5","comfy-input-bg":"#C9C9C9","input-text":"#222","descrip-text":"#444","drag-text":"#555","error-text":"#F44336","border-color":"#bbb","tr-even-bg-color":"#f9f9f9","tr-odd-bg-color":"#fff","content-bg":"#e0e0e0","content-fg":"#222","content-hover-bg":"#adadad","content-hover-fg":"#222"}}},NODE_COLORS:{red:{color:"#af3535",bgcolor:z,groupcolor:"#A88"},brown:{color:"#38291f",bgcolor:z,groupcolor:"#b06634"},green:{color:"#346434",bgcolor:z,groupcolor:"#8A8"},blue:{color:"#1f1f48",bgcolor:z,groupcolor:"#88A"},pale_blue:{color:"#006691",bgcolor:z,groupcolor:"#3f789e"},cyan:{color:"#008181",bgcolor:z,groupcolor:"#8AA"},purple:{color:"#422342",bgcolor:z,groupcolor:"#a1309b"},yellow:{color:"#c09430",bgcolor:z,groupcolor:"#b58b2a"},black:{color:"rgba(0,0,0,.8)",bgcolor:z,groupcolor:"#444"}}},j={"Workflow created by":"工作流创建者","Watch more video content":"观看更多视频内容","Workflow Guide":"工作流指南","💎 View Checkpoint Info...":"💎 查看 Checkpoint 信息...","💎 View Lora Info...":"💎 查看 Lora 信息...","🔃 Reload Node":"🔃 刷新节点","Updated At:":"最近更新:","Created At:":"首次发布:","✏️ Edit":"✏️ 编辑","💾 Save":"💾 保存","No notes":"当前还没有备注内容","Saving Notes...":"正在保存备注...","Type your notes here":"在这里输入备注内容",ModelName:"模型名称","Models Required":"所需模型","Download Model":"下载模型","Source Url":"模型源地址",Notes:"备注",Type:"类型","Trained Words":"训练词",BaseModel:"基础算法",Details:"详情",Description:"描述",Download:"下载量",Source:"来源","Saving Preview...":"正在保存预览图...","Saving Succeed":"保存成功","Clean SuccessFully":"清理成功","Clean Failed":"清理失败","Saving Failed":"保存失败","No COMBO link":"沒有找到COMBO连接","Reboot ComfyUI":"重启ComfyUI","Are you sure you'd like to reboot the server?":"是否要重启ComfyUI?","Nodes Map":"管理节点组","Nodes map sorting mode":"管理节点组排序模式","No Nodes":"未找到节点","No nodes found in the map":"在工作流程中没有找到节点","Expand All":"展开所有组","Collapse All":"折叠所有组",Close:"关闭","Default automatic sorting, if set to manual, groups can be dragged and dropped and the sorting results saved.":"默认自动排序,如果设置为手动,组可以拖放并保存排序结果。","For drag and drop sorting, please find Nodes map sorting mode in Settings->EasyUse and change it to manual":"如需拖拽排序请在设置->EasyUse节点中找到管理节点组排序模式并修改成 manual",Queue:"队列","Cleanup Of VRAM Usage":"清理显存占用","Please stop all running tasks before cleaning GPU":"请在清理GPU之前停止所有运行中的任务",Always:"启用中",Bypass:"已忽略",Never:"已停用","Auto Sorting":"自动排序","Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never":"点击`启用中/已忽略`可设置组模式, 长按可停用该组节点","Enable Shift+Up/Down/Left/Right key and Shift+Ctrl+Alt+Left/Right to align selected nodes":"启用 Shift+上/下/左/右 和 Shift+Ctrl+Alt+左/右 键对齐选中的节点","Enable Shift+Ctrl+Left/Right key to normalize selected nodes":"启用 Shift+Ctrl+左/右 键规范化选中的节点","Enable Shift+g to add selected nodes to a group":"启用 Shift+g 键将选中的节点添加一个组","Enable Shift+r to unload models and node cache":"启用 Shift+r 键卸载模型和节点缓存","Enable Shift+m to toggle nodes map":"启用 Shift+m 键显隐管理节点组","Enable Up/Down/Left/Right key to jump nearest nodes":"启用 上/下/左/右 键跳转到最近的前后节点","Enable Alt+1~9 to paste nodes from nodes template":"启用 Alt+1~9 从节点模板粘贴到工作流中","Enable contextMenu auto nest subdirectories":"启用上下文菜单自动嵌套子目录","Enable right-click menu to add node A~Z sorting":"启用右键菜单中新建节点A~Z排序","Enable model thumbnails display":"启动模型预览图显示","Enable nodes runtime display":"启动节点运行时间显示","Enable chain get node and set node with parent nodes":"启用将获取点和设置点与父节点链在一起","Maximum number of model thumbnails displayed":"显示的模型缩略图的最大数量","Too many thumbnails will affect the first loading time, set the maximum value to not load the thumbnail function when there are too many models's thumbnail":"太多的缩略图会影响首次加载时间,当模型缩略图太多时,设置最大值以不加载缩略图功能","Too many thumbnails, have closed the display":"模型缩略图太多啦,为您关闭了显示","Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute horizontal/vertical nodes":"Shift+上/下/左/右 可以对齐选中的节点, Shift+Ctrl+Alt+左/右 可以水平/垂直分布节点","Enable Shift+Ctrl+Left key to normalize width and Shift+Ctrl+Right key to normalize height":"启用 Shift+Ctrl+左 键规范化宽度和 Shift+Ctrl+右 键规范化高度","After v1.2.39, Ctrl+g can be used instead of it":"从v1.2.39开始,可以使用Ctrl+g代替","Use three shortcut buttons in the right-click menu":"在右键菜单中使用三个快捷按钮","Enable Nodes Map":"启用节点组管理","You need to refresh the page to update successfully":"您需要刷新页面以成功更新","Get styles list Failed":"获取样式列表失败","Get style image Failed":"获取样式图片失败","Empty All":"清空所有","Type here to search styles ...":"在此处输入以搜索样式 ...","Loading UserInfo...":"正在获取用户信息...","Please set the APIKEY first":"请先设置APIKEY","Setting APIKEY":"设置APIKEY","Save Account Info":"保存账号信息",Choose:"选择",Delete:"删除",Edit:"编辑","At least one account is required":"删除失败: 至少需要一个账户","APIKEY is not Empty":"APIKEY 不能为空","Add Account":"添加账号","Getting Your APIKEY":"获取您的APIKEY","Choose Selected Images":"选择选中的图片","Choose images to continue":"选择图片以继续",Background:"背景",Hat:"帽子",Hair:"头发",Body:"身体",Face:"脸部",Clothes:"衣服",Others:"其他",Glove:"手套",Glasses:"眼镜",Sunglasses:"太阳镜","Upper-clothes":"上衣","Top-clothes":"上衣","Bottom-clothes":"下身装","Torso-skin":"皮肤",Dress:"连衣裙",Coat:"外套",Socks:"袜子",Pants:"裤子",Jumpsuits:"连体衣",Scarf:"围巾",Skirt:"裙子","Left-arm":"左臂","Right-arm":"右臂","Left-leg":"左腿","Right-leg":"右腿","Left-foot":"左脚","Right-foot":"右脚","Left-shoe":"左鞋","Right-shoe":"右鞋",s:"秒","No Node Templates Found":"未找到节点模板预设","Get Node Templates File Failed":"获取节点模板文件失败","Node template with {key} not set":"未设置快捷键为{key}的节点预设","ComfyUI Basic":"ComfyUI 基础节点","Recommend Nodes":"推荐节点","Others A~Z":"其他节点 A~Z"},V=A("AGL.Locale"),Y=(e,t=!1)=>"zh-CN"===(t?navigator.language:V)&&j[e]||e,H={addGroup:{id:"EasyUse.Hotkeys.AddGroup",name:Y("Enable Shift+g to add selected nodes to a group"),tooltip:Y("After v1.2.39, Ctrl+g can be used instead of it"),type:"boolean",defaultValue:!0},cleanVRAMUsed:{id:"EasyUse.Hotkeys.cleanVRAMUsed",name:Y("Enable Shift+r to unload models and node cache"),type:"boolean",defaultValue:!0},toggleSiteMap:{id:"EasyUse.Hotkeys.toggleNodesMap",name:Y("Enable Shift+m to toggle nodes map"),type:"boolean",defaultValue:!0},alignSelectedNodes:{id:"EasyUse.Hotkeys.AlignSelectedNodes",name:Y("Enable Shift+Up/Down/Left/Right key and Shift+Ctrl+Alt+Left/Right to align selected nodes"),tooltip:Y("Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute horizontal/vertical nodes"),type:"boolean",defaultValue:!0},NormalizeSelectedNodes:{id:"EasyUse.Hotkeys.NormalizeSelectedNodes",name:Y("Enable Shift+Ctrl+Left/Right key to normalize selected nodes"),tooltip:Y("Enable Shift+Ctrl+Left key to normalize width and Shift+Ctrl+Right key to normalize height"),type:"boolean",defaultValue:!0},nodesTemplate:{id:"EasyUse.Hotkeys.NodesTemplate",name:Y("Enable Alt+1~9 to paste nodes from nodes template"),type:"boolean",defaultValue:!0},jumpNearestNodes:{id:"EasyUse.Hotkeys.JumpNearestNodes",name:Y("Enable Up/Down/Left/Right key to jump nearest nodes"),type:"boolean",defaultValue:!0},subDirectories:{id:"EasyUse.ContextMenu.SubDirectories",name:Y("Enable contextMenu auto nest subdirectories"),type:"boolean",defaultValue:!1},modelsThumbnails:{id:"EasyUse.ContextMenu.ModelsThumbnails",name:Y("Enable model thumbnails display"),type:"boolean",defaultValue:!1},modelsThumbnailsLimit:{id:"EasyUse.ContextMenu.ModelsThumbnailsLimit",name:Y("Maximum number of model thumbnails displayed"),tooltip:Y("Too many thumbnails will affect the first loading time, set the maximum value to not load the thumbnail function when there are too many models's thumbnail"),type:"slider",attrs:{min:0,max:5e3,step:100},defaultValue:500},rightMenuNodesSort:{id:"EasyUse.ContextMenu.NodesSort",name:Y("Enable right-click menu to add node A~Z sorting"),type:"boolean",defaultValue:!0},quickOptions:{id:"EasyUse.ContextMenu.QuickOptions",name:Y("Use three shortcut buttons in the right-click menu"),type:"combo",options:["At the forefront","At the end","Disable"],defaultValue:"At the forefront"},nodesRuntime:{id:"EasyUse.Nodes.Runtime",name:Y("Enable nodes runtime display"),type:"boolean",defaultValue:!0},chainGetSet:{id:"EasyUse.Nodes.ChainGetSet",name:Y("Enable chain get node and set node with parent nodes"),type:"boolean",defaultValue:!0},nodesMap:{id:"EasyUse.NodesMap.Sorting",name:Y("Nodes map sorting mode"),tooltip:Y("Default automatic sorting, if set to manual, groups can be dragged and dropped and the sorting results saved."),type:"combo",options:["Auto sorting","Manual drag&drop sorting"],defaultValue:"Auto sorting"},enableNodesMap:{id:"EasyUse.NodesMap.Enable",name:Y("Enable Nodes Map"),tooltip:Y("You need to refresh the page to update successfully"),type:"boolean",defaultValue:!0}};function X(e=100,t){return new Promise((s=>{setTimeout((()=>{s(t)}),e)}))}function Z(e,t){if(e="number"==typeof e?e:e instanceof Date?e.getTime():parseInt(e),isNaN(e))return null;let s=new Date(e);(e=s.toString().split(/[\s\:]/g).slice(0,-2))[1]=["01","02","03","04","05","06","07","08","09","10","11","12"][s.getMonth()];let n={MM:1,dd:2,yyyy:3,hh:4,mm:5,ss:6};return t.replace(/([Mmdhs]|y{2})\1/g,(t=>e[n[t]]))}const K=/Mac|iPod|iPhone|iPad/.test(navigator.platform),J=e=>K?e.replace(/Ctrl/g,"⌘").replace(/Alt/g,"⌥").replace(/Shift/g,"⇧"):e,$=f("groups",{state:e=>({groups:[],nodes:[],isWatching:!1}),getters:{groups_nodes(){var e;let t=[],s=[];if((null==(e=this.nodes)?void 0:e.length)>0){this.nodes.map((e=>{let n=e.pos,i=!1;for(let s=0;so.pos[0]&&n[0]o.pos[1]&&n[1]e.pos[0]-t.pos[0])).sort(((e,t)=>e.pos[1]-t.pos[1])))},setNodes(e){this.nodes=_(e)},update(){var e,t,s;(((null==(e=v.extensionManager)?void 0:e.activeSidebarTab)||(null==(s=null==(t=v.extensionManager.sidebarTab)?void 0:t.activeSidebarTab)?void 0:s.id))===P||this.isWatching)&&setTimeout((e=>{this.setGroups(v.canvas.graph._groups),this.setNodes(v.canvas.graph._nodes)}),1)},watchGraph(e=!1){e&&(this.isWatching=!0);let t=this;this.update();const s=v.graph.onNodeAdded;v.graph.onNodeAdded=function(e){t.update();const n=e.onRemoved;return e.onRemoved=function(){return t.update(),null==n?void 0:n.apply(this,arguments)},null==s?void 0:s.apply(this,arguments)},v.canvas.onNodeMoved=function(e){t.update()};const n=LGraphCanvas.onNodeAlign;LGraphCanvas.onNodeAlign=function(e){return t.update(),null==n?void 0:n.apply(this,arguments)};const i=LGraphCanvas.onGroupAdd;LGraphCanvas.onGroupAdd=function(){return t.update(),null==i?void 0:i.apply(this,arguments)};const o=LGraphCanvas.onGroupAlign;LGraphCanvas.onGroupAlign=function(e){return t.update(),null==o?void 0:o.apply(this,arguments)};const a=LGraphCanvas.onMenuNodeRemove;LGraphCanvas.onMenuNodeRemove=function(e){return t.update(),null==a?void 0:a.apply(this,arguments)}},unwatchGraph(){this.isWatching=!1}}});let q=null;const Q=["custom_obsidian","custom_obsidian_dark","custom_milk_white"],ee={"easy positive":"green","easy negative":"red","easy promptList":"cyan","easy promptLine":"cyan","easy promptConcat":"cyan","easy promptReplace":"cyan","easy forLoopStart":"blue","easy forLoopEnd":"blue","easy loadImagesForLoop":"blue"};let te=LGraphCanvas.node_colors,se=null,ne=null,ie=null,oe=null;for(let hs in H){const e="Disabled"==A("Comfy.UseNewMenu")?"👽 "+J(H[hs].name):J(H[hs].name),t=H[hs].tooltip?J(H[hs].tooltip):"";ae={...H[hs],name:e,tooltip:t},v.ui.settings.addSetting(ae)}var ae;function le(e,t=!1){let s="after",n="before";t&&([n,s]=[s,n]),e.label=(e.label??e.name).replace(n,s),e.name=e.label}function re(e,t,s,n,i,o,a){t.strokeStyle=n,t.fillStyle=i;let l=LiteGraph.NODE_TITLE_HEIGHT,r=this.ds.scale<.5,d=e._shape||e.constructor.shape||LiteGraph.ROUND_SHAPE,u=e.constructor.title_mode,c=!0;u==LiteGraph.TRANSPARENT_TITLE||u==LiteGraph.NO_TITLE?c=!1:u==LiteGraph.AUTOHIDE_TITLE&&mouse_over&&(c=!0);let p=new Float32Array(4);p=[0,c?-l:0,s[0]+1,c?s[1]+l:s[1]];let h=t.globalAlpha;if(t.lineWidth=1,t.beginPath(),d==LiteGraph.BOX_SHAPE||r?t.fillRect(p[0],p[1],p[2],p[3]):d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CARD_SHAPE?t.roundRect(p[0],p[1],p[2],p[3],d==LiteGraph.CARD_SHAPE?[this.round_radius,this.round_radius,0,0]:[this.round_radius]):d==LiteGraph.CIRCLE_SHAPE&&t.arc(.5*s[0],.5*s[1],.5*s[0],0,2*Math.PI),t.strokeStyle=LiteGraph.WIDGET_OUTLINE_COLOR,t.stroke(),t.strokeStyle=n,t.fill(),!e.flags.collapsed&&c&&(t.shadowColor="transparent",t.fillStyle="rgba(0,0,0,0.2)",t.fillRect(0,-1,p[2],2)),t.shadowColor="transparent",e.onDrawBackground&&e.onDrawBackground(t,this,this.canvas,this.graph_mouse),c||u==LiteGraph.TRANSPARENT_TITLE){const i="dark"==function(e){let t=e.replace("#","");return s=parseInt(t.substring(0,2),16),n=parseInt(t.substring(2,4),16),i=parseInt(t.substring(4,6),16),.299*s+.587*n+.114*i>127.5?"light":"dark";var s,n,i}((null==e?void 0:e.color)||"#ffffff");if(e.onDrawTitleBar)e.onDrawTitleBar(t,l,s,this.ds.scale,n);else if(u!=LiteGraph.TRANSPARENT_TITLE&&(e.constructor.title_color||this.render_title_colored)){let i=e.constructor.title_color||n;if(e.flags.collapsed&&(t.shadowColor=LiteGraph.DEFAULT_SHADOW_COLOR),this.use_gradients){let e=LGraphCanvas.gradients[i];e||(e=LGraphCanvas.gradients[i]=t.createLinearGradient(0,0,400,0),e.addColorStop(0,i),e.addColorStop(1,"#000")),t.fillStyle=e}else t.fillStyle=i;t.beginPath(),d==LiteGraph.BOX_SHAPE||r?t.rect(0,-l,s[0]+1,l):d!=LiteGraph.ROUND_SHAPE&&d!=LiteGraph.CARD_SHAPE||t.roundRect(0,-l,s[0]+1,l,e.flags.collapsed?[this.round_radius]:[this.round_radius,this.round_radius,0,0]),t.fill(),t.shadowColor="transparent"}let a=!1;LiteGraph.node_box_coloured_by_mode&&LiteGraph.NODE_MODES_COLORS[e.mode]&&(a=LiteGraph.NODE_MODES_COLORS[e.mode]),LiteGraph.node_box_coloured_when_on&&(a=e.action_triggered?"#FFF":e.execute_triggered?"#AAA":a);let c=10;if(e.onDrawTitleBox)e.onDrawTitleBox(t,l,s,this.ds.scale);else if(d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CIRCLE_SHAPE||d==LiteGraph.CARD_SHAPE){const s=i?"#ffffff":LiteGraph.NODE_SELECTED_TITLE_COLOR,n=i?"#eeeeee":e.boxcolor||a||LiteGraph.NODE_DEFAULT_BOXCOLOR;t.fillStyle=o?s:n,t.beginPath(),t.fillRect(10,0-1.05*c-1,1.1*c,.125*c),t.fillRect(10,0-1.45*c-1,1.1*c,.125*c),t.fillRect(10,0-1.85*c-1,1.1*c,.125*c)}else t.fillStyle=e.boxcolor||a||LiteGraph.NODE_DEFAULT_BOXCOLOR,t.fillRect(.5*(l-c),-.5*(l+c),c,c);if(t.globalAlpha=h,e.onDrawTitleText&&e.onDrawTitleText(t,l,s,this.ds.scale,this.title_text_font,o),!r){t.font=this.title_text_font;let s=String(e.getTitle());s&&(t.fillStyle=o?i?"#ffffff":LiteGraph.NODE_SELECTED_TITLE_COLOR:i?"#ffffff":e.constructor.title_text_color||this.node_title_color,e.flags.collapsed?(t.textAlign="left",t.measureText(s),t.fillText(s.substr(0,20),l,LiteGraph.NODE_TITLE_TEXT_Y-l),t.textAlign="left"):(t.textAlign="left",t.fillText(s,l,LiteGraph.NODE_TITLE_TEXT_Y-l)))}if(!e.flags.collapsed&&e.subgraph&&!e.skip_subgraph_button){let s=LiteGraph.NODE_TITLE_HEIGHT,n=e.size[0]-s,i=LiteGraph.isInsideRectangle(this.graph_mouse[0]-e.pos[0],this.graph_mouse[1]-e.pos[1],n+2,2-s,s-4,s-4);t.fillStyle=i?"#888":"#555",d==LiteGraph.BOX_SHAPE||r?t.fillRect(n+2,2-s,s-4,s-4):(t.beginPath(),t.roundRect(n+2,2-s,s-4,s-4,[4]),t.fill()),t.fillStyle="#333",t.beginPath(),t.moveTo(n+.2*s,.6*-s),t.lineTo(n+.8*s,.6*-s),t.lineTo(n+.5*s,.3*-s),t.fill()}e.onDrawTitle&&e.onDrawTitle(t)}if(o){e.onBounding&&e.onBounding(p),u==LiteGraph.TRANSPARENT_TITLE&&(p[1]-=l,p[3]+=l),t.lineWidth=2,t.globalAlpha=.8,t.beginPath();let i=0,o=0,a=1;d==LiteGraph.BOX_SHAPE?t.rect(i+p[0],i+p[1],o+p[2],o+p[3]):d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CARD_SHAPE&&e.flags.collapsed?t.roundRect(i+p[0],i+p[1],o+p[2],o+p[3],[this.round_radius*a]):d==LiteGraph.CARD_SHAPE?t.roundRect(i+p[0],i+p[1],o+p[2],o+p[3],[this.round_radius*a,a,this.round_radius*a,a]):d==LiteGraph.CIRCLE_SHAPE&&t.arc(.5*s[0],.5*s[1],.5*s[0]+6,0,2*Math.PI),t.strokeStyle=LiteGraph.NODE_BOX_OUTLINE_COLOR,t.stroke(),t.strokeStyle=n,t.globalAlpha=1}e.execute_triggered>0&&e.execute_triggered--,e.action_triggered>0&&e.action_triggered--}function de(e,t,s,n){var i,o;if(!e.widgets||!e.widgets.length)return 0;let a=e.size[0],l=(e.size[1],e.widgets);t+=2;let r=LiteGraph.NODE_WIDGET_HEIGHT,d=this.ds.scale>.5;s.save(),s.globalAlpha=this.editor_alpha;let u=LiteGraph.WIDGET_OUTLINE_COLOR,c=LiteGraph.WIDGET_BGCOLOR,p=LiteGraph.WIDGET_TEXT_COLOR,h=LiteGraph.WIDGET_SECONDARY_TEXT_COLOR,m=12;for(let f=0;f1&&(a=1),s.fillStyle=y.options.hasOwnProperty("slider_color")?y.options.slider_color:n==y?u:O,s.beginPath(),s.roundRect(m,_,a*(v-24),r,[.25*r]),s.fill(),y.marker){let e=(y.marker-y.options.min)/t;e<0&&(e=0),e>1&&(e=1),s.fillStyle=y.options.hasOwnProperty("marker_color")?y.options.marker_color:"#AA9",s.roundRect(m+e*(v-24),_,2,r,[.25*r])}if(d){s.textAlign="center",s.fillStyle=p;let e=(y.label||y.name)+" : "+Number(y.value).toFixed(null!=y.options.precision?y.options.precision:3);s.fillText(e,.5*v,_+.7*r)}break;case"number":case"combo":if(s.textAlign="left",s.strokeStyle=u,s.fillStyle=c,s.beginPath(),d?s.roundRect(m,_,v-24,r,[.25*r]):s.rect(m,_,v-24,r),s.fill(),d){y.disabled||s.stroke(),s.fillStyle=p,y.disabled||(s.beginPath(),s.moveTo(24,_+6.5),s.lineTo(18,_+.5*r),s.lineTo(24,_+r-6.5),s.fill(),s.beginPath(),s.moveTo(v-m-12,_+6.5),s.lineTo(v-m-6,_+.5*r),s.lineTo(v-m-12,_+r-6.5),s.fill()),s.fillStyle=h,s.font="10px Inter",s.fillText(y.label||y.name,29,_+.7*r),s.fillStyle=p,s.textAlign="right";let e=6;if("number"==y.type)s.font="10px Inter",s.fillText(Number(y.value).toFixed(void 0!==y.options.precision?y.options.precision:3),v-24-e,_+.7*r);else{let t=y.value;if(y.options.values){let e=y.options.values;e.constructor===Function&&(e=e()),e&&e.constructor!==Array&&(t=e[y.value])}const n=v-48-(s.measureText(y.label||y.name).width+24),i=s.measureText(t).width;if(i>n){const e="…",o=s.measureText(e).width,a=s.measureText("a").width;if(n<=o)t="․";else{t=`${t}`;if(i+o-n+3*a>n){const e=n+3*a,s=Math.floor((e-o)/a);t=t.substr(0,s)}for(;s.measureText(t).width+o>n;)t=t.substr(0,t.length-1);t+=e}}s.fillText(t,v-24-e,_+.7*r)}}break;case"string":case"text":if(s.textAlign="left",s.strokeStyle=u,s.fillStyle=c,s.beginPath(),d?s.roundRect(m,_,v-24,r,[.25*r]):s.rect(m,_,v-24,r),s.fill(),d){y.disabled||s.stroke(),s.save(),s.beginPath(),s.rect(m,_,v-24,r),s.clip(),s.fillStyle=h;const e=y.label||y.name;s.font="10px Inter",null!=e&&s.fillText(e,24,_+.7*r),s.fillStyle=p,s.textAlign="right",s.fillText(String(y.value).substr(0,30),v-24,_+.7*r),s.restore()}break;default:y.draw&&y.draw(s,e,v,_,r)}t+=(y.computeSize?y.computeSize(v)[1]:r)+4,s.globalAlpha=this.editor_alpha}s.restore(),s.textAlign="left"}function ue(e,t,s,n,i){return new LiteGraph.ContextMenu(LiteGraph.NODE_MODES,{event:s,callback:function(e){if(!i)return;var t=Object.values(LiteGraph.NODE_MODES).indexOf(e),s=function(e){t>=0&&LiteGraph.NODE_MODES[t]?e.changeMode(t):e.changeMode(LiteGraph.ALWAYS),q||(q=$()),q.update()},n=LGraphCanvas.active_canvas;if(!n.selected_nodes||Object.keys(n.selected_nodes).length<=1)s(i);else for(var o in n.selected_nodes)s(n.selected_nodes[o])},parentMenu:n,node:i}),!1}function ce(e,t,s,n,i){if(!i)throw"no node for color";var o=[];for(var a in o.push({value:null,content:"No color"}),LGraphCanvas.node_colors){var l=LGraphCanvas.node_colors[a];e={value:a,content:""+a+""};o.push(e)}return new LiteGraph.ContextMenu(o,{event:s,callback:function(e){if(!i)return;var t=e.value?LGraphCanvas.node_colors[e.value]:null,s=function(e){t?e.constructor===LiteGraph.LGraphGroup?e.color=t.groupcolor:(e.color=t.color,e.bgcolor=t.bgcolor):(delete e.color,delete e.bgcolor),q||(q=$()),q.update()},n=LGraphCanvas.active_canvas;if(!n.selected_nodes||Object.keys(n.selected_nodes).length<=1)s(i);else for(var o in n.selected_nodes)s(n.selected_nodes[o]);i.setDirtyCanvas(!0,!0)},parentMenu:n,node:i}),!1}function pe(e,t,s,n,i){var o=e.property||"title",a=i[o],l=document.createElement("div");l.is_modified=!1,l.className="graphdialog",l.innerHTML="",l.close=function(){l.parentNode&&l.parentNode.removeChild(l)},l.querySelector(".name").innerText=o;var r=l.querySelector(".value");r&&(r.value=a,r.addEventListener("blur",(function(e){this.focus()})),r.addEventListener("keydown",(function(e){if(l.is_modified=!0,27==e.keyCode)l.close();else if(13==e.keyCode)m();else if(13!=e.keyCode&&"textarea"!=e.target.localName)return;e.preventDefault(),e.stopPropagation()})));var d=LGraphCanvas.active_canvas.canvas,u=d.getBoundingClientRect(),c=-20,p=-20;u&&(c-=u.left,p-=u.top),event?(l.style.left=event.clientX+c+"px",l.style.top=event.clientY+p+"px"):(l.style.left=.5*d.width+c+"px",l.style.top=.5*d.height+p+"px"),l.querySelector("button").addEventListener("click",m),d.parentNode.appendChild(l),r&&r.focus();var h=null;function m(){r&&function(t){"Number"==e.type?t=Number(t):"Boolean"==e.type&&(t=Boolean(t));i[o]=t,l.parentNode&&l.parentNode.removeChild(l);i.setDirtyCanvas(!0,!0),q||(q=$());q.update()}(r.value)}l.addEventListener("mouseleave",(function(e){LiteGraph.dialog_close_on_mouse_leave&&!l.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(h=setTimeout(l.close,LiteGraph.dialog_close_on_mouse_leave_delay))})),l.addEventListener("mouseenter",(function(e){LiteGraph.dialog_close_on_mouse_leave&&h&&clearTimeout(h)}))}v.registerExtension({name:"Comfy.EasyUse.UI",init(){var e,t;const s="Comfy.CustomColorPalettes",n="Comfy.Settings.Comfy.CustomColorPalettes";if(ne||(ne=A(s,n,{})),ie||(ie=A("Comfy.ColorPalette","Comfy.Settings.Comfy.ColorPalette")||"dark"),(!(null==(e=null==ne?void 0:ne.obsidian)?void 0:e.version)||ne.obsidian.version{(null==e?void 0:e.value)&&(null==e?void 0:e.oldValue)&&(await X(1),Object.assign(v.canvas.default_connection_color_byType,D),Object.assign(LGraphCanvas.link_type_colors,D)),"custom_milk_white"==e.value&&document.body.classList.remove(T)})),setTimeout((e=>he(A("Comfy.UseNewMenu")||"Disabled")),1);const t=null==(e=v.ui.settings.settingsLookup)?void 0:e["Comfy.UseNewMenu"];t&&(t.onChange=e=>he(e))},async nodeCreated(e){var t;if(ee.hasOwnProperty(e.comfyClass)){const t=ee[e.comfyClass],s=te[t];if(!s)return;s.color&&(e.color=s.color),s.bgcolor&&(e.bgcolor=s.bgcolor)}if(se||(se=A("Comfy.WidgetControlMode")),"before"==se){const s="before"==se;if((null==(t=e.widgets)?void 0:t.length)>0)for(const t of e.widgets)if(["control_before_generate","control_after_generate"].includes(t.name)&&(await le(t,s),t.linkedWidgets))for(const e of t.linkedWidgets)await le(e,s)}}});const he=e=>{var t;const s=(null==(t=document.getElementById("crystools-root"))?void 0:t.children)||null,n=A("Comfy.Workflow.WorkflowTabsPosition",null,"");if((null==s?void 0:s.length)>0&&n)if(oe||(oe=document.getElementById("MonitorUI")),"Disabled"==e){document.getElementById("crystools-root").appendChild(oe)}else{let e=document.getElementById("crystools-root-easyuse");if(e)e.appendChild(oe);else{const e=document.getElementsByClassName("comfyui-menu-right");e.length>0&&e[0].before(w("div",{id:"crystools-root-easyuse"},oe))}}};let me={};const ge=(e,t)=>e.widgets.find((e=>e.name===t)),fe=(e,t,s=!1,n="")=>{var i;if(!t||((e,t)=>!!e.inputs&&e.inputs.some((e=>e.name===t)))(e,t.name))return;me[t.name]||(me[t.name]={origType:t.type,origComputeSize:t.computeSize});const o=e.size;t.type=s?me[t.name].origType:"easyHidden"+n,t.computeSize=s?me[t.name].origComputeSize:()=>[0,-4],null==(i=t.linkedWidgets)||i.forEach((n=>fe(e,n,":"+t.name,s)));const a=s?Math.max(e.computeSize()[1],o[1]):e.size[1];e.setSize([e.size[0],a])},ye=(e,t=0)=>{var s,n;if(e)return(null==(s=e.widgets)?void 0:s[t])?e.widgets[t].value:e.widgets_values?null==(n=e.widgets_values)?void 0:n[t]:void 0},_e=e=>e.setSize([e.size[0],e.computeSize()[1]]),ve=(e,t)=>graph.getNodeById(e),be=e=>{var t;try{return Object.values(null==(t=null==graph?void 0:graph.list_of_graphcanvas[0])?void 0:t.selected_nodes)}catch(s){return[]}};function we(e,t,s){return e+(n=s,(.5-.5*Math.cos(Math.PI*n))*(t-e));var n}const Le=(e,t=!0)=>{var s,n;const i=(null==(n=null==(s=e.graph)?void 0:s.list_of_graphcanvas)?void 0:n[0])||null;if(!i)return;const[o,a]=e.pos,[l,r]=e.size;(([e,t],s)=>{const n=s.ds,i=document.body.clientWidth,o=document.body.clientHeight,a=n.scale,l=.5*i/a-e,r=.5*o/a-t,d=Date.now()+250,u=n.offset[0],c=n.offset[1],p=()=>{const e=d-Date.now();if(!(Date.now(){const t=ve(e);t&&Le(t)},Ee=(e,t=(()=>graph.links??[])())=>t[e],Ce=e=>e.toLowerCase().replace(/_./g,(e=>e.replace("_","").toUpperCase())),ke=e=>"easy getNode"===e.type,Ae=e=>"easy setNode"===e.type,xe=e=>ke(e)||Ae(e),Ie=(e=(()=>graph._nodes??[])())=>e.filter((e=>xe(e)));let Ne={},Te={};const Oe=(e,t,s=0)=>{e.widgets_values||(e.widgets_values=[]),e.widgets_values[s]=t,e.widgets[s].value=t},De=e=>graph.add(e),Re=e=>graph.remove(e),Ge=(e,t=0)=>{var s,n;if("Reroute"!==e.type)return[e,t];const i=e,o=null==(n=null==(s=i.inputs)?void 0:s[0])?void 0:n.link;if(!o)return[i,t];const a=Ee(o);if(!a)return[i,t];const l=ve(a.origin_id);return l?(setTimeout((()=>{Re(i)})),Ge(l,a.origin_slot)):[i,t]},Me=e=>{var t,s,n;if("Reroute"!==e.type)return e;const i=e,o=null==(s=null==(t=i.outputs)?void 0:t[0])?void 0:s.links;if(!o)return i;const a=o[0];if(!a)return i;const l=Ee(a);if(!l)return i;const r=ve(l.target_id);return r?(1===(null==(n=i.outputs[0].links)?void 0:n.length)&&setTimeout((()=>{Re(i)})),Me(r)):i},Pe=(e,t="width")=>{var s;const n=e[0],i="width"==t?0:1,o=null==(s=n.size)?void 0:s[i];o&&(e.forEach((e=>{e.size[i]=o})),LGraphCanvas.active_canvas.setDirty(!0,!0))},Fe=(e,t="horizontal")=>{if(e.length<3)return;const s="horizontal"===t?0:1;e.sort(((e,t)=>e.pos[s]-t.pos[s]));const n=Math.min(...e.map((e=>e.pos[s]))),i=(Math.max(...e.map((e=>e.pos[s]+e.size[s])))-n-e.reduce(((e,t)=>e+t.size[s]),0))/(e.length-1);let o=n;e.forEach((e=>{e.pos[s]=o,o+=e.size[s]+i})),LGraphCanvas.active_canvas.setDirty(!0,!0)};const Ue=new class{constructor(){g(this,"element",w(`div.${N}toast`)),g(this,"children",HTMLElement),g(this,"container",document.body),this.container.appendChild(this.element)}async show(e){let t=w(`div.${N}toast-container`,[w("div",[w("span",[...e.icon?[w("i",{className:e.icon})]:[],w("span",e.content)])])]);t.setAttribute("toast-id",e.id),this.element.replaceChildren(t),this.container.appendChild(this.element),await X(64),t.style.marginTop=`-${t.offsetHeight}px`,await X(64),t.classList.add("show"),e.duration&&(await X(e.duration),this.hide(e.id))}async hide(e){const t=document.querySelector(`.${N}toast > [toast-id="${e}"]`);(null==t?void 0:t.classList.contains("show"))&&(t.classList.remove("show"),await X(750)),t&&t.remove()}async clearAllMessages(){let e=document.querySelector(`.${N}container`);e&&(e.innerHTML="")}async info(e,t=3e3,s=[]){this.show({id:"toast-info",icon:`mdi mdi-information ${N}theme`,content:e,duration:t})}async success(e,t=3e3){this.show({id:"toast-success",icon:`mdi mdi-check-circle ${N}success`,content:e,duration:t})}async error(e,t=3e3){this.show({id:"toast-error",icon:`mdi mdi-close-circle ${N}error`,content:e,duration:t})}async warn(e,t=3e3){this.show({id:"toast-warn",icon:`mdi mdi-alert-circle ${N}warning`,content:e,duration:t})}async showLoading(e,t=0){this.show({id:"toast-loading",icon:"mdi mdi-rotate-right loading",content:e,duration:t})}async hideLoading(){this.hide("toast-loading")}},Be=["rescale_after_model","rescale","lora_name","upscale_method","image_output","add_noise","info","sampler_name","ckpt_B_name","ckpt_C_name","save_model","refiner_ckpt_name","num_loras","num_controlnet","mode","toggle","resolution","ratio","target_parameter","input_count","replace_count","downscale_mode","range_mode","text_combine_mode","input_mode","lora_count","ckpt_count","conditioning_mode","preset","use_tiled","use_batch","num_embeds","easing_mode","guider","scheduler","inpaint_mode","t5_type","rem_mode","encode"],ze=["LIGHT - SD1.5 only (low strength)","STANDARD (medium strength)","VIT-G (medium strength)","REGULAR - FLUX and SD3.5 only (high strength)","PLUS (high strength)","PLUS (kolors genernal)","PLUS FACE (portraits)","FULL FACE - SD1.5 only (portraits stronger)","COMPOSITION"],We=["FACEID","FACEID PLUS - SD1.5 only","FACEID PLUS V2","FACEID PLUS KOLORS","FACEID PORTRAIT (style transfer)","FACEID PORTRAIT UNNORM - SDXL only (strong)"],je=["easy seed","easy latentNoisy","easy wildcards","easy preSampling","easy preSamplingAdvanced","easy preSamplingNoiseIn","easy preSamplingSdTurbo","easy preSamplingCascade","easy preSamplingDynamicCFG","easy preSamplingLayerDiffusion","easy fullkSampler","easy fullCascadeKSampler"],Ve=["easy fullLoader","easy a1111Loader","easy comfyLoader","easy hyditLoader","easy pixArtLoader"],Ye=["easy imageSize","easy imageSizeBySide","easy imageSizeByLongerSide","easy imageSizeShow","easy imageRatio","easy imagePixelPerfect"],He=["easy forLoopStart","easy forLoopEnd","easy whileLoopStart","easy whileLoopEnd"],Xe=["easy anythingIndexSwitch","easy imageIndexSwitch","easy textIndexSwitch","easy conditioningIndexSwitch"],Ze=["easy anythingInversedSwitch"],Ke=["easy loadImagesForLoop",...He,...Xe,...Ze],Je={"easy anythingInversedSwitch":"out","easy anythingIndexSwitch":"value","easy imageIndexSwitch":"image","easy textIndexSwitch":"text","easy conditioningIndexSwitch":"cond"};function $e(e,t){const s=e.comfyClass;let n=t.value;switch(t.name){case"range_mode":fe(e,ge(e,"step"),"step"==n),fe(e,ge(e,"num_steps"),"num_steps"==n),_e(e);break;case"text_combine_mode":fe(e,ge(e,"replace_text"),"replace"==n);break;case"lora_name":["lora_model_strength","lora_clip_strength"].map((t=>fe(e,ge(e,t),"None"!==n)));break;case"resolution":"自定义 x 自定义"===n&&(t.value="width x height (custom)"),["empty_latent_width","empty_latent_height","width","height"].map((t=>fe(e,ge(e,t),"width x height (custom)"===n)));break;case"ratio":["empty_latent_width","empty_latent_height"].map((t=>fe(e,ge(e,t),"custom"===n)));break;case"num_loras":var i=n+1,o=ge(e,"mode").value;for(let t=0;tfe(e,ge(e,t),"simple"!==o)));for(let t=i;t<21;t++)["lora_"+t+"_name","lora_"+t+"_strength","lora_"+t+"_model_strength","lora_"+t+"_clip_strength"].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"num_controlnet":i=n+1,o=ge(e,"mode").value;for(let t=0;tfe(e,ge(e,t),!0))),["start_percent_"+t,"end_percent_"+t].map((t=>fe(e,ge(e,t),"simple"!==o)));for(let t=i;t<21;t++)["controlnet_"+t,"controlnet_"+t+"_strength","scale_soft_weight_"+t,"start_percent_"+t,"end_percent_"+t].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"mode":switch(null==e?void 0:e.comfyClass){case"easy loraStack":i=ge(e,"num_loras").value+1,o=n;for(let t=0;tfe(e,ge(e,t),"simple"!==o)));_e(e);break;case"easy controlnetStack":i=ge(e,"num_controlnet").value+1,o=n;for(let t=0;tfe(e,ge(e,t),"simple"!==o)));_e(e);break;case"easy icLightApply":o=n;["lighting","remove_bg"].map((t=>fe(e,ge(e,t),"Foreground"===o))),fe(e,ge(e,"source"),"Foreground"!==o),_e(e)}break;case"toggle":t.type="toggle",t.options={on:"Enabled",off:"Disabled"};break;case"t5_type":["clip_name","padding"].map((t=>fe(e,ge(e,t),"sd3"==n))),["t5_name","device","dtype"].map((t=>fe(e,ge(e,t),"t5v11"==n))),_e(e);break;case"preset":if("FLUX.1-dev"==n&&(t.value="REGULAR - FLUX and SD3.5 only (high strength)"),ze.includes(n)){let t=ge(e,"use_tiled");fe(e,ge(e,"lora_strength")),fe(e,ge(e,"provider"),!!["REGULAR - FLUX and SD3.5 only (high strength)"].includes(n)),fe(e,ge(e,"weight_faceidv2")),fe(e,ge(e,"weight_kolors")),fe(e,ge(e,"use_tiled"),!0),fe(e,ge(e,"sharpening"),t&&t.value)}else We.includes(n)&&(fe(e,ge(e,"weight_faceidv2"),!!["FACEID PLUS V2","FACEID PLUS KOLORS"].includes(n)),fe(e,ge(e,"weight_kolors"),!!["FACEID PLUS KOLORS"].includes(t.value)),["FACEID PLUS KOLORS","FACEID PORTRAIT (style transfer)","FACEID PORTRAIT UNNORM - SDXL only (strong)"].includes(n)?fe(e,ge(e,"lora_strength"),!1):fe(e,ge(e,"lora_strength"),!0),fe(e,ge(e,"provider"),!0),fe(e,ge(e,"use_tiled")),fe(e,ge(e,"sharpening")));_e(e);break;case"use_tiled":fe(e,ge(e,"sharpening"),!!n),_e(e);break;case"num_embeds":i=n+1;for(let t=0;tfe(e,ge(e,t),!1)));break;case"brushnet_random":case"brushnet_segmentation":["dtype","scale","start_at","end_at"].map((t=>fe(e,ge(e,t),!0))),["fitting","function"].map((t=>fe(e,ge(e,t),!1)));break;case"powerpaint":["dtype","fitting","function","scale","start_at","end_at"].map((t=>fe(e,ge(e,t),!0)))}_e(e);break;case"encode":fe(e,ge(e,"noise_mask"),!!["inpaint_model_conditioning","different_diffusion"].includes(n)),_e(e);break;case"image_output":fe(e,ge(e,"link_id"),!!["Sender","Sender&Save"].includes(n)),fe(e,ge(e,"decode_vae_name"),!!["Hide","Hide&Save"].includes(n)),["save_prefix","output_path","embed_workflow","number_padding","overwrite_existing"].map((t=>fe(e,ge(e,t),!!["Save","Hide&Save","Sender&Save"].includes(n))));break;case"add_noise":var a=ge(e,"control_before_generate"),l=ge(e,"control_after_generate")||a;"disable"===n?(fe(e,ge(e,"seed")),l&&(l.last_value=l.value,l.value="fixed",fe(e,l))):("enable"===n&&(t.value="enable (CPU)"),fe(e,ge(e,"seed"),!0),l&&((null==l?void 0:l.last_value)&&(l.value=l.last_value),fe(e,l,!0))),_e(e);break;case"guider":switch(n){case"Basic":case"IP2P+Basic":["cfg_negative"].map((t=>fe(e,ge(e,t))));break;case"CFG":case"IP2P+CFG":fe(e,ge(e,"cfg"),!0),fe(e,ge(e,"cfg_negative"));break;case"DualCFG":case"IP2P+DualCFG":["cfg","cfg_negative"].map((t=>fe(e,ge(e,t),!0)))}_e(e);break;case"scheduler":["karrasADV","exponentialADV","polyExponential"].includes(n)?(["sigma_max","sigma_min"].map((t=>fe(e,ge(e,t),!0))),["denoise","beta_d","beta_min","eps_s","coeff"].map((t=>fe(e,ge(e,t))),!1),fe(e,ge(e,"rho"),"exponentialADV"!=n)):"vp"==n?(["sigma_max","sigma_min","denoise","rho","coeff"].map((t=>fe(e,ge(e,t)))),["beta_d","beta_min","eps_s"].map((t=>fe(e,ge(e,t),!0)))):(["sigma_max","sigma_min","beta_d","beta_min","eps_s","rho"].map((t=>fe(e,ge(e,t)))),fe(e,ge(e,"coeff"),"gits"==n),fe(e,ge(e,"denoise"),!0)),_e(e);break;case"conditioning_mode":["replace","concat","combine"].includes(n)?["average_strength","old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t)))):"average"==n?(fe(e,ge(e,"average_strength"),!0),["old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t),!1)))):"timestep"==n&&(["average_strength"].map((t=>fe(e,ge(e,t),!1))),["old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t)))));break;case"rescale":ge(e,"rescale_after_model").value,fe(e,ge(e,"width"),"to Width/Height"===n),fe(e,ge(e,"height"),"to Width/Height"===n),fe(e,ge(e,"percent"),"by percentage"===n),fe(e,ge(e,"longer_side"),"to longer side - maintain aspect"===n),_e(e);break;case"upscale_method":["factor","crop"].map((t=>fe(e,ge(e,t),"None"!==n)));break;case"target_parameter":switch(s){case"easy XYInputs: Steps":["first_step","last_step"].map((t=>fe(e,ge(e,t),"steps"==n))),["first_start_step","last_start_step"].map((t=>fe(e,ge(e,t),"start_at_step"==n))),["first_end_step","last_end_step"].map((t=>fe(e,ge(e,t),"end_at_step"==n)));break;case"easy XYInputs: Sampler/Scheduler":let t=ge(e,"input_count").value+1;for(let s=0;sfe(e,ge(e,t),"strength"==n))),["first_start_percent","last_start_percent"].map((t=>fe(e,ge(e,t),"start_percent"==n))),["first_end_percent","last_end_percent"].map((t=>fe(e,ge(e,t),"end_percent"==n))),["strength","start_percent","end_percent"].map((t=>fe(e,ge(e,t),n!=t))),_e(e)}case"replace_count":i=n+1;for(let t=0;tfe(e,ge(e,t),!r)));for(let t=i;t<11;t++)["lora_name_"+t,"model_str_"+t,"clip_str_"+t].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"ckpt_count":i=n+1;var d=-1!=ge(e,"input_mode").value.indexOf("ClipSkip"),u=-1!=ge(e,"input_mode").value.indexOf("VAE");for(let t=0;tfe(e,ge(e,t),!1)));_e(e);break;case"input_count":i=n+1;var c=ge(e,"target_parameter").value;for(let t=0;tfe(e,ge(e,s),!!t)));["model_strength","clip_strength"].map((s=>fe(e,ge(e,s),!t)));break;case"easy XYInputs: Checkpoint":i=ge(e,"ckpt_count").value+1,d=-1!=ge(e,"input_mode").value.indexOf("ClipSkip"),u=-1!=ge(e,"input_mode").value.indexOf("VAE");for(let s=0;se.name===t));if(-1!==e){for(let t=e;t{var e;const t=this.computeSize();t[0]"info"===e.name));if(-1!==e&&this.widgets[e]){this.widgets[e].value=t}}requestAnimationFrame((()=>{var e;const t=this.computeSize();t[0]"prompt"==e.name));this.addWidget("button","get values from COMBO link","",(()=>{var t,n;const i=(null==(n=null==(t=this.outputs[1])?void 0:t.links)?void 0:n.length)>0?this.outputs[1].links[0]:null,o=s.graph._nodes.find((e=>{var t;return null==(t=e.inputs)?void 0:t.find((e=>e.link==i))}));if(i&&o){const t=o.inputs.find((e=>e.link==i)).widget.name,s=o.widgets.find((e=>e.name==t));let n=(null==s?void 0:s.options.values)||null;n&&(n=n.join("\n"),e.value=n)}else Ue.error(Y("No COMBO link"),3e3)}),{serialize:!1})}),Ve.includes(t.name)){let t=function(e){var t="";for(let s=0;se.name===t+"_prompt")),n="comfy-multiline-input wildcard_"+t+"_"+this.id.toString();if(-1==s&&e){const s=document.createElement("textarea");s.className=n,s.placeholder="Wildcard Prompt ("+t+")";const i=this.addDOMWidget(t+"_prompt","customtext",s,{getValue:e=>s.value,setValue(e){s.value=e},serialize:!1});i.inputEl=s,i.inputEl.readOnly=!0,s.addEventListener("input",(()=>{var e;null==(e=i.callback)||e.call(i,i.value)})),i.value=e}else if(this.widgets[s])if(e){this.widgets[s].value=e}else{this.widgets.splice(s,1);const e=document.getElementsByClassName(n);e&&e[0]&&e[0].remove()}}};e.prototype.onExecuted=function(e){null==l||l.apply(this,arguments);const n=t(e.positive),i=t(e.negative);s.call(this,n,"positive"),s.call(this,i,"negative")}}if(["easy sv3dLoader"].includes(t.name)){let t=function(e,t,s){switch(e){case"azimuth":return s.readOnly=!0,s.style.opacity=.6,"0:(0.0,0.0)"+(t>1?`\n${t-1}:(360.0,0.0)`:"");case"elevation":return s.readOnly=!0,s.style.opacity=.6,"0:(-90.0,0.0)"+(t>1?`\n${t-1}:(90.0,0.0)`:"");case"custom":return s.readOnly=!1,s.style.opacity=1,"0:(0.0,0.0)\n9:(180.0,0.0)\n20:(360.0,0.0)"}};e.prototype.onNodeCreated=async function(){o&&o.apply(this,[]);const e=this.widgets.find((e=>"easing_mode"==e.name)),s=this.widgets.find((e=>"batch_size"==e.name)),n=this.widgets.find((e=>"scheduler"==e.name));setTimeout((i=>{n.value||(n.value=t(e.value,s.value,n.inputEl))}),1),e.callback=e=>{n.value=t(e,s.value,n.inputEl)},s.callback=s=>{n.value=t(e.value,s,n.inputEl)}}}if(je.includes(n)&&(e.prototype.onNodeCreated=async function(){o&&o.apply(this,[]);const e=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),n=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));if("easy seed"==t.name){const t=this.addWidget("button","🎲 Manual Random Seed",null,(t=>{"fixed"!=n.value&&(n.value="fixed"),e.value=Math.floor(Math.random()*R),s.queuePrompt(0,1)}),{serialize:!1});e.linkedWidgets=[t,n]}},e.prototype.onAdded=async function(){i&&i.apply(this,[]);const e=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),t=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));setTimeout((s=>{"control_before_generate"==t.name&&0===e.value&&(e.value=Math.floor(Math.random()*R))}),1)}),"easy convertAnything"==n&&(e.prototype.onNodeCreated=async function(){o&&o.apply(this,[]);const e=this.widgets.find((e=>"output_type"==e.name)),t=t=>{this.outputs[0].type=e.value.toUpperCase(),this.outputs[0].name=e.value,this.outputs[0].label=e.value};setTimeout((e=>t()),10),e.callback=e=>t()}),"easy imageInsetCrop"==n){let t=function(e){const t=e.widgets[0];for(let s=1;s<=4;s++)"Pixels"===t.value?(e.widgets[s].options.step=80,e.widgets[s].options.max=8192):(e.widgets[s].options.step=10,e.widgets[s].options.max=99)};e.prototype.onAdded=async function(e){const s=this.widgets[0];let n=s.callback;s.callback=(...e)=>{t(this),n&&n.apply(s,[...e])},setTimeout((e=>{t(this)}),1)}}if(Ke.includes(n)){const t=e=>{switch(n){case"easy forLoopStart":return 0;case"easy forLoopEnd":return 1}},s=e=>{switch(n){case"easy forLoopStart":return 2;case"easy forLoopEnd":return 0}};e.prototype.onNodeCreated=async function(){if("easy loadImagesForLoop"==n&&(this.outputs[0].shape=5),He.includes(n)){const e=this.inputs.findIndex((e=>"flow"===e.name)),i=this.outputs.findIndex((e=>"flow"===e.name));if(-1!==e&&(this.inputs[e].shape=5),-1!==i&&(this.outputs[i].shape=5),"easy whileLoopStart"==n||"easy whileLoopEnd"==n)return;this.inputs=this.inputs.filter(((e,s)=>s<=t())),this.outputs=this.outputs.filter(((e,t)=>t<=s())),_e(this)}return Xe.includes(n)&&("easy textIndexSwitch"==n&&(this.widgets=this.widgets.filter(((e,t)=>t<=2))),this.inputs=this.inputs.filter(((e,t)=>t<=1)),_e(this)),null==o?void 0:o.apply(this,arguments)},e.prototype.onConnectionsChange=function(e,i,o,a){var l,r;if("easy whileLoopStart"!=n&&"easy whileLoopEnd"!=n&&a)if(1==e){let e=this.inputs.every((e=>null!==e.link)),s=this.inputs.filter((e=>!["condition","index","total"].includes(e.name)));if(He.includes(n)){if(e){if(s.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let e=s[s.length-1],t=parseInt(e.name.split("initial_value")[1])+1;if(this.inputs.find((e=>e.name==="initial_value"+t)))return;let n="initial_value"+t,i="value"+t;this.addInput(n,"*"),this.addOutput(i,"*")}else if(!o){const e=t();let s=this.inputs.findLastIndex((e=>e.link));if(i>=e&&(-1===s||i>=s)){let e=this.inputs[i];if(!e.name||["condition","total"].includes(e.name))return;let t=parseInt(e.name.split("initial_value")[1])+1,s=this.inputs.findIndex((e=>e.name==="initial_value"+t)),n=this.outputs.findIndex((e=>e.name==="value"+t));-1!==s&&this.removeInput(s),-1!==n&&this.removeOutput(n)}}}else if(Xe.includes(n))if(e){if(s.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let e=Je[n]+s.length;this.addInput(e,"*")}else o||i==this.inputs.length-2&&this.removeInput(i+1)}else if(2==e){let e=this.outputs.filter((e=>!["flow","index"].includes(e.name))),t=e.every((e=>{var t;return(null==(t=e.links)?void 0:t.length)>0}));if(He.includes(n)){if(t){if(e.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let t=e[e.length-1],s=parseInt(t.name.split("value")[1])+1;if(this.inputs.find((e=>e.name==="initial_value"+s)))return;if(this.outputs.find((e=>e.name==="value"+s)))return;let n="initial_value"+s,i="value"+s;this.addInput(n,"*"),this.addOutput(i,"*")}else if(!o){const e=s();let t=a.origin_slot,n=this.outputs.findLastIndex((e=>{var t;return(null==(t=e.links)?void 0:t.length)>0}));if(t>=e&&(-1===n||t>=n)){let e=this.outputs[t];if(!e.name||["flow","index"].includes(e.name))return;let s=parseInt(e.name.split("value")[1])+1,n=this.inputs.findIndex((e=>e.name==="initial_value"+s)),i=this.outputs.findIndex((e=>e.name==="value"+s));if(-1!==n&&(null==(l=this.inputs[n])?void 0:l.link))return;-1!==n&&this.removeInput(n),-1!==i&&this.removeOutput(i)}}}else if(Ze.includes(n))if(t){if(e.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let t=Je[n]+e.length;this.addOutput(t,"*")}else if(!o){let t=a.origin_slot;t==this.outputs.length-2&&0==(null==(r=e[t].links)?void 0:r.length)&&this.removeOutput(t+1)}}}}["easy fluxLoader","easy fullLoader"].includes(n)&&(e.prototype.onConnectionsChange=async function(e,t){r&&r.apply(this,[]);const s=this.inputs.find((e=>"model_override"===e.name)),n=this.inputs.find((e=>"vae_override"===e.name));fe(this,ge(this,"ckpt_name"),!(null==s?void 0:s.link)),fe(this,ge(this,"vae_name"),!(null==n?void 0:n.link))})},nodeCreated(e){if(e.comfyClass.startsWith("easy ")){if(e.widgets)for(const s of e.widgets){if(!Be.includes(s.name))continue;let t=s.value;$e(e,s),Object.defineProperty(s,"value",{get:e=>t,set(n){n!==t&&(t=n,$e(e,s))}})}const t=e.comfyClass;if("easy preDetailerFix"==t){const t=e.widgets.find((e=>"customtext"===e.type));if(!t)return;t.dynamicPrompts=!1,t.inputEl.placeholder="wildcard spec: if kept empty, this option will be ignored",t.serializeValue=()=>t.value}if("easy wildcards"==t){const t=e.widgets.find((e=>"text"==e.name));let s=1;Object.defineProperty(e.widgets[s],"value",{set:e=>{if("Select the LoRA to add to the text"!=e){let s=e;s.endsWith(".safetensors")&&(s=s.slice(0,-12)),t.value+=``}},get:e=>"Select the LoRA to add to the text"}),Object.defineProperty(e.widgets[s+1],"value",{set:e=>{"Select the Wildcard to add to the text"!=e&&(""!=t.value&&(t.value+=", "),t.value+=e)},get:e=>"Select the Wildcard to add to the text"}),e.widgets[s].serializeValue=e=>"Select the LoRA to add to the text",e.widgets[s+1].serializeValue=e=>"Select the Wildcard to add to the text"}if(Ye.includes(t)){const t=document.createElement("textarea");t.className="comfy-multiline-input",t.readOnly=!0;const s=e.addDOMWidget("info","customtext",t,{getValue:e=>t.value,setValue:e=>t.value=e,serialize:!1});s.inputEl=t,t.addEventListener("input",(()=>{var e;null==(e=s.callback)||e.call(s,s.value)}))}}}});const qe=LiteGraph.LGraphNode;v.registerExtension({name:"easy bookmark",registerCustomNodes(){class e extends qe{constructor(){super("🔖"),g(this,"type","easy bookmark"),g(this,"title","🔖"),g(this,"slot_start_y",-20),g(this,"___collapsed_width",0),g(this,"isVirtualNode",!0),g(this,"serialize_widgets",!0),g(this,"keypressBound",null),this.addWidget("text","shortcut_key","1",(e=>{""!==(e=e.trim()[0]||"1")&&(this.title="🔖 "+e)}),{y:8}),this.addWidget("number","zoom",1,(e=>{}),{y:8+LiteGraph.NODE_WIDGET_HEIGHT+4,max:2,min:.5,precision:2}),this.keypressBound=this.onKeypress.bind(this)}get _collapsed_width(){return this.___collapsed_width}set _collapsed_width(e){const t=v.canvas,s=t.canvas.getContext("2d");if(s){const e=s.font;s.font=t.title_text_font,this.___collapsed_width=40+s.measureText(this.title).width,s.font=e}}onAdded(){setTimeout((e=>{const t=this.widgets[0].value;t&&(this.title="🔖 "+t)}),1),window.addEventListener("keydown",this.keypressBound)}onRemoved(){window.removeEventListener("keydown",this.keypressBound)}onKeypress(e){const t=e.target;["input","textarea"].includes(t.localName)||this.widgets[0]&&e.key.toLocaleLowerCase()===this.widgets[0].value.toLocaleLowerCase()&&this.canvasToBookmark()}canvasToBookmark(){var e,t;const s=v.canvas;(null==(e=null==s?void 0:s.ds)?void 0:e.offset)&&(s.ds.offset[0]=16-this.pos[0],s.ds.offset[1]=40-this.pos[1]),null!=(null==(t=null==s?void 0:s.ds)?void 0:t.scale)&&(s.ds.scale=Number(this.widgets[1].value||1)),s.setDirty(!0,!0)}}LiteGraph.registerNodeType("easy bookmark",Object.assign(e,{title:"Bookmark 🔖"})),e.category="EasyUse/Util"}}),v.registerExtension({name:"Comfy.EasyUse.ChainNode",init(){v.canvas._mousemove_callback=e=>{A("EasyUse.Nodes.ChainGetSet",null,!0)&&((e=!1,t={})=>{var s,n,i,o,a;const l=Ie();if(!l||l.length<1)return;const r=be();if(0===r.length)return;let d=t.inputX||160,u=t.ouputX||60;if(r.filter((e=>xe(e))).length>1)return;for(const p of r){let o=t.inputY||10,a=t.outputY||30;const l=[],c=p.id;if(p.graph){Ne[c]||(Ne[c]=[]);for(const e of p.inputs??[]){const t=e.link;if(!t)continue;const{origin_id:s,target_slot:n}=Ee(t),i=ve(s);if(!i)continue;if(!xe(i))continue;let a=p.getConnectionPos(!0,n);Ne[c][n]||(Ne[c][n]=a),!Ne[c]||Ne[c][n][1]===a[1]&&Ne[c][n][0]===a[0]||(d=a[0]-Ne[c][n][0],o=a[1]-Ne[c][n][1],i.pos=[i.pos[0]+d,i.pos[1]+o]),Ne[c][n]=a,l.push(i)}Te[c]||(Te[c]=[]);for(const e of p.outputs??[])if(e.links&&p.graph)for(const t of e.links){const{target_id:e,target_slot:n,origin_slot:i}=Ee(t),o=ve(e);if(!o)continue;if(!xe(o))continue;const r=null==(s=o.outputs)?void 0:s.links;if((null==r?void 0:r.length)>1)return;const d=p.getConnectionPos(!1,i);Te[c][i]||(Te[c][i]=d),!Te[c]||Te[c][i][0]===d[0]&&Te[c][i][1]===d[1]||(u=d[0]-Te[c][i][0],a=d[1]-Te[c][i][1],o.pos=[o.pos[0]+u,o.pos[1]+a]),Te[c][i]=d,l.push(o)}if(e&&1===r.length){const e=[p,...l];(null==(i=null==(n=p.graph)?void 0:n.list_of_graphcanvas)?void 0:i[0]).selectNodes(e)}}}const c=r[0];if(!c)return;(null==(a=null==(o=c.graph)?void 0:o.list_of_graphcanvas)?void 0:a[0]).setDirty(!0,!0)})()};const e=LGraphCanvas.prototype.showLinkMenu;LGraphCanvas.prototype.showLinkMenu=function(t,s){return s.shiftKey?(((e,t=!1)=>{var s,n,i,o,a,l,r,d,u,c;const{type:p}=e;if("*"===p)return;let{origin_id:h,target_id:m,origin_slot:g,target_slot:f}=e,y=ve(h),_=ve(m);if(!y||!_)return!1;if("Reroute"===y.type){let e=0;[y,e]=Ge(y),h=null==y?void 0:y.id,g=e,void 0!==g&&-1!==g||(g=0)}if("Reroute"===_.type&&(_=Me(_),m=null==_?void 0:_.id,f=null==_?void 0:_.inputs.findIndex((e=>e.type===p)),void 0!==f&&-1!==f||(f=0)),void 0===h||void 0===m||!y||!_)return!1;if(t&&(xe(y)||xe(_)))return!1;let v=Ce((null==(s=_.getInputInfo(f))?void 0:s.name)??p.toLowerCase());v||(v=Ce((null==(i=null==(n=null==y?void 0:y.outputs)?void 0:n[g])?void 0:i.name)??(null==(a=null==(o=null==y?void 0:y.outputs)?void 0:o[g])?void 0:a.type.toString())??v+`_from_${h}_to_${m}`));let b,w=!1,L=!1;if(xe(y))v=ye(y),L=!0;else{const e=null==(r=null==(l=y.outputs)?void 0:l[g])?void 0:r.links;if(e)for(const t of e){const e=ve((null==(d=Ee(t))?void 0:d.target_id)??-1);e&&xe(e)&&Ae(e)&&(v=ye(e),L=!0)}if(!L){for(const e of Ie()){if(v!==ye(e)||!Ae(e))continue;const t=null==(u=e.inputs[0])?void 0:u.link;(null==(c=Ee(t))?void 0:c.origin_id)===y.id?L=!0:w=!0}w&&(v+=`_from_${h}_to_${m}`)}}if(!L){b=LiteGraph.createNode("easy setNode"),b.is_auto_link=!0;const e=y.getConnectionPos(!1,g);b.pos=[e[0]+20,e[1]],b.inputs[0].name=v,b.inputs[0].type=p,b.inputs[0].widget=_.inputs[f].widget,Oe(b,v),De(b),b.flags.collapsed=!0;let t=[];y.widgets?t=Object.values(y.widgets).map((e=>e.value)):y.widgets_values&&(t=JSON.parse(JSON.stringify(y.widgets_values))),y.connect(g,b,0),y.widgets_values=t,"PrimitiveNode"===y.type&&setTimeout((()=>{if(y){y.connect(g,b,0);for(const[e,s]of t.entries())Oe(y,s,e);null!==b&&b.setSize(b.computeSize())}}))}const S=LiteGraph.createNode("easy getNode"),E=_.getConnectionPos(!0,f);S.pos=[E[0]-150,E[1]],S.outputs[0].name=v,S.outputs[0].type=p,S.outputs[0].widget=_.inputs[f].widget,De(S),Oe(S,v),null===S||(S.flags.collapsed=!0,S.setSize(S.computeSize()),S.connect(0,_,f))})(t),!1):(e.apply(this,[t,s]),!1)}}});const Qe=async()=>{try{const{Running:e,Pending:t}=await b.getQueue();if(e.length>0||t.length>0)return void Ue.error(Y("Clean Failed")+":"+Y("Please stop all running tasks before cleaning GPU"));200==(await b.fetchApi("/easyuse/cleangpu",{method:"POST"})).status?Ue.success(Y("Clean SuccessFully")):Ue.error(Y("Clean Failed"))}catch(e){}};let et=[];function tt(e,t,s,n,i){var o=LGraphCanvas.active_canvas,a=o.getCanvasWindow(),l=o.graph;if(l)return function e(t,n){var r=LiteGraph.getNodeTypesCategories(o.filter||l.filter).filter((function(e){return e.startsWith(t)})),d=[];r.map((function(s){if(s){var n=new RegExp("^("+t+")"),i=s.replace(n,"").split("/")[0],o=""===t?i+"/":t+i+"/",a=i;-1!=a.indexOf("::")&&(a=a.split("::")[1]),-1===d.findIndex((function(e){return e.value===o}))&&d.push({value:o,content:a,has_submenu:!0,callback:function(t,s,n,i){e(t.value,i)}})}})),LiteGraph.getNodeTypesInCategory(t.slice(0,-1),o.filter||l.filter).map((function(e){if(!e.skip_list){var t={value:e.type,content:e.title,has_submenu:!1,callback:function(e,t,s,n){var a=n.getFirstEvent();o.graph.beforeChange();var l=LiteGraph.createNode(e.value);l&&(l.pos=o.convertEventToCanvasOffset(a),o.graph.add(l)),i&&i(l),o.graph.afterChange()}};d.push(t)}}));const u=A("EasyUse.ContextMenu.NodesSort",null,!0);""===t&&u&&(d=function(e){let t=[],s=[];return e.forEach((e=>{(null==e?void 0:e.value)&&G.includes(e.value.split("/")[0])?t.push(e):s.push(e)})),[{title:Y("ComfyUI Basic"),is_category_title:!0},...t,{title:Y("Others A~Z"),is_category_title:!0},...s.sort(((e,t)=>e.content.localeCompare(t.content)))]}(d)),new LiteGraph.ContextMenu(d,{event:s,parentMenu:n},a)}("",n),!1}v.registerExtension({name:"Comfy.EasyUse.ContextMenu",async setup(){LGraphCanvas.onMenuAdd=tt;const e=A("EasyUse.ContextMenu.ModelsThumbnails",null,!1),t=A("EasyUse.ContextMenu.ModelsThumbnailsLimit",null,500);if(e&&t>0){const e=await b.fetchApi(`/easyuse/models/thumbnail?limit=${t}`);if(200===e.status){let t=await e.json();et=t}else Ue.error(Y("Too many thumbnails, have closed the display"))}const s=LiteGraph.ContextMenu;LiteGraph.ContextMenu=function(e,t){if(A("EasyUse.ContextMenu.SubDirectories",null,!1)&&(null==t?void 0:t.callback)&&!e.some((e=>"string"!=typeof e))){const n=function(e,t){const s=e,n=[...s],i={},o=[],a=[],l=["ckpt","pt","bin","pth","safetensors"];if((null==e?void 0:e.length)>0){const t=ot(e[e.length-1]);if(!l.includes(t))return null}for(const r of s){const e=r.indexOf("/")>-1?"/":"\\",t=r.split(e);if(t.length>1){const s=t.shift();i[s]=i[s]||[],i[s].push(t.join(e))}else"CHOOSE"===r||r.startsWith("DISABLE ")?o.push(r):a.push(r)}if(Object.values(i).length>0){const e=t.callback;t.callback=null;const s=(t,s)=>{["None","无","無","なし"].includes(t.content)?e("None",s):e(n.find((e=>e.endsWith(t.content)),s))},r=(e,t="")=>{const n=t?t+"\\"+it(e):it(e),i=ot(e),o=(new Date).getTime();let a,r="";if(l.includes(i))for(let s=0;s{let s=[],n=[];const o=e.map((e=>{const o={},a=e.indexOf("/")>-1?"/":"\\",l=e.split(a);if(l.length>1){const e=l.shift();o[e]=o[e]||[],o[e].push(l.join(a))}if(Object.values(i).length>0){let t=Object.keys(o)[0];t&&o[t]?s.push({key:t,value:o[t][0]}):n.push(r(e,t))}return r(e,t)}));if(s.length>0){let e={};return s.forEach((t=>{e[t.key]=e[t.key]||[],e[t.key].push(t.value)})),[...Object.entries(e).map((e=>({content:e[0],has_submenu:!0,callback:()=>{},submenu:{options:u(e[1],e[0])}}))),...n]}return o};for(const[t,n]of Object.entries(i))d.push({content:t,has_submenu:!0,callback:()=>{},submenu:{options:u(n,t)}});return d.push(...a.map((e=>r(e,"")))),o.length>0&&d.push(...o.map((e=>r(e,"")))),d}return null}(e,t);return n?s.call(this,n,t):s.apply(this,[...arguments])}if(t.parentMenu);else if(t.extra);else if(t.scale);else{const s=A("EasyUse.ContextMenu.QuickOptions",null,"At the forefront");if(t.hasOwnProperty("extra")&&"Disable"!==s){if("At the forefront"==s?e.unshift(null):e.push(null),n=window.location.host,["192.168.","10.","127.",/^172\.((1[6-9]|2[0-9]|3[0-1])\.)/].some((e=>"string"==typeof e?n.startsWith(e):e.test(n)))){const t={content:`${Y("Reboot ComfyUI")}`,callback:e=>(async()=>{if(confirm(Y("Are you sure you'd like to reboot the server?")))try{b.fetchApi("/easyuse/reboot")}catch(e){}})()};"At the forefront"==s?e.unshift(t):e.push(t)}const t=A("EasyUse.Hotkeys.cleanVRAMUsed",null,!0)?"("+J("Shift+r")+")":"",i={content:`${Y("Cleanup Of VRAM Usage")} ${t}`,callback:e=>Qe()};"At the forefront"==s?e.unshift(i):e.push(i);const o=A("EasyUse.Hotkeys.toggleNodesMap",null,!0)?"("+J("Shift+m")+")":"",a={content:`${Y("Nodes Map")} ${o}`,callback:e=>{var t,s,n;const i=(null==(t=v.extensionManager)?void 0:t.sidebarTab)||v.extensionManager,o=(null==(s=v.extensionManager.sidebarTab)?void 0:s.activeSidebarTabId)||(null==(n=v.extensionManager)?void 0:n.activeSidebarTab);i.activeSidebarTabId=o==P?null:P}};"At the forefront"==s?e.unshift(a):e.push(a)}}return s.apply(this,[...arguments]);var n},LiteGraph.ContextMenu.prototype=s.prototype,A("EasyUse.ContextMenu.NodesSort",null,!0)&&(LiteGraph.ContextMenu.prototype.addItem=nt)}});const st=e=>e&&"object"==typeof e&&"image"in e&&e.content;function nt(e,t,s){var n=this;s=s||{};var i=document.createElement("div");i.className="litemenu-entry submenu";var o,a=!1;function l(e){var t=this.value,i=!0;(n.current_submenu&&n.current_submenu.close(e),s.callback)&&(!0===s.callback.call(this,t,s,e,n,s.node)&&(i=!1));if(t){if(t.callback&&!s.ignore_item_callbacks&&!0!==t.disabled)!0===t.callback.call(this,t,s,e,n,s.extra)&&(i=!1);if(t.submenu){if(!t.submenu.options)throw"ContextMenu submenu needs options";new n.constructor(t.submenu.options,{callback:t.submenu.callback,event:e,parentMenu:n,ignore_item_callbacks:t.submenu.ignore_item_callbacks,title:t.submenu.title,extra:t.submenu.extra,autoopen:s.autoopen}),i=!1}}i&&!n.lock&&n.close()}return null===t?i.classList.add("separator"):t.is_category_title?(i.classList.remove("litemenu-entry"),i.classList.remove("submenu"),i.classList.add("litemenu-title"),i.innerHTML=t.title):(i.innerHTML=t&&t.title?t.title:e,i.value=t,t&&(t.disabled&&(a=!0,i.classList.add("disabled")),(t.submenu||t.has_submenu)&&i.classList.add("has_submenu")),"function"==typeof t?(i.dataset.value=e,i.onclick_callback=t):i.dataset.value=t,t.className&&(i.className+=" "+t.className)),i&&st(t)&&(null==t?void 0:t.image)&&!t.submenu&&(i.textContent+=" *",w("div.pysssss-combo-image",{parent:i,style:{backgroundImage:`url(/pysssss/view/${o=t.image,encodeURIComponent(o).replace(/[!'()*]/g,(e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`))})`}})),this.root.appendChild(i),a||i.addEventListener("click",l),!a&&s.autoopen&&LiteGraph.pointerListenerAdd(i,"enter",(function(e){var t=this.value;if(!t||!t.has_submenu)return;l.call(this,e)})),i}function it(e){return null==e?void 0:e.substring(0,e.lastIndexOf("."))}function ot(e){return null==e?void 0:e.substring(e.lastIndexOf(".")+1)}class at extends L{constructor(){super(),this.element.classList.add("easyuse-model-metadata")}show(e){super.show(w("div",Object.keys(e).map((t=>w("div",[w("label",{textContent:t}),w("span",{textContent:e[t]})])))))}}class lt extends L{constructor(e){super(),this.name=e,this.element.classList.add("easyuse-model-info")}get customNotes(){return this.metadata["easyuse.notes"]}set customNotes(e){this.metadata["easyuse.notes"]=e}get hash(){return this.metadata["easyuse.sha256"]}async show(e,t){this.type=e;const s=b.fetchApi("/easyuse/metadata/"+encodeURIComponent(`${e}/${t}`));this.info=w("div",{style:{flex:"auto"}}),this.imgCurrent=0,this.imgList=w("div.easyuse-preview-list",{style:{display:"none"}}),this.imgWrapper=w("div.easyuse-preview",[w("div.easyuse-preview-group",[this.imgList])]),this.main=w("main",{style:{display:"flex"}},[this.imgWrapper,this.info]),this.content=w("div.easyuse-model-content",[w("div.easyuse-model-header",[w("h2",{textContent:this.name})]),this.main]);const n=w("div",{textContent:"ℹ️ Loading...",parent:this.content});super.show(this.content),this.metadata=await(await s).json(),this.viewMetadata.style.cursor=this.viewMetadata.style.opacity="",this.viewMetadata.removeAttribute("disabled"),n.remove(),this.addInfo()}createButtons(){const e=super.createButtons();return this.viewMetadata=w("button",{type:"button",textContent:"View raw metadata",disabled:"disabled",style:{opacity:.5,cursor:"not-allowed"},onclick:e=>{this.metadata&&(new at).show(this.metadata)}}),e.unshift(this.viewMetadata),e}parseNote(){if(!this.customNotes)return[];let e=[];const t=new RegExp("(\\bhttps?:\\/\\/[^\\s]+)","g");let s,n=0;do{let i;s=t.exec(this.customNotes);let o=0;s?(i=s.index,o=s.index+s[0].length):i=this.customNotes.length;let a=this.customNotes.substring(n,i);a&&(a=a.replaceAll("\n","
"),e.push(w("span",{innerHTML:a}))),s&&e.push(w("a",{href:s[0],textContent:s[0],target:"_blank"})),n=o}while(s);return e}addInfoEntry(e,t){return w("p",{parent:this.info},["string"==typeof e?w("label",{textContent:e+": "}):e,"string"==typeof t?w("span",{textContent:t}):t])}async getCivitaiDetails(){const e=await fetch("https://civitai.com/api/v1/model-versions/by-hash/"+this.hash);if(200===e.status)return await e.json();throw 404===e.status?new Error("Model not found"):new Error(`Error loading info (${e.status}) ${e.statusText}`)}addCivitaiInfo(){const e=this.getCivitaiDetails(),t=w("span",{textContent:"ℹ️ Loading..."});return this.addInfoEntry(w("label",[w("img",{style:{width:"18px",position:"relative",top:"3px",margin:"0 5px 0 0"},src:"https://civitai.com/favicon.ico"}),w("span",{textContent:"Civitai: "})]),t),e.then((e=>{var t,s;this.imgWrapper.style.display="block";let n=this.element.querySelector(".easyuse-model-header");n&&n.replaceChildren(w("h2",{textContent:this.name}),w("div.easyuse-model-header-remark",[w("h5",{textContent:Y("Updated At:")+Z(new Date(e.updatedAt),"yyyy/MM/dd")}),w("h5",{textContent:Y("Created At:")+Z(new Date(e.updatedAt),"yyyy/MM/dd")})]));let i=null,o=this.parseNote.call(this),a=Y("✏️ Edit"),l=w("div.easyuse-model-detail-textarea",[w("p",(null==o?void 0:o.length)>0?o:{textContent:Y("No notes")})]);if(o&&0!=o.length?l.classList.remove("empty"):l.classList.add("empty"),this.info.replaceChildren(w("div.easyuse-model-detail",[w("div.easyuse-model-detail-head.flex-b",[w("span",Y("Notes")),w("a",{textContent:a,href:"#",style:{fontSize:"12px",float:"right",color:"var(--warning-color)",textDecoration:"none"},onclick:async e=>{if(e.preventDefault(),i){if(i.value!=this.customNotes){Ue.showLoading(Y("Saving Notes...")),this.customNotes=i.value;const e=await b.fetchApi("/easyuse/metadata/notes/"+encodeURIComponent(`${this.type}/${this.name}`),{method:"POST",body:this.customNotes});if(Ue.hideLoading(),200!==e.status)return Ue.error(Y("Saving Failed")),void alert(`Error saving notes (${e.status}) ${e.statusText}`);Ue.success(Y("Saving Succeed")),o=this.parseNote.call(this),l.replaceChildren(w("p",(null==o?void 0:o.length)>0?o:{textContent:Y("No notes")})),i.value?l.classList.remove("empty"):l.classList.add("empty")}else l.replaceChildren(w("p",{textContent:Y("No notes")})),l.classList.add("empty");e.target.textContent=a,i.remove(),i=null}else e.target.textContent="💾 Save",i=w("textarea",{placeholder:Y("Type your notes here"),style:{width:"100%",minWidth:"200px",minHeight:"50px",height:"100px"},textContent:this.customNotes}),l.replaceChildren(i),i.focus()}})]),l]),w("div.easyuse-model-detail",[w("div.easyuse-model-detail-head",{textContent:Y("Details")}),w("div.easyuse-model-detail-body",[w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Type")}),w("div.easyuse-model-detail-item-value",{textContent:e.model.type})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("BaseModel")}),w("div.easyuse-model-detail-item-value",{textContent:e.baseModel})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Download")}),w("div.easyuse-model-detail-item-value",{textContent:(null==(t=e.stats)?void 0:t.downloadCount)||0})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Trained Words")}),w("div.easyuse-model-detail-item-value",{textContent:(null==e?void 0:e.trainedWords.join(","))||"-"})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Source")}),w("div.easyuse-model-detail-item-value",[w("label",[w("img",{style:{width:"14px",position:"relative",top:"3px",margin:"0 5px 0 0"},src:"https://civitai.com/favicon.ico"}),w("a",{href:"https://civitai.com/models/"+e.modelId,textContent:"View "+e.model.name,target:"_blank"})])])])])])),null==(s=e.images)?void 0:s.length){this.imgCurrent=0,this.isSaving=!1,e.images.map((e=>e.url&&this.imgList.appendChild(w("div.easyuse-preview-slide",[w("div.easyuse-preview-slide-content",[w("img",{src:e.url}),w("div.save",{textContent:"Save as preview",onclick:async()=>{if(this.isSaving)return;this.isSaving=!0,Ue.showLoading(Y("Saving Preview..."));const t=await(await fetch(e.url)).blob(),s="temp_preview."+new URL(e.url).pathname.split(".")[1],n=new FormData;n.append("image",new File([t],s)),n.append("overwrite","true"),n.append("type","temp");if(200!==(await b.fetchApi("/upload/image",{method:"POST",body:n})).status)return this.isSaving=!1,Ue.error(Y("Saving Failed")),Ue.hideLoading(),void alert(`Error saving preview (${req.status}) ${req.statusText}`);await b.fetchApi("/easyuse/save/"+encodeURIComponent(`${this.type}/${this.name}`),{method:"POST",body:JSON.stringify({filename:s,type:"temp"}),headers:{"content-type":"application/json"}}).then((e=>{Ue.success(Y("Saving Succeed")),Ue.hideLoading()})),this.isSaving=!1,app.refreshComboInNodes()}})])]))));let t=this;this.imgDistance=(-660*this.imgCurrent).toString(),this.imgList.style.display="",this.imgList.style.transform="translate3d("+this.imgDistance+"px, 0px, 0px)",this.slides=this.imgList.querySelectorAll(".easyuse-preview-slide"),this.slideLeftButton=w("button.left",{parent:this.imgWrapper,style:{display:e.images.length<=2?"none":"block"},innerHTML:'',onclick:()=>{e.images.length<=2||(t.imgList.classList.remove("no-transition"),0==t.imgCurrent?(t.imgCurrent=e.images.length/2-1,this.slides[this.slides.length-1].style.transform="translate3d("+(-660*(this.imgCurrent+1)).toString()+"px, 0px, 0px)",this.slides[this.slides.length-2].style.transform="translate3d("+(-660*(this.imgCurrent+1)).toString()+"px, 0px, 0px)",t.imgList.style.transform="translate3d(660px, 0px, 0px)",setTimeout((e=>{this.slides[this.slides.length-1].style.transform="translate3d(0px, 0px, 0px)",this.slides[this.slides.length-2].style.transform="translate3d(0px, 0px, 0px)",t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)",t.imgList.classList.add("no-transition")}),500)):(t.imgCurrent=t.imgCurrent-1,t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)"))}}),this.slideRightButton=w("button.right",{parent:this.imgWrapper,style:{display:e.images.length<=2?"none":"block"},innerHTML:'',onclick:()=>{if(!(e.images.length<=2))if(t.imgList.classList.remove("no-transition"),t.imgCurrent>=e.images.length/2-1){t.imgCurrent=0;const s=e.images.length/2;this.slides[0].style.transform="translate3d("+(660*s).toString()+"px, 0px, 0px)",this.slides[1].style.transform="translate3d("+(660*s).toString()+"px, 0px, 0px)",t.imgList.style.transform="translate3d("+(-660*s).toString()+"px, 0px, 0px)",setTimeout((e=>{this.slides[0].style.transform="translate3d(0px, 0px, 0px)",this.slides[1].style.transform="translate3d(0px, 0px, 0px)",t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)",t.imgList.classList.add("no-transition")}),500)}else t.imgCurrent=t.imgCurrent+1,t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)"}})}return e.description&&w("div",{parent:this.content,innerHTML:e.description,style:{marginTop:"10px"}}),e})).catch((e=>{this.imgWrapper.style.display="none",t.textContent="⚠️ "+e.message})).finally((e=>{}))}}class rt extends lt{async addInfo(){await this.addCivitaiInfo()}}class dt extends lt{getTagFrequency(){if(!this.metadata.ss_tag_frequency)return[];const e=JSON.parse(this.metadata.ss_tag_frequency),t={};for(const s in e){const n=e[s];for(const e in n)e in t?t[e]+=n[e]:t[e]=n[e]}return Object.entries(t).sort(((e,t)=>t[1]-e[1]))}getResolutions(){let e=[];if(this.metadata.ss_bucket_info){const t=JSON.parse(this.metadata.ss_bucket_info);if(null==t?void 0:t.buckets)for(const{resolution:s,count:n}of Object.values(t.buckets))e.push([n,`${s.join("x")} * ${n}`])}e=e.sort(((e,t)=>t[0]-e[0])).map((e=>e[1]));let t=this.metadata.ss_resolution;if(t){const s=t.split(","),n=s[0].replace("(",""),i=s[1].replace(")","");e.push(`${n.trim()}x${i.trim()} (Base res)`)}else(t=this.metadata["modelspec.resolution"])&&e.push(t+" (Base res");return e.length||e.push("⚠️ Unknown"),e}getTagList(e){return e.map((e=>w("li.easyuse-model-tag",{dataset:{tag:e[0]},$:e=>{e.onclick=()=>{e.classList.toggle("easyuse-model-tag--selected")}}},[w("p",{textContent:e[0]}),w("span",{textContent:e[1]})])))}addTags(){let e,t=this.getTagFrequency();if(null==t?void 0:t.length){const s=t.length;let n;s>500&&(t=t.slice(0,500),e=w("p",[w("span",{textContent:"⚠️ Only showing first 500 tags "}),w("a",{href:"#",textContent:`Show all ${s}`,onclick:()=>{n.replaceChildren(...this.getTagList(this.getTagFrequency())),e.remove()}})])),n=w("ol.easyuse-model-tags-list",this.getTagList(t)),this.tags=w("div",[n])}else this.tags=w("p",{textContent:"⚠️ No tag frequency metadata found"});this.content.append(this.tags),e&&this.content.append(e)}async addInfo(){const e=this.addCivitaiInfo();this.addTags();const t=await e;t&&w("div",{parent:this.content,innerHTML:t.description,style:{maxHeight:"250px",overflow:"auto"}})}createButtons(){const e=super.createButtons();function t(e,t){const s=w("textarea",{parent:document.body,style:{position:"fixed"},textContent:t.map((e=>e.dataset.tag)).join(", ")});s.select();try{document.execCommand("copy"),e.target.dataset.text||(e.target.dataset.text=e.target.textContent),e.target.textContent="Copied "+t.length+" tags",setTimeout((()=>{e.target.textContent=e.target.dataset.text}),1e3)}catch(n){prompt("Copy to clipboard: Ctrl+C, Enter",text)}finally{document.body.removeChild(s)}}return e.unshift(w("button",{type:"button",textContent:"Copy Selected",onclick:e=>{t(e,[...this.tags.querySelectorAll(".easyuse-model-tag--selected")])}}),w("button",{type:"button",textContent:"Copy All",onclick:e=>{t(e,[...this.tags.querySelectorAll(".easyuse-model-tag")])}})),e}}const ut={pipe:{category:"Easy Pipe",nodes:["easy pipeIn","easy pipeOut","easy pipeEdit","easy pipeEditPrompt","easy pipeBatchIndex"],input:{pipe:"pipe"},output:{pipe:"pipe"},widget:{optional_positive:"optional_positive",optional_negative:"optional_negative"}},loaders:{category:"Easy Loaders",nodes:["easy fullLoader","easy a1111Loader","easy comfyLoader","easy kolorsLoader","easy hunyuanDiTLoader","easy pixArtLoader","easy fluxLoader"],input:{optional_lora_stack:"optional_lora_stack",optional_controlnet_stack:"optional_controlnet_stack",positive:"positive",negative:"negative"},output:{pipe:"pipe",model:"model",vae:"vae",clip:null,positive:null,negative:null,latent:null},widget:{ckpt_name:"ckpt_name",vae_name:"vae_name",clip_skip:"clip_skip",lora_name:"lora_name",resolution:"resolution",empty_latent_width:"empty_latent_width",empty_latent_height:"empty_latent_height",positive:"positive",negative:"negative",batch_size:"batch_size",a1111_prompt_style:"a1111_prompt_style"}},preSampling:{category:"Easy PreSampling",nodes:["easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG","easy preSamplingNoiseIn","easy preSamplingCustom","easy preSamplingLayerDiffusion","easy fullkSampler"],input:{pipe:"pipe",image_to_latent:"image_to_latent",latent:"latent"},output:{pipe:"pipe"},widget:{steps:"steps",cfg:"cfg",cfg_scale_min:"cfg",sampler_name:"sampler_name",scheduler:"scheduler",denoise:"denoise",seed_num:"seed_num",seed:"seed"}},samplers:{category:"Custom Sampler",nodes:["KSamplerSelect","SamplerEulerAncestral","SamplerEulerAncestralCFG++","SamplerLMS","SamplerDPMPP_3M_SDE","SamplerDPMPP_2M_SDE","SamplerDPMPP_SDE","SamplerDPMAdaptative","SamplerLCMUpscale","SamplerTCD","SamplerTCD EulerA"],output:{SAMPLER:"SAMPLER"}},sigmas:{category:"Custom Sigmas",nodes:["BasicScheduler","KarrasScheduler","ExponentialScheduler","PolyexponentialScheduler","VPScheduler","BetaSamplingScheduler","SDTurboScheduler","SplitSigmas","SplitSigmasDenoise","FlipSigmas","AlignYourStepsScheduler","GITSScheduler"],output:{SIGMAS:"SIGMAS"}},kSampler:{category:"Easy kSampler",nodes:["easy kSampler","easy kSamplerTiled","easy kSamplerCustom","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerLayerDiffusion"],input:{pipe:"pipe",model:"model"},output:{pipe:"pipe",image:"image"},widget:{image_output:"image_output",save_prefix:"save_prefix",link_id:"link_id"}},controlNet:{category:"Easy ControlNet",nodes:["easy controlnetLoader","easy controlnetLoaderADV","easy controlnetLoader++","easy instantIDApply","easy instantIDApplyADV"],input:{pipe:"pipe",image:"image",image_kps:"image_kps",control_net:"control_net",positive:"positive",negative:"negative",mask:"mask"},output:{pipe:"pipe",positive:"positive",negative:"negative"},widget:{control_net_name:"control_net_name",strength:["strength","cn_strength"],scale_soft_weights:["scale_soft_weights","cn_soft_weights"],cn_strength:["strength","cn_strength"],cn_soft_weights:["scale_soft_weights","cn_soft_weights"]}},adapter:{category:"Easy Adapter",nodes:["easy ipadapterApply","easy ipadapterApplyADV","easy ipadapterApplyFaceIDKolors","easy ipadapterStyleComposition","easy ipadapterApplyFromParams","easy pulIDApply","easy pulIDApplyADV"],input:{model:"model",image:"image",image_style:"image",attn_mask:"attn_mask",optional_ipadapter:"optional_ipadapter"},output:{model:"model",tiles:"tiles",masks:"masks",ipadapter:"ipadapter"},widget:{preset:"preset",lora_strength:"lora_strength",provider:"provider",weight:"weight",weight_faceidv2:"weight_faceidv2",start_at:"start_at",end_at:"end_at",cache_mode:"cache_mode",use_tiled:"use_tiled",insightface:"insightface",pulid_file:"pulid_file"}},positive:{category:"Easy Positive",nodes:["easy positive","easy wildcards"],input:{},output:{text:"positive",positive:"text"},widget:{text:"positive",positive:"text"}},loadImage:{category:"Easy LoadImage",nodes:["easy loadImageBase64","LoadImage","LoadImageMask"],input:{pipe:"pipe",image:"image",mask:"mask"},output:{IMAGE:"IMAGE",MASK:"MASK"},widget:{image:"image",base64_data:"base64_data",channel:"channel"}},saveImage:{category:"Save/Preview Image",nodes:["SaveImage","PreviewImage"]},inPaint:{category:"Easy Inpaint",nodes:["easy applyBrushNet","easy applyPowerPaint","easy applyInpaint"],input:{},output:{pipe:"pipe"},widget:{dtype:"dtype",fitting:"fitting",function:"function",scale:"scale",start_at:"start_at",end_at:"end_at"}},showAny:{category:"Show Anything",nodes:["easy showAnything","easy showAnythingLazy"],input:{anything:"anything"},output:{output:"output"}},saveText:{category:"Save Text",nodes:["easy saveText","easy saveTextLazy"],input:{image:"image",text:"text",output_file_path:"output_file_path",file_name:"file_name",file_extension:"file_extension",overwrite:"overwrite"},output:{text:"text",image:"image"},widget:{image:"image",text:"text",output_file_path:"output_file_path",file_name:"file_name",file_extension:"file_extension",overwrite:"overwrite"}},persona:{category:"LLM Party Persona",nodes:["load_persona","classify_persona","classify_persona_plus","custom_persona","translate_persona","flux_persona"],input:{file_content:"file_content"},output:{system_prompt:"system_prompt"},widget:{is_enable:"is_enable"}},llmModelLoader:{category:"LLM Model Loader",nodes:["LLM_api_loader","genai_api_loader","LLM_local_loader"],output:{model:"model"}},llmModelChain:{category:"LLM Model Chain",nodes:["LLM","LLM_local"],input:{model:"model",image:"images",images:"image",extra_parameters:"extra_parameters",system_prompt_input:"system_prompt_input",user_prompt_input:"user_prompt_input",tools:"tools",file_content:"file_content"},output:{assistant_response:"assistant_response",history:"history",tool:"tool",image:"image"},widget:{system_prompt:"system_prompt",user_prompt:"user_prompt",temperature:"temperature",is_memory:"is_memory",is_tools_in_sys_prompt:"is_tools_in_sys_prompt",max_length:"max_length",main_brain:"main_brain",conversation_rounds:"conversation_rounds",history_record:"history_record",is_enable:"is_enable"}},maskModify:{category:"Mask Modify",nodes:["CropMask","ThresholdMask","GrowMask","FeatherMask","LayerMask: MaskGrain","LayerMask: MaskEdgeUltraDetail","LayerMask: MaskEdgeUltraDetail V2"],input:{mask:"mask"},output:{MASK:"MASK",mask:"mask",image:"image"}},maskModifyWAS:{category:"Mask Modify (WAS)",nodes:["Mask Dilate Region","Mask Gaussian Region"],input:{masks:"masks"},output:{MASKS:"MASKS"}}};function ct(e,t,s){return function(){!function(e,t,s){var n;const i=LiteGraph.createNode(t);if(i){if(v.graph.add(i),i.pos=e.pos.slice(),i.size=e.size.slice(),(null==(n=e.widgets)?void 0:n.length)>0&&e.widgets.forEach((e=>{var t,n,o;if(null==(n=null==(t=ut[s])?void 0:t.widget)?void 0:n[e.name]){const t=ut[s].widget[e.name];if(t&&i.widgets){const s=(o=t,i.widgets.find((e=>"object"==typeof o?o.includes(e.name):e.name===o)));s&&(s.value=e.value,"seed_num"==e.name&&(s.linkedWidgets[0].value=e.linkedWidgets[0].value),"converted-widget"==e.type&&_t(i,s,e))}}})),e.inputs&&e.inputs.forEach(((t,n)=>{var o,a,l;if(t&&t.link&&(null==(a=null==(o=ut[s])?void 0:o.input)?void 0:a[t.name])){const n=null==(l=ut[s])?void 0:l.input[t.name];if(null===n)return;const o=i.findInputSlot(n);if(-1!==o){const s=e.graph.links[t.link];if(s){const t=e.graph.getNodeById(s.origin_id);t&&t.connect(s.origin_slot,i,o)}}}})),e.outputs&&e.outputs.forEach(((t,n)=>{var o,a;if(t&&t.links&&(null==(a=null==(o=ut[s])?void 0:o.output)?void 0:a[t.name])){const n=ut[s].output[t.name];if(null===n)return;const o=i.findOutputSlot(n);-1!==o&&t.links.forEach((t=>{const s=e.graph.links[t];if(s){const t=e.graph.getNodeById(s.target_id);t&&i.connect(o,t,s.target_slot)}}))}})),v.graph.remove(e),"easy fullkSampler"==i.type){const e=i.outputs[0].links;if(e&&e[0]){const t=v.graph._nodes.find((t=>t.inputs&&t.inputs[0]&&t.inputs[0].link==e[0]));t&&v.graph.remove(t)}}else if(ut.preSampling.nodes.includes(i.type)){const e=i.outputs[0].links;if(!e||!e[0]){const e=LiteGraph.createNode("easy kSampler");v.graph.add(e),e.pos=i.pos.slice(),e.pos[0]=e.pos[0]+i.size[0]+20;const t=i.findInputSlot("pipe");-1!==t&&i&&i.connect(0,e,t)}}i.setSize([i.size[0],i.computeSize()[1]])}}(e,t,s)}}const pt=(e,t)=>{const s=e.prototype.getExtraMenuOptions;e.prototype.getExtraMenuOptions=function(){const e=s.apply(this,arguments);return t.apply(this,arguments),e}},ht=(e,t,s,n,i=!0)=>{pt(n,(function(n,o){o.unshift({content:e,has_submenu:i,callback:(e,n,i,o,a)=>mt(e,n,i,o,a,t,s)}),"loaders"==t&&(o.unshift({content:Y("💎 View Lora Info..."),callback:(e,t,s,n,i)=>{let o=i.widgets.find((e=>"lora_name"==e.name)).value;o&&"None"!=o&&new dt(o).show("loras",o)}}),o.unshift({content:Y("💎 View Checkpoint Info..."),callback:(e,t,s,n,i)=>{let o=i.widgets[0].value;o&&"None"!=o&&new rt(o).show("checkpoints",o)}}))}))},mt=(e,t,s,n,i,o,a)=>{const l=[];return a.map((e=>{i.type!==e&&l.push({content:`${e}`,callback:ct(i,e,o)})})),new LiteGraph.ContextMenu(l,{event:s,callback:null,parentMenu:n,node:i}),!1},gt="converted-widget",ft=Symbol();function yt(e,t,s=""){if(t.origType=t.type,t.origComputeSize=t.computeSize,t.origSerializeValue=t.serializeValue,t.computeSize=()=>[0,-4],t.type=gt+s,t.serializeValue=()=>{if(!e.inputs)return;let s=e.inputs.find((e=>{var s;return(null==(s=e.widget)?void 0:s.name)===t.name}));return s&&s.link?t.origSerializeValue?t.origSerializeValue():t.value:void 0},t.linkedWidgets)for(const n of t.linkedWidgets)yt(e,n,":"+t.name)}function _t(e,t,s){yt(e,t);const{type:n}=function(e){let t=e[0];t instanceof Array&&(t="COMBO");return{type:t}}(s),i=e.size;t.options&&t.options.forceInput||e.addInput(t.name,n,{widget:{name:t.name,[ft]:()=>s}});for(const o of e.widgets)o.last_y+=LiteGraph.NODE_SLOT_HEIGHT;e.setSize([Math.max(i[0],e.size[0]),Math.max(i[1],e.size[1])])}const vt=function(e){var t,s,n,i;const o=e.constructor.type,a=e.properties.origVals||{},l=a.title||e.title,r=a.color||e.color,d=a.bgcolor||e.bgcolor,u=e,c={size:[...e.size],color:r,bgcolor:d,pos:[...e.pos]};let p=[],h=[];if(e.inputs)for(const y of e.inputs)if(y.link){const t=y.name,s=e.findInputSlot(t),n=e.getInputNode(s),i=e.getInputLink(s);p.push([i.origin_slot,n,t])}if(e.outputs)for(const y of e.outputs)if(y.links){const e=y.name;for(const t of y.links){const s=graph.links[t],n=graph._nodes_by_id[s.target_id];h.push([e,n,s.target_slot])}}v.graph.remove(e);let m=v.graph.add(LiteGraph.createNode(o,l,c));function g(){if(u.widgets)for(let e of u.widgets)if("converted-widget"===e.type){const t=m.widgets.find((t=>t.name===e.name));for(let s of u.inputs)s.name===e.name&&_t(m,t,s.widget)}for(let e of p){const[t,s,n]=e;s.connect(t,m.id,n)}for(let e of h){const[t,s,n]=e;m.connect(t,s,n)}}m.inputs.map(((t,s)=>{m.inputs[s].label=e.inputs[s].label})),m.outputs.map(((t,s)=>{m.outputs[s].label=e.outputs[s].label}));let f=u.widgets_values;if(!f&&(null==(t=m.widgets)?void 0:t.length)>0)return m.widgets.forEach(((e,t)=>{const s=u.widgets[t];e.name===s.name&&e.type===s.type&&(e.value=s.value)})),void g();if(f){let e=function(e,t){var s,n,i,o,a,l;if(!0===e||!1===e){if((null==(s=t.options)?void 0:s.on)&&(null==(n=t.options)?void 0:n.off))return{value:e,pass:!0}}else if("number"==typeof e){if((null==(i=t.options)?void 0:i.min)<=e&&e<=(null==(o=t.options)?void 0:o.max))return{value:e,pass:!0}}else{if(null==(l=null==(a=t.options)?void 0:a.values)?void 0:l.includes(e))return{value:e,pass:!0};if(t.inputEl&&"string"==typeof e)return{value:e,pass:!0}}return{value:t.value,pass:!1}},t=!1;const o=(null==f?void 0:f.length)<=(null==(s=m.widgets)?void 0:s.length);let a=o?0:f.length-1;const l=s=>{var n;const i=u.widgets[s];let l=m.widgets[s];if(l.name===i.name&&l.type===i.type){for(;(o?a=0)&&!t;){let{value:t,pass:s}=e(f[a],l);if(s&&null!==t){l.value=t;break}a+=o?1:-1}a++,o||(a=f.length-((null==(n=m.widgets)?void 0:n.length)-1-s))}};if(o&&(null==(n=m.widgets)?void 0:n.length)>0)for(let s=0;s0)for(let s=m.widgets.length-1;s>=0;s--)l(s)}g()};v.registerExtension({name:"Comfy.EasyUse.ExtraMenu",async beforeRegisterNodeDef(e,t,s){pt(e,(function(e,s){s.unshift({content:Y("🔃 Reload Node"),callback:(e,t,s,n,i)=>{let o=LGraphCanvas.active_canvas;if(!o.selected_nodes||Object.keys(o.selected_nodes).length<=1)vt(i);else for(let a in o.selected_nodes)vt(o.selected_nodes[a])}}),"easy ckptNames"==t.name&&s.unshift({content:Y("💎 View Checkpoint Info..."),callback:(e,t,s,n,i)=>{i.widgets[0].value}})}));for(const n in ut)ut[n].nodes.includes(t.name)&&ht(`↪️ Swap ${ut[n].category}`,n,ut[n].nodes,e)}});const bt=LiteGraph.LGraphNode,wt="➡️";v.registerExtension({name:"easy setNode",registerCustomNodes(){class e extends bt{constructor(t){super("Set"),g(this,"defaultVisibility",!0),g(this,"serialize_widgets",!0),this.properties||(this.properties={previousName:""}),this.properties.showOutputText=e.defaultVisibility;const s=this;s.color=LGraphCanvas.node_colors.blue.color,this.addWidget("text","Constant","",((e,t,n,i,o)=>{s.validateName(s.graph),""!==this.widgets[0].value&&(this.title=wt+this.widgets[0].value),this.properties.previousName=this.widgets[0].value,this.update()}),{}),this.addInput("*","*"),this.onConnectionsChange=function(e,t,n,i,o){if(1!=e||n||(this.inputs[t].type="*",this.inputs[t].name="*",this.title="Set"),i&&s.graph&&1==e&&n){const e=s.graph._nodes.find((e=>e.id==i.origin_id)).outputs[i.origin_slot],t=e.type,n=s.is_auto_link?this.widgets[0].value:e.name;"Set"===this.title&&(this.title=wt+n,this.widgets[0].value=n),"*"===this.widgets[0].value&&(this.widgets[0].value=n),this.validateName(s.graph),this.inputs[0].type=t,this.inputs[0].name=n,setTimeout((e=>{this.title=wt+this.widgets[0].value,this.properties.previousName=this.widgets[0].value}),1)}this.update()},this.validateName=function(e){let t=s.widgets[0].value;if(""!=t){let n=0,i=[];do{i=e._nodes.filter((e=>e!=this&&("easy setNode"==e.type&&e.widgets[0].value===t))),i.length>0&&(t=s.widgets[0].value+n),n++}while(i.length>0);s.widgets[0].value=t,this.update()}},this.clone=function(){const t=e.prototype.clone.apply(this);return t.inputs[0].name="*",t.inputs[0].type="*",t.properties.previousName="",t.size=t.computeSize(),t},this.onAdded=function(e){this.validateName(e)},this.update=function(){if(s.graph){this.findGetters(s.graph).forEach((e=>{e.setType(this.inputs[0].type)})),this.widgets[0].value&&this.findGetters(s.graph,!0).forEach((e=>{e.setName(this.widgets[0].value)}));s.graph._nodes.filter((e=>"easy getNode"==e.type)).forEach((e=>{e.setComboValues&&e.setComboValues()}))}},this.findGetters=function(e,t){const s=t?this.properties.previousName:this.widgets[0].value;return e._nodes.filter((e=>"easy getNode"==e.type&&e.widgets[0].value===s&&""!=s))},this.isVirtualNode=!0}onRemoved(){this.graph._nodes.filter((e=>"easy getNode"==e.type)).forEach((e=>{e.setComboValues&&e.setComboValues([this])}))}}LiteGraph.registerNodeType("easy setNode",Object.assign(e,{title:"Set"})),e.category="EasyUse/Util"}}),v.registerExtension({name:"easy getNode",registerCustomNodes(){class e extends bt{constructor(t){super("Get"),g(this,"defaultVisibility",!0),g(this,"serialize_widgets",!0),this.properties||(this.properties={}),this.properties.showOutputText=e.defaultVisibility;const s=this;s.color=LGraphCanvas.node_colors.blue.color,this.addWidget("combo","Constant","",(e=>{this.onRename()}),{values:()=>s.graph._nodes.filter((e=>"easy setNode"==e.type)).map((e=>e.widgets[0].value)).sort()}),this.addOutput("*","*"),this.onConnectionsChange=function(e,t,s,n,i){this.validateLinks(),2!=e||s?(this.onRename(),setTimeout((e=>{this.title="⬅️"+this.widgets[0].value}),1)):(this.outputs[t].type="*",this.outputs[t].name="*",this.title="Get")},this.setName=function(e){s.widgets[0].value=e,s.onRename(),s.serialize()},this.onRename=function(e=0){const t=this.findSetter(s.graph);if(t){const s=t.inputs[0].type,n=t.inputs[0].name;this.setType(s,n),this.outputs[e].type=s,this.outputs[e].name=n,this.title="⬅️"+t.widgets[0].value}else this.setType("*","*"),this.outputs[e].type="*",this.outputs[e].name="*"},this.clone=function(){const t=e.prototype.clone.apply(this);return t.size=t.computeSize(),t},this.validateLinks=function(){"*"!=this.outputs[0].type&&this.outputs[0].links&&this.outputs[0].links.forEach((e=>{const t=s.graph.links[e];t&&t.type!=this.outputs[0].type&&"*"!=t.type&&s.graph.removeLink(e)}))},this.setType=function(e,t){this.outputs[0].name=t,this.outputs[0].type=e,this.validateLinks()},this.findSetter=function(e){const t=this.widgets[0].value;return e._nodes.find((e=>"easy setNode"==e.type&&e.widgets[0].value===t&&""!=t))},this.isVirtualNode=!0}getInputLink(e){const t=this.findSetter(this.graph);if(t){const s=t.inputs[e];return this.graph.links[s.link]}throw new Error("No setter found for "+this.widgets[0].value+"("+this.type+")")}onAdded(e){}}LiteGraph.registerNodeType("easy getNode",Object.assign(e,{title:"Get"})),e.category="EasyUse/Util"}}),b.addEventListener("easyuse-global-seed",(function(e){let t=app.graph._nodes_by_id;for(let s in t){let n=t[s];if("easy globalSeed"==n.type){if(n.widgets){const t=n.widgets.find((e=>"value"==e.name));n.widgets.find((e=>"last_seed"==e.name)).value=t.value,t.value=e.detail.value}}else if(n.widgets){const t=n.widgets.find((e=>"seed_num"==e.name||"seed"==e.name||"noise_seed"==e.name));t&&null!=e.detail.seed_map[n.id]&&(t.value=e.detail.seed_map[n.id])}}}));const Lt=b.queuePrompt;b.queuePrompt=async function(e,{output:t,workflow:s}){s.seed_widgets={};for(let n in app.graph._nodes_by_id){let e=app.graph._nodes_by_id[n].widgets;if(e)for(let t in e)"seed_num"!=e[t].name&&"seed"!=e[t].name&&"noise_seed"!=e[t].name||"converted-widget"==e[t].type||(s.seed_widgets[n]=parseInt(t))}return await Lt.call(b,e,{output:t,workflow:s})};const St=["easy imageSave","easy fullkSampler","easy kSampler","easy kSamplerTiled","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerSDTurbo","easy detailerFix"];v.registerExtension({name:"Comfy.EasyUse.SaveImageExtraOutput",async beforeRegisterNodeDef(e,t,s){if(St.includes(t.name)){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?t.apply(this,arguments):void 0,n=this.widgets.find((e=>"filename_prefix"===e.name||"save_prefix"===e.name));return n.serializeValue=()=>E(s,n.value),e}}else{const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?t.apply(this,arguments):void 0;return this.properties&&"Node name for S&R"in this.properties||this.addProperty("Node name for S&R",this.constructor.type,"string"),e}}}});const Et=["easy wildcards","easy positive","easy negative","easy stylesSelector","easy promptConcat","easy promptReplace"],Ct=["easy preSampling","easy preSamplingAdvanced","easy preSamplingNoiseIn","easy preSamplingCustom","easy preSamplingDynamicCFG","easy preSamplingSdTurbo","easy preSamplingLayerDiffusion"],kt=["easy kSampler","easy kSamplerTiled","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerSDTurbo"],At=["easy controlnetLoader","easy controlnetLoaderADV"],xt=["easy instantIDApply","easy instantIDApplyADV"],It=["easy ipadapterApply","easy ipadapterApplyADV","easy ipadapterApplyFaceIDKolors","easy ipadapterStyleComposition"],Nt=["easy pipeIn","easy pipeOut","easy pipeEdit","easy pipeEditPrompt"],Tt=["easy XYPlot","easy XYPlotAdvanced"],Ot=["easy setNode"],Dt=["Reroute","RescaleCFG","LoraLoaderModelOnly","LoraLoader","FreeU","FreeU_v2",...It,...Ot],Rt={"easy seed":{from:{INT:["Reroute",...Ct,"easy fullkSampler"]}},"easy positive":{from:{STRING:["Reroute",...Et]}},"easy negative":{from:{STRING:["Reroute",...Et]}},"easy wildcards":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy stylesSelector":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy promptConcat":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy promptReplace":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy fullLoader":{from:{PIPE_LINE:["Reroute",...Ct,"easy fullkSampler",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy a1111Loader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy comfyLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy hunyuanDiTLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy kolorsLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy pixArtLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy fluxLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy svdLoader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy zero123Loader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy sv3dLoader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy preSampling":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingAdvanced":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingDynamicCFG":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingCustom":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingLayerDiffusion":{from:{PIPE_LINE:["Reroute","easy kSamplerLayerDiffusion",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingNoiseIn":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy fullkSampler":{from:{PIPE_LINE:["Reroute",...Nt.reverse(),"easy preDetailerFix","easy preMaskDetailerFix",...Ct,...Ot]}},"easy kSampler":{from:{PIPE_LINE:["Reroute",...Nt.reverse(),"easy preDetailerFix","easy preMaskDetailerFix","easy hiresFix",...Ct,...Ot]}},"easy controlnetLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}},"easy controlnetLoaderADV":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}},"easy instantIDApply":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{COMBO:["Reroute","easy promptLine"]}},"easy instantIDApplyADV":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{COMBO:["Reroute","easy promptLine"]}},"easy ipadapterApply":{to:{COMBO:["Reroute","easy promptLine"]}},"easy ipadapterApplyADV":{to:{STRING:["Reroute","easy sliderControl",...Et],COMBO:["Reroute","easy promptLine"]}},"easy ipadapterStyleComposition":{to:{COMBO:["Reroute","easy promptLine"]}},"easy preDetailerFix":{from:{PIPE_LINE:["Reroute","easy detailerFix",...Nt,...Ot]},to:{PIPE_LINE:["Reroute","easy ultralyticsDetectorPipe","easy samLoaderPipe","easy kSampler","easy fullkSampler"]}},"easy preMaskDetailerFix":{from:{PIPE_LINE:["Reroute","easy detailerFix",...Nt,...Ot]}},"easy samLoaderPipe":{from:{PIPE_LINE:["Reroute","easy preDetailerFix",...Nt,...Ot]}},"easy ultralyticsDetectorPipe":{from:{PIPE_LINE:["Reroute","easy preDetailerFix",...Nt,...Ot]}},"easy cascadeLoader":{from:{PIPE_LINE:["Reroute","easy fullCascadeKSampler","easy preSamplingCascade",...At,...Nt,...Ot],MODEL:Dt.filter((e=>!It.includes(e)))}},"easy fullCascadeKSampler":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced",...Nt,...Ot]}},"easy preSamplingCascade":{from:{PIPE_LINE:["Reroute","easy cascadeKSampler",...Nt,...Ot]}},"easy cascadeKSampler":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced",...Nt,...Ot]}},"easy pipeEdit":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Ot]},to:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}},"easy pipeEditPrompt":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Ot]},to:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}}};v.registerExtension({name:"Comfy.EasyUse.Suggestions",async setup(e){LGraphCanvas.prototype.createDefaultNodeForSlot=function(e){e=e||{};var t,s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},e),n=s.nodeFrom&&null!==s.slotFrom,i=!n&&s.nodeTo&&null!==s.slotTo;if(!n&&!i)return!1;if(!s.nodeType)return!1;var o=n?s.nodeFrom:s.nodeTo,a=n?s.slotFrom:s.slotTo,l=o.type,r=!1;switch(typeof a){case"string":r=n?o.findOutputSlot(a,!1):o.findInputSlot(a,!1),a=n?o.outputs[a]:o.inputs[a];break;case"object":r=n?o.findOutputSlot(a.name):o.findInputSlot(a.name);break;case"number":r=a,a=n?o.outputs[a]:o.inputs[a];break;default:return!1}var d=a.type==LiteGraph.EVENT?"_event_":a.type,u=n?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(u&&u[d]){a.link;let e=!1;const i=n?"from":"to";if(Rt[l]&&Rt[l][i]&&(null==(t=Rt[l][i][d])?void 0:t.length)>0){for(var c in Rt[l][i][d])if(s.nodeType==Rt[l][i][d][c]||"AUTO"==s.nodeType){e=Rt[l][i][d][c];break}}else if("object"==typeof u[d]||"array"==typeof u[d]){for(var c in u[d])if(s.nodeType==u[d][c]||"AUTO"==s.nodeType){e=u[d][c];break}}else s.nodeType!=u[d]&&"AUTO"!=s.nodeType||(e=u[d]);if(e){var p=!1;"object"==typeof e&&e.node&&(p=e,e=e.node);var h=LiteGraph.createNode(e);if(h){if(p){if(p.properties)for(var m in p.properties)h.addProperty(m,p.properties[m]);if(p.inputs)for(var m in h.inputs=[],p.inputs)h.addOutput(p.inputs[m][0],p.inputs[m][1]);if(p.outputs)for(var m in h.outputs=[],p.outputs)h.addOutput(p.outputs[m][0],p.outputs[m][1]);p.title&&(h.title=p.title),p.json&&h.configure(p.json)}return this.graph.add(h),h.pos=[s.position[0]+s.posAdd[0]+(s.posSizeFix[0]?s.posSizeFix[0]*h.size[0]:0),s.position[1]+s.posAdd[1]+(s.posSizeFix[1]?s.posSizeFix[1]*h.size[1]:0)],n?s.nodeFrom.connectByType(r,h,d):s.nodeTo.connectByTypeOutput(r,h,d),!0}}}return!1},LGraphCanvas.prototype.showConnectionMenu=function(e){e=e||{};var t,s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null,allow_searchbox:this.allow_searchbox,showSearchBox:this.showSearchBox},e),n=this,i=s.nodeFrom&&s.slotFrom,o=!i&&s.nodeTo&&s.slotTo;if(!i&&!o)return!1;var a=i?s.nodeFrom:s.nodeTo,l=i?s.slotFrom:s.slotTo,r=!1;switch(typeof l){case"string":r=i?a.findOutputSlot(l,!1):a.findInputSlot(l,!1),l=i?a.outputs[l]:a.inputs[l];break;case"object":r=i?a.findOutputSlot(l.name):a.findInputSlot(l.name);break;case"number":r=l,l=i?a.outputs[l]:a.inputs[l];break;default:return!1}var d=["Add Node",null];s.allow_searchbox&&(d.push("Search"),d.push(null));var u=l.type==LiteGraph.EVENT?"_event_":l.type,c=i?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in,p=a.type;if(c&&c[u]){const e=i?"from":"to";if(Rt[p]&&Rt[p][e]&&(null==(t=Rt[p][e][u])?void 0:t.length)>0)for(var h in Rt[p][e][u])d.push(Rt[p][e][u][h]);else if("object"==typeof c[u]||"array"==typeof c[u])for(var h in c[u])d.push(c[u][h]);else d.push(c[u])}var m=new LiteGraph.ContextMenu(d,{event:s.e,title:(l&&""!=l.name?l.name+(u?" | ":""):"")+(l&&u?u:""),callback:function(e,t,o){switch(e){case"Add Node":LGraphCanvas.onMenuAdd(null,null,o,m,(function(e){i?s.nodeFrom.connectByType(r,e,u):s.nodeTo.connectByTypeOutput(r,e,u)}));break;case"Search":i?s.showSearchBox(o,{node_from:s.nodeFrom,slot_from:l,type_filter_in:u}):s.showSearchBox(o,{node_to:s.nodeTo,slot_from:l,type_filter_out:u});break;default:n.createDefaultNodeForSlot(Object.assign(s,{position:[s.e.canvasX,s.e.canvasY],nodeType:e}))}}});return!1}}}),v.registerExtension({name:"Comfy.EasyUse.TimeTaken",setup(){let e=new Map,t=0;b.addEventListener("execution_start",(e=>{graph&&graph._nodes.forEach((e=>{e.executionDuration&&delete e.executionDuration}))})),b.addEventListener("executing",(s=>{if(!A("EasyUse.Nodes.Runtime",null,!0))return;const n=(null==s?void 0:s.node)||(null==s?void 0:s.detail)||null,i=e.get(t);if(e.delete(t),t&&i){const e=Date.now()-i,s=ve(t);s&&(s.executionDuration||(s.executionDuration=0),s.executionDuration=s.executionDuration+e/1e3)}t=n,e.set(n,Date.now())}))},beforeRegisterNodeDef(e,t){const s=e.prototype.onDrawForeground;e.prototype.onDrawForeground=function(...e){const[t]=e;return function(e,t){if(!t)return;t=parseFloat(t).toFixed(3)+Y("s"),e.save(),e.fillStyle=LiteGraph.NODE_DEFAULT_BGCOLOR,function(e,t,s,n,i,o){e.beginPath(),e.moveTo(t+o,s),e.lineTo(t+n-o,s),e.arcTo(t+n,s,t+n,s+o,o),e.lineTo(t+n,s+i-o),e.arcTo(t+n,s+i,t+n-o,s+i,o),e.lineTo(t+o,s+i),e.arcTo(t,s+i,t,s+i-o,o),e.lineTo(t,s+o),e.arcTo(t,s,t+o,s,o),e.closePath()}(e,0,-LiteGraph.NODE_TITLE_HEIGHT-20,e.measureText(t).width+10,LiteGraph.NODE_TITLE_HEIGHT-10,4),e.fill(),function(e,t,s,n,i="#000",o=12,a="Inter"){e.font=`${o}px ${a}`,e.fillStyle=i,e.fillText(t,s,n)}(e,t,8,-LiteGraph.NODE_TITLE_HEIGHT-6,LiteGraph.NODE_TITLE_COLOR),e.restore()}(t,this.executionDuration),null==s?void 0:s.apply(this,e)}}});let Gt=null;v.registerExtension({name:"Comfy.EasyUse.HotKeys",setup(){if(void 0!==y){y("up,down,left,right",(function(e,t){var s,n,i,o,a,l,r,d,u,c,p,h,m,g,f;e.preventDefault();if(!A("EasyUse.Hotkeys.JumpNearestNodes",null,!0))return;const y=be();if(0===y.length)return;const _=y[0];switch(t.key){case"up":case"left":let e=null;if(ke(_)){const e=null==(s=_.widgets_values)?void 0:s[0],t=null==(n=_.graph)?void 0:n._nodes,i=null==t?void 0:t.find((t=>{var s;if(Ae(t)){if((null==(s=t.widgets_values)?void 0:s[0])===e)return t}return null}));i&&Le(i)}else if((null==(i=_.inputs)?void 0:i.length)>0){for(let t=0;t<_.inputs.length;t++)if(_.inputs[t].link){e=_.inputs[t].link;break}if(e){const t=null==(o=_.graph)?void 0:o.links;if(t[e]){const s=null==(a=t[e])?void 0:a.origin_id,n=null==(r=null==(l=_.graph)?void 0:l._nodes_by_id)?void 0:r[s];n&&Le(n)}}}break;case"down":case"right":let t=null;if(Ae(_)){const e=null==(d=_.widgets_values)?void 0:d[0],t=null==(u=_.graph)?void 0:u._nodes,s=null==t?void 0:t.find((t=>{var s;if(ke(t)){if((null==(s=t.widgets_values)?void 0:s[0])===e)return t}return null}));s&&Le(s)}else if((null==(c=_.outputs)?void 0:c.length)>0){for(let e=0;e<_.outputs.length;e++)if((null==(p=_.outputs[e].links)?void 0:p.length)>0&&_.outputs[e].links[0]){t=_.outputs[e].links[0];break}if(t){const e=null==(h=_.graph)?void 0:h.links;if(e[t]){const s=null==(m=e[t])?void 0:m.target_id,n=null==(f=null==(g=_.graph)?void 0:g._nodes_by_id)?void 0:f[s];n&&Le(n)}}}}})),y("shift+up,shift+down,shift+left,shift+right,shift+alt+⌘+left,shift+alt+⌘+right,shift+alt+ctrl+left,shift+alt+ctrl+right",(function(e,t){e.preventDefault();if(!A("EasyUse.Hotkeys.AlignSelectedNodes",null,!0))return;const s=be();if(s.length<=1)return;const n=s;switch(t.key){case"shift+up":LGraphCanvas.alignNodes(n,"top",n[0]);break;case"shift+down":LGraphCanvas.alignNodes(n,"bottom",n[0]);break;case"shift+left":LGraphCanvas.alignNodes(n,"left",n[0]);break;case"shift+right":LGraphCanvas.alignNodes(n,"right",n[0]);break;case"shift+alt+ctrl+left":case"shift+alt+⌘+left":Fe(n,"horizontal");break;case"shift+alt+ctrl+right":case"shift+alt+⌘+right":Fe(n,"vertical")}Gt||(Gt=$()),Gt&&Gt.update()})),y("shift+⌘+left,shift+⌘+right,shift+ctrl+left,shift+ctrl+right",(function(e,t){e.preventDefault();if(!A("EasyUse.Hotkeys.NormalizeSelectedNodes",null,!0))return;const s=be();if(s.length<=1)return;const n=s;switch(t.key){case"shift+ctrl+left":case"shift+⌘+left":Pe(n,"width");break;case"shift+ctrl+right":case"shift+⌘+right":Pe(n,"height")}Gt||(Gt=$()),Gt&&Gt.update()})),y("shift+g",(function(e,t){e.preventDefault();A("EasyUse.Hotkeys.AddGroup",null,!0)&&(Pt(),Gt||(Gt=$()),Gt&&Gt.update())})),y("shift+r",(function(e,t){e.preventDefault();A("EasyUse.Hotkeys.cleanVRAMused",null,!0)&&Qe()})),y("shift+m",(function(e,t){var s,n,i;if(!A("EasyUse.Hotkeys.toggleNodesMap",null,!0))return;let o=(null==(s=v.extensionManager)?void 0:s.sidebarTab)||v.extensionManager,a=(null==(n=v.extensionManager.sidebarTab)?void 0:n.activeSidebarTabId)||(null==(i=v.extensionManager)?void 0:i.activeSidebarTab);o.activeSidebarTabId=a==P?null:P}));const e=[];Array.from(Array(10).keys()).forEach((t=>e.push(`alt+${t}`))),y(e.join(","),(async function(e,t){e.preventDefault();if(!A("EasyUse.Hotkeys.NodesTemplate",null,!0))return;const s=t.key;let n=parseInt(s.split("+")[1]);const i=await b.getUserData("comfy.templates.json");let o=null;if(200==i.status)try{o=await i.json()}catch(l){Ue.error(Y("Get Node Templates File Failed"))}else localStorage["Comfy.NodeTemplates"]?o=JSON.parse(localStorage["Comfy.NodeTemplates"]):Ue.warn(Y("No Node Templates Found"));if(!o)return void Ue.warn(Y("No Node Templates Found"));n=0===n?9:n-1;const a=o[n];if(a)try{const e=(null==a?void 0:a.name)||"Group",t=(null==a?void 0:a.data)?JSON.parse(a.data):[];Mt((async()=>{await C.registerFromWorkflow(t.groupNodes,{}),localStorage.litegrapheditor_clipboard=a.data,v.canvas.pasteFromClipboard(),t.groupNodes||Pt(e)}))}catch(l){Ue.error(l)}else Ue.warn(Y("Node template with {key} not set").replace("{key}",s))}));const t=async function(e){if(("b"===e.key||"m"==e.key)&&(e.metaKey||e.ctrlKey)){if(0===be().length)return;Gt||(Gt=$()),Gt&&Gt.update()}};window.addEventListener("keydown",t,!0)}}});const Mt=async e=>{const t=localStorage.litegrapheditor_clipboard;await e(),localStorage.litegrapheditor_clipboard=t},Pt=e=>{const t=be();if(0===t.length)return;const s=t;let n=new LiteGraph.LGraphGroup;n.title=e||"Group",((e,t=[],s=20)=>{var n,i,o,a,l,r,d,u,c,p;for(var h of(i=o=a=l=-1,r=d=u=c=-1,[e._nodes,t]))for(var m in h)r=(p=h[m]).pos[0],d=p.pos[1],u=p.pos[0]+p.size[0],c=p.pos[1]+p.size[1],"Reroute"!=p.type&&(d-=LiteGraph.NODE_TITLE_HEIGHT),(null==(n=p.flags)?void 0:n.collapsed)&&(c=d+LiteGraph.NODE_TITLE_HEIGHT,(null==p?void 0:p._collapsed_width)&&(u=r+Math.round(p._collapsed_width))),(-1==i||ra)&&(a=u),(-1==l||c>l)&&(l=c);o-=Math.round(1.4*e.font_size),e.pos=[i-s,o-s],e.size=[a-i+2*s,l-o+2*s]})(n,s),v.canvas.graph.add(n)};function Ft(e,t,s,n){const i=[];return e.workflow.links.forEach((e=>{s&&e[1]===t&&!i.includes(e[3])&&i.push(e[3]),n&&e[3]===t&&!i.includes(e[1])&&i.push(e[1])})),i}async function Ut(e,t=!1){const s=structuredClone(await v.graphToPrompt()),n=[];if(s.workflow.nodes.forEach((e=>{n.push(e.id)})),s.workflow.links=s.workflow.links.filter((e=>n.includes(e[1])&&n.includes(e[3]))),t)for(;!v.graph._nodes_by_id[e].isChooser;)e=Ft(s,e,!0,!1)[0];const i=function(e,t){const s=[],n=[t];for(;n.length>0;){const t=n.pop();s.push(t),n.push(...Ft(e,t,!0,!1).filter((e=>!(s.includes(e)||n.includes(e)))))}n.push(...s.filter((e=>e!=t)));const i=[t];for(;n.length>0;){const t=n.pop();i.push(t),n.push(...Ft(e,t,!1,!0).filter((e=>!(i.includes(e)||n.includes(e)))))}const o=[];return o.push(...s),o.push(...i.filter((e=>!o.includes(e)))),o}(s,e);s.workflow.nodes=s.workflow.nodes.filter((t=>(t.id===e&&t.inputs.forEach((e=>{e.link=null})),i.includes(t.id)))),s.workflow.links=s.workflow.links.filter((e=>i.includes(e[1])&&i.includes(e[3])));const o={};for(const[r,d]of Object.entries(s.output))i.includes(parseInt(r))&&(o[r]=d);const a={};for(const[r,d]of Object.entries(o[e.toString()].inputs))Array.isArray(d)||(a[r]=d);o[e.toString()].inputs=a,s.output=o;const l=v.graphToPrompt;v.graphToPrompt=()=>(v.graphToPrompt=l,s),v.queuePrompt(0)}const Bt=new class{constructor(){this.current_node_id=void 0,this.class_of_current_node=null,this.current_node_is_chooser=!1}update(){var e,t;return v.runningNodeId!=this.current_node_id&&(this.current_node_id=v.runningNodeId,this.current_node_id?(this.class_of_current_node=null==(t=null==(e=v.graph)?void 0:e._nodes_by_id[v.runningNodeId.toString()])?void 0:t.comfyClass,this.current_node_is_chooser="easy imageChooser"===this.class_of_current_node):(this.class_of_current_node=void 0,this.current_node_is_chooser=!1),!0)}},zt=class e{constructor(){}static idle(){return!v.runningNodeId}static paused(){return!0}static paused_here(t){return e.here(t)}static running(){return!e.idle()}static here(e){return v.runningNodeId==e}static state(){return"Paused"}};g(zt,"cancelling",!1);let Wt=zt;function jt(e,t){const s=new FormData;s.append("message",t),s.append("id",e),b.fetchApi("/easyuse/image_chooser_message",{method:"POST",body:s})}function Vt(){jt(-1,"__cancel__"),Wt.cancelling=!0,b.interrupt(),Wt.cancelling=!1}var Yt=0;function Ht(){Yt+=1}const Xt=["easy kSampler","easy kSamplerTiled","easy fullkSampler"];function Zt(e){const t=v.graph._nodes_by_id[e.detail.id];if(t){t.selected_images=new Set,t.anti_selected=new Set;const s=function(e,t){var s;return e.imgs=[],t.forEach((t=>{const s=new Image;e.imgs.push(s),s.onload=()=>{v.graph.setDirtyCanvas(!0)},s.src=`/view?filename=${encodeURIComponent(t.filename)}&type=temp&subfolder=${v.getPreviewFormatParam()}`})),null==(s=e.setSizeForImage)||s.call(e),e.imgs}(t,e.detail.urls);return{node:t,image:s,isKSampler:Xt.includes(t.type)}}}function Kt(e,t,s){var n;if(e.imageRects)n=e.imageRects[t];else{const t=e.imagey;n=[1,t+1,e.size[0]-2,e.size[1]-t-2]}s.strokeRect(n[0]+1,n[1]+1,n[2]-2,n[3]-2)}class Jt extends L{constructor(){super(),this.node=null,this.select_index=[],this.dialog_div=null}show(e,t){this.select_index=[],this.node=t;const s=e.map(((e,s)=>{const n=w("img",{src:e.src,onclick:e=>{this.select_index.includes(s)?(this.select_index=this.select_index.filter((e=>e!==s)),n.classList.remove("selected")):(this.select_index.push(s),n.classList.add("selected")),t.selected_images.has(s)?t.selected_images.delete(s):t.selected_images.add(s)}});return n}));super.show(w("div.comfyui-easyuse-chooser-dialog",[w("h5.comfyui-easyuse-chooser-dialog-title",Y("Choose images to continue")),w("div.comfyui-easyuse-chooser-dialog-images",s)]))}createButtons(){const e=super.createButtons();return e[0].onclick=e=>{Wt.running()&&Vt(),super.close()},e.unshift(w("button",{type:"button",textContent:Y("Choose Selected Images"),onclick:e=>{jt(this.node.id,[...this.node.selected_images,-1,...this.node.anti_selected]),Wt.idle()&&(Ht(),Ut(this.node.id).then((()=>{jt(this.node.id,[...this.node.selected_images,-1,...this.node.anti_selected])}))),super.close()}})),e}}function $t(){const e=v.graph._nodes_by_id[this.node_id];if(e){const t=[...e.selected_images];(null==t?void 0:t.length)>0&&e.setProperty("values",t),jt(e.id,[...e.selected_images,-1,...e.anti_selected]),Wt.idle()&&(Ht(),Ut(e.id).then((()=>{jt(e.id,[...e.selected_images,-1,...e.anti_selected])})))}}function qt(){Wt.running()&&Vt()}function Qt(e){Object.defineProperty(e,"clicked",{get:function(){return this._clicked},set:function(e){this._clicked=e&&""!=this.name}})}function es(e){e.options||(e.options={}),e.options.serialize=!1}v.registerExtension({name:"Comfy.EasyUse.imageChooser",init(){window.addEventListener("beforeunload",Vt,!0)},setup(e){const t=LGraphCanvas.prototype.draw;LGraphCanvas.prototype.draw=function(){Bt.update()&&e.graph._nodes.forEach((e=>{e.update&&e.update()})),t.apply(this,arguments)},b.addEventListener("easyuse-image-choose",(function(e){const{node:t,image:s,isKSampler:n}=Zt(e);if(n){(new Jt).show(s,t)}}));const s=b.interrupt;b.interrupt=function(){Wt.cancelling||Vt(),s.apply(this,arguments)},b.addEventListener("execution_start",(function(){(Yt>0?(Yt-=1,0):(jt(-1,"__start__"),1))&&e.graph._nodes.forEach((e=>{(e.selected_images||e.anti_selected)&&(e.selected_images.clear(),e.anti_selected.clear(),e.update())}))}))},async nodeCreated(e,t){if("easy imageChooser"==e.comfyClass){e.setProperty("values",[]),void 0===(null==e?void 0:e.imageIndex)&&Object.defineProperty(e,"imageIndex",{get:function(){return null},set:function(t){e.overIndex=t}}),void 0===(null==e?void 0:e.imagey)&&Object.defineProperty(e,"imagey",{get:function(){return null},set:function(t){return e.widgets[e.widgets.length-1].last_y+LiteGraph.NODE_WIDGET_HEIGHT}});const t=e.onMouseDown;e.onMouseDown=function(s,n,i){if(s.isPrimary){const t=function(e,t){var s,n;if((null==(s=e.imgs)?void 0:s.length)>1)for(var i=0;i0&&s0&&ne.imagey)return 0;return-1}(e,n);t>=0&&this.imageClicked(t)}return t&&t.apply(this,arguments)},e.send_button_widget=e.addWidget("button","","",$t),e.cancel_button_widget=e.addWidget("button","","",qt),Qt(e.cancel_button_widget),Qt(e.send_button_widget),es(e.cancel_button_widget),es(e.send_button_widget)}},beforeRegisterNodeDef(e,t,s){if("easy imageChooser"==(null==t?void 0:t.name)){const t=e.prototype.onDrawBackground;e.prototype.onDrawBackground=function(e){t.apply(this,arguments),function(e,t){var s;if(e.imgs){if(e.imageRects)for(let s=0;s{Kt(e,s,t)})),t.strokeStyle="#F88",null==(s=null==e?void 0:e.anti_selected)||s.forEach((s=>{Kt(e,s,t)}))}}(this,e)},e.prototype.imageClicked=function(t){"easy imageChooser"===(null==e?void 0:e.comfyClass)&&(this.selected_images.has(t)?this.selected_images.delete(t):this.selected_images.add(t),this.update())};const s=e.prototype.update;e.prototype.update=function(){var e;if(s&&s.apply(this,arguments),this.send_button_widget){this.send_button_widget.node_id=this.id;const t=(this.selected_images?this.selected_images.size:0)+(this.anti_selected?this.anti_selected.size:0),s=(null==(e=this.imgs)?void 0:e.length)||0;Wt.paused_here(this.id)&&t>0?this.send_button_widget.name=t>1?"Progress selected ("+t+"/"+s+")":"Progress selected image":this.send_button_widget.name=t>0?t>1?"Progress selected ("+t+"/"+s+")":"Progress selected image as restart":""}if(this.cancel_button_widget){const e=Wt.running();this.cancel_button_widget.name=e?"Cancel current run":""}this.setDirtyCanvas(!0,!0)}}}}),Number.prototype.div=function(e){return function(e,t){let s,n,i=0,o=0,a="string"==typeof e?e:e.toString(),l="string"==typeof t?t:t.toString();try{i=a.toString().split(".")[1].length}catch(r){}try{o=l.toString().split(".")[1].length}catch(r){}return s=Number(a.toString().replace(".","")),n=Number(l.toString().replace(".","")),s/n*Math.pow(10,o-i)}(this,e)};let ts=[],ss=0;const ns={sd3:6.5,"sd3-turbo":4};class is extends L{constructor(){super(),this.lists=[],this.dialog_div=null,this.user_div=null}addItem(e,t){return w("div.easyuse-account-dialog-item",[w("input",{type:"text",placeholder:"Enter name",oninput:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts[t].name=e.target.value},value:ts[e].name}),w("input.key",{type:"text",oninput:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts[t].key=e.target.value},placeholder:"Enter APIKEY",value:ts[e].key}),w("button.choose",{textContent:Y("Choose"),onclick:async e=>{var s,n,i;const o=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);let a=ts[o].name,l=ts[o].key;if(!a)return void Ue.error(Y("Please enter the account name"));if(!l)return void Ue.error(Y("Please enter the APIKEY"));let r=!0;for(let t=0;t{(new is).show(t)}},[w("div.user",[w("div.avatar",o?[w("img",{src:o})]:"😀"),w("div.info",[w("h5.name",a),w("h6.remark","Credits: "+l)])]),w("div.edit",{textContent:Y("Edit")})])),Ue.success(Y("Save Succeed"))}else Ue.success(Y("Save Succeed"));this.close()}else Ue.error(Y("Save Failed"))}}),w("button.delete",{textContent:Y("Delete"),onclick:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts.length<=1?Ue.error(Y("At least one account is required")):(ts.splice(t,1),this.dialog_div.removeChild(e.target.parentNode))}})])}show(e){ts.forEach(((t,s)=>{this.lists.push(this.addItem(s,e))})),this.dialog_div=w("div.easyuse-account-dialog",this.lists),super.show(w("div.easyuse-account-dialog-main",[w("div",[w("a",{href:"https://platform.stability.ai/account/keys",target:"_blank",textContent:Y("Getting Your APIKEY")})]),this.dialog_div]))}createButtons(){const e=super.createButtons();return e.unshift(w("button",{type:"button",textContent:Y("Save Account Info"),onclick:e=>{let t=!0;for(let s=0;s{200==e.status?Ue.success(Y("Save Succeed")):Ue.error(Y("Save Failed"))}))}else Ue.error(Y("APIKEY is not Empty"))}})),e.unshift(w("button",{type:"button",textContent:Y("Add Account"),onclick:e=>{const t="Account "+ts.length.toString();ts.push({name:t,key:""});const s=this.addItem(ts.length-1);this.lists.push(s),this.dialog_div.appendChild(s)}})),e}}v.registerExtension({name:"Comfy.EasyUse.API.SD3",async beforeRegisterNodeDef(e,t,s){if("easy stableDiffusion3API"==t.name){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=async function(){var e,s,n;t&&(null==t||t.apply(this,arguments));const i=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),o=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));let a=this.widgets.find((e=>"model"==e.name));a.callback=e=>{l.value="-"+ns[e]};const l=this.addWidget("text","cost_credit","0",(e=>{}),{serialize:!1});l.disabled=!0,setTimeout((e=>{"control_before_generate"==o.name&&0===i.value&&(i.value=Math.floor(4294967294*Math.random())),l.value="-"+ns[a.value]}),100);let r=w("div.easyuse-account-user",[Y("Loading UserInfo...")]);this.addDOMWidget("account","btn",w("div.easyuse-account",r)),b.addEventListener("stable-diffusion-api-generate-succeed",(async({detail:e})=>{var t;let s=r.querySelectorAll(".remark");if(s&&s[0]){const t=(null==e?void 0:e.model)?ns[e.model]:0;if(t){let e=function(e,t){let s,n,i,o,a,l;a="string"==typeof e?e:e.toString(),l="string"==typeof t?t:t.toString();try{s=a.split(".")[1].length}catch(r){s=0}try{n=l.split(".")[1].length}catch(r){n=0}return i=Math.pow(10,Math.max(s,n)),o=s>=n?s:n,((e*i-t*i)/i).toFixed(o)}(parseFloat(s[0].innerText.replace(/Credits: /g,"")),t);e>0&&(s[0].innerText="Credits: "+e.toString())}}await X(1e4);const n=await b.fetchApi("/easyuse/stability/balance");if(200==n.status){const e=await n.json();if(null==e?void 0:e.balance){const n=(null==(t=e.balance)?void 0:t.credits)||0;s&&s[0]&&(s[0].innerText="Credits: "+n)}}}));const d=await b.fetchApi("/easyuse/stability/api_keys");if(200==d.status){let t=await d.json();if(ts=t.keys,ss=t.current,ts.length>0&&void 0!==ss){const t=ts[ss].key,i=ts[ss].name;if(t){const t=await b.fetchApi("/easyuse/stability/user_info");if(200==t.status){const i=await t.json();if((null==i?void 0:i.account)&&(null==i?void 0:i.balance)){const t=(null==(e=i.account)?void 0:e.profile_picture)||null,o=(null==(s=i.account)?void 0:s.email)||null,a=(null==(n=i.balance)?void 0:n.credits)||0;r.replaceChildren(w("div.easyuse-account-user-info",{onclick:e=>{(new is).show(r)}},[w("div.user",[w("div.avatar",t?[w("img",{src:t})]:"😀"),w("div.info",[w("h5.name",o),w("h6.remark","Credits: "+a)])]),w("div.edit",{textContent:Y("Edit")})]))}}}else r.replaceChildren(w("div.easyuse-account-user-info",{onclick:e=>{(new is).show(r)}},[w("div.user",[w("div.avatar","😀"),w("div.info",[w("h5.name",i),w("h6.remark",Y("Click to set the APIKEY first"))])]),w("div.edit",{textContent:Y("Edit")})]))}}}}}});let os=null;function as(){os&&(os.removeEventListeners(),os.dropdown.remove(),os=null)}function ls(e,t,s,n=!1){as(),new rs(e,t,s,n)}class rs{constructor(e,t,s,n=!1){this.dropdown=document.createElement("ul"),this.dropdown.setAttribute("role","listbox"),this.dropdown.classList.add("easy-dropdown"),this.selectedIndex=-1,this.inputEl=e,this.suggestions=t,this.onSelect=s,this.isDict=n,this.focusedDropdown=this.dropdown,this.buildDropdown(),this.onKeyDownBound=this.onKeyDown.bind(this),this.onWheelBound=this.onWheel.bind(this),this.onClickBound=this.onClick.bind(this),this.addEventListeners()}buildDropdown(){this.isDict?this.buildNestedDropdown(this.suggestions,this.dropdown):this.suggestions.forEach(((e,t)=>{this.addListItem(e,t,this.dropdown)}));const e=this.inputEl.getBoundingClientRect();this.dropdown.style.top=e.top+e.height-10+"px",this.dropdown.style.left=e.left+"px",document.body.appendChild(this.dropdown),os=this}buildNestedDropdown(e,t){let s=0;Object.keys(e).forEach((n=>{const i=e[n];if("object"==typeof i&&null!==i){const e=document.createElement("ul");e.setAttribute("role","listbox"),e.classList.add("easy-nested-dropdown");const o=document.createElement("li");o.classList.add("folder"),o.textContent=n,o.appendChild(e),o.addEventListener("mouseover",this.onMouseOver.bind(this,s,t)),t.appendChild(o),this.buildNestedDropdown(i,e),s+=1}else{const e=document.createElement("li");e.classList.add("item"),e.setAttribute("role","option"),e.textContent=n,e.addEventListener("mouseover",this.onMouseOver.bind(this,s,t)),e.addEventListener("mousedown",this.onMouseDown.bind(this,n)),t.appendChild(e),s+=1}}))}addListItem(e,t,s){const n=document.createElement("li");n.setAttribute("role","option"),n.textContent=e,n.addEventListener("mouseover",this.onMouseOver.bind(this,t)),n.addEventListener("mousedown",this.onMouseDown.bind(this,e)),s.appendChild(n)}addEventListeners(){document.addEventListener("keydown",this.onKeyDownBound),this.dropdown.addEventListener("wheel",this.onWheelBound),document.addEventListener("click",this.onClickBound)}removeEventListeners(){document.removeEventListener("keydown",this.onKeyDownBound),this.dropdown.removeEventListener("wheel",this.onWheelBound),document.removeEventListener("click",this.onClickBound)}onMouseOver(e,t){t&&(this.focusedDropdown=t),this.selectedIndex=e,this.updateSelection()}onMouseOut(){this.selectedIndex=-1,this.updateSelection()}onMouseDown(e,t){t.preventDefault(),this.onSelect(e),this.dropdown.remove(),this.removeEventListeners()}onKeyDown(e){const t=Array.from(this.focusedDropdown.children),s=t[this.selectedIndex];if(os)if(38===e.keyCode)e.preventDefault(),this.selectedIndex=Math.max(0,this.selectedIndex-1),this.updateSelection();else if(40===e.keyCode)e.preventDefault(),this.selectedIndex=Math.min(t.length-1,this.selectedIndex+1),this.updateSelection();else if(39===e.keyCode){if(e.preventDefault(),s&&s.classList.contains("folder")){const e=s.querySelector(".easy-nested-dropdown");e&&(this.focusedDropdown=e,this.selectedIndex=0,this.updateSelection())}}else if(37===e.keyCode&&this.focusedDropdown!==this.dropdown){const e=this.focusedDropdown.closest(".easy-dropdown, .easy-nested-dropdown").parentNode.closest(".easy-dropdown, .easy-nested-dropdown");e&&(this.focusedDropdown=e,this.selectedIndex=Array.from(e.children).indexOf(this.focusedDropdown.parentNode),this.updateSelection())}else if((13===e.keyCode||9===e.keyCode)&&this.selectedIndex>=0){e.preventDefault(),s.classList.contains("item")&&(this.onSelect(t[this.selectedIndex].textContent),this.dropdown.remove(),this.removeEventListeners());const n=s.querySelector(".easy-nested-dropdown");n&&(this.focusedDropdown=n,this.selectedIndex=0,this.updateSelection())}else 27===e.keyCode&&(this.dropdown.remove(),this.removeEventListeners())}onWheel(e){const t=parseInt(this.dropdown.style.top);localStorage.getItem("Comfy.Settings.Comfy.InvertMenuScrolling")?this.dropdown.style.top=t+(e.deltaY<0?10:-10)+"px":this.dropdown.style.top=t+(e.deltaY<0?-10:10)+"px"}onClick(e){this.dropdown.contains(e.target)||e.target===this.inputEl||(this.dropdown.remove(),this.removeEventListeners())}updateSelection(){Array.from(this.focusedDropdown.children).forEach(((e,t)=>{t===this.selectedIndex?e.classList.add("selected"):e.classList.remove("selected")}))}}function ds(e){const t=e.min||0,s=e.max||0,n=e.step||1;if(0===n)return[];const i=[];let o=t;for(;o<=s;){if(Number.isInteger(n))i.push(Math.round(o)+"; ");else{let e=o.toFixed(3);-0==e&&(e="0.000"),/\.\d{3}$/.test(e)||(e+="0"),i.push(e+"; ")}o+=n}return s>=0&&t>=0?i:i.reverse()}let us={},cs={};function ps(e,t){String(e.id);const s=t.name,n=t.value.replace(/^(loader|preSampling):\s/,"");cs[s]?cs[s]!=us[n]&&(cs[s]=us[n]):cs={...cs,[s]:us[n]}}v.registerExtension({name:"Comfy.EasyUse.XYPlot",async beforeRegisterNodeDef(e,t,s){if("easy XYPlot"===t.name){us=t.input.hidden.plot_dict[0];for(const e in us){const t=us[e];if(Array.isArray(t)){let s=[];for(const e of t)s.push(e+"; ");us[e]=s}else us[e]="object"==typeof t?"seed"==e?t+"; ":ds(t):t+"; "}us.None=[],us["---------------------"]=[]}},nodeCreated(e){"easy XYPlot"===e.comfyClass&&(function(e){if(e.widgets)for(const t of e.widgets)if("x_axis"===t.name||"y_axis"===t.name){let s=t.value;Object.defineProperty(t,"value",{get:()=>s,set(n){n!==s&&(s=n,ps(e,t))}})}}(e),function(e){if(e.widgets){const t=e.widgets.filter((e=>"customtext"===e.type&&!1!==e.dynamicPrompts||e.dynamicPrompts));for(const e of t){let t=function(e,t,n,i){return e&&(t[n]=e),t.map((e=>s(e,i))).filter((e=>""!==e)).join("")},s=function(e,t){if(e=n(e),i(e,t))return e+"; ";let s=o(e,t);return 1===s.length||2===s.length?s[0]:i(a(e),t)?a(e)+"; ":""},n=function(e){return e.replace(/(\n|;| )/g,"")},i=function(e,t){return t.includes(e+"; ")},o=function(e,t){return t.filter((t=>t.toLowerCase().includes(e.toLowerCase())))},a=function(e){return Number(e)?Number(e).toFixed(3):["0","0.","0.0","0.00","00"].includes(e)?"0.000":e};const l=function(){const s=e.name[0]+"_axis";let n=(null==cs?void 0:cs[s])||[];if(0===n.length)return;const i=e.inputEl.value,o=e.inputEl.selectionStart;let a=i.split("; ");const l=i.substring(0,o).split("; ").length-1,r=a[l].replace(/\n/g,"").toLowerCase(),d=n.filter((e=>e.toLowerCase().includes(r))).map((e=>e.replace(/; /g,"")));if(d.length>0)ls(e.inputEl,d,(s=>{const i=t(s,a,l,n);e.inputEl.value=i}));else{as();const s=t(null,a,l,n);e.inputEl.value=s}};e.inputEl.removeEventListener("input",l),e.inputEl.addEventListener("input",l),e.inputEl.removeEventListener("mouseup",l),e.inputEl.addEventListener("mouseup",l)}}}(e))}});export{Y as $,M as N,b as a,v as b,fe as c,A as d,Qe as e,P as f,ge as g,x as h,Se as j,V as l,X as s,Ue as t,$ as u}; diff --git a/web_version/v2/assets/extensions-CrUNTPqr.js b/web_version/v2/assets/extensions-CrUNTPqr.js deleted file mode 100644 index a7fb826..0000000 --- a/web_version/v2/assets/extensions-CrUNTPqr.js +++ /dev/null @@ -1 +0,0 @@ -var e,t,s,o,n,i,a,l,r,d,u,c,p,h,m=Object.defineProperty,g=(e,t,s)=>((e,t,s)=>t in e?m(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s)(e,"symbol"!=typeof t?t+"":t,s);import{d as f,h as y}from"./vendor-DT1J-jWa.js";import{c as _}from"./lodash-CZi7izHi.js";let v=(null==(t=null==(e=window.comfyAPI)?void 0:e.app)?void 0:t.app)||null,b=(null==(o=null==(s=window.comfyAPI)?void 0:s.api)?void 0:o.api)||null,w=(null==(i=null==(n=window.comfyAPI)?void 0:n.ui)?void 0:i.$el)||null,L=(null==(l=null==(a=window.comfyAPI)?void 0:a.dialog)?void 0:l.ComfyDialog)||null,E=(null==(d=null==(r=window.comfyAPI)?void 0:r.widgets)?void 0:d.ComfyWidgets)||null,S=(null==(c=null==(u=window.comfyAPI)?void 0:u.utils)?void 0:c.applyTextReplacements)||null,C=(null==(h=null==(p=window.comfyAPI)?void 0:p.groupNode)?void 0:h.GroupNodeConfig)||null;const A=(e,t=void 0)=>{var s,o;return e?null==(o=null==(s=null==v?void 0:v.ui)?void 0:s.settings)?void 0:o.getSettingValue(e,t):null};function k(e,t=null,s=void 0){try{let o=e?A(e,s):null;return null==o&&(o=t?localStorage[t]:localStorage[e]||null),o}catch(o){return null}}function x(e,t=e=>{}){var s;const o=null==(s=v.ui.settings.settingsLookup)?void 0:s[e];o&&(o.onChange=e=>t(e))}async function I(e,t,s=null){var o,n;try{(null==(n=null==(o=null==v?void 0:v.ui)?void 0:o.settings)?void 0:n.setSettingValue)?v.ui.settings.setSettingValue(e,t):await b.storeSetting(e,t),s&&(localStorage[s]="object"==typeof t?JSON.stringify(t):t)}catch(i){}}const N="comfyui-easyuse-",T="dark-theme",O="#236692",D={PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd",FLOW_CONTROL:"#373780"},R=0x4000000000000,M=["loaders","latent","image","mask","sampling","_for_testing","advanced","utils","api"],G={ALWAYS:0,NEVER:2,BYPASS:4},P="easyuse_nodes_map",F=LGraphCanvas.node_colors.bgcolor,U={ColorPalette:{version:105,id:"obsidian",name:"Obsidian",colors:{node_slot:{CLIP:"#FFD500",CLIP_VISION:"#A8DADC",CLIP_VISION_OUTPUT:"#ad7452",CONDITIONING:"#FFA931",CONTROL_NET:"#6EE7B7",IMAGE:"#64B5F6",LATENT:"#FF9CF9",MASK:"#81C784",MODEL:"#B39DDB",STYLE_MODEL:"#C2FFAE",VAE:"#FF6E6E",TAESD:"#DCC274",PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd"},litegraph_base:{BACKGROUND_IMAGE:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",CLEAR_BACKGROUND_COLOR:"#222222",NODE_TITLE_COLOR:"#d4d4d8",NODE_SELECTED_TITLE_COLOR:"#ffffff",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#ffffff",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#09090b",NODE_DEFAULT_BGCOLOR:"rgba(24,24,27,.9)",NODE_DEFAULT_BOXCOLOR:"rgba(255,255,255,.75)",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:O,DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#242427",WIDGET_OUTLINE_COLOR:"#3f3f46",WIDGET_TEXT_COLOR:"#d4d4d8",WIDGET_SECONDARY_TEXT_COLOR:"#d4d4d8",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#A86",CONNECTING_LINK_COLOR:"#AFA"},comfy_base:{"fg-color":"#fff","bg-color":"#09090b","comfy-menu-bg":"rgba(24,24,24,.9)","comfy-input-bg":"#262626","input-text":"#ddd","descrip-text":"#999","drag-text":"#ccc","error-text":"#ff4444","border-color":"#29292c","tr-even-bg-color":"rgba(28,28,28,.9)","tr-odd-bg-color":"rgba(19,19,19,.9)"}}},NODE_COLORS:{red:{color:"#af3535",bgcolor:F,groupcolor:"#A88"},brown:{color:"#38291f",bgcolor:F,groupcolor:"#b06634"},green:{color:"#346434",bgcolor:F,groupcolor:"#8A8"},blue:{color:"#1f1f48",bgcolor:F,groupcolor:"#88A"},pale_blue:{color:"#006691",bgcolor:F,groupcolor:"#3f789e"},cyan:{color:"#008181",bgcolor:F,groupcolor:"#8AA"},purple:{color:"#422342",bgcolor:F,groupcolor:"#a1309b"},yellow:{color:"#c09430",bgcolor:F,groupcolor:"#b58b2a"},black:{color:"rgba(0,0,0,.8)",bgcolor:F,groupcolor:"#444"}}};let B=JSON.parse(JSON.stringify(U));delete B.NODE_COLORS,B.ColorPalette.id="obsidian_dark",B.ColorPalette.name="Obsidian Dark",B.ColorPalette.colors.litegraph_base.BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==",B.ColorPalette.colors.litegraph_base.CLEAR_BACKGROUND_COLOR="#09090b";const z=LGraphCanvas.node_colors.bgcolor,W={ColorPalette:{id:"milk_white",name:"Milk White",colors:{node_slot:{CLIP:"#FFA726",CLIP_VISION:"#5C6BC0",CLIP_VISION_OUTPUT:"#8D6E63",CONDITIONING:"#EF5350",CONTROL_NET:"#66BB6A",IMAGE:"#42A5F5",LATENT:"#AB47BC",MASK:"#9CCC65",MODEL:"#7E57C2",STYLE_MODEL:"#D4E157",VAE:"#FF7043",PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd"},litegraph_base:{BACKGROUND_IMAGE:"data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==",CLEAR_BACKGROUND_COLOR:"lightgray",NODE_TITLE_COLOR:"#222",NODE_SELECTED_TITLE_COLOR:"#000",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#444",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#F7F7F7",NODE_DEFAULT_BGCOLOR:"#F5F5F5",NODE_DEFAULT_BOXCOLOR:"#555",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:"#000",DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0.1)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#D4D4D4",WIDGET_OUTLINE_COLOR:"#999",WIDGET_TEXT_COLOR:"#222",WIDGET_SECONDARY_TEXT_COLOR:"#555",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#FF9800",CONNECTING_LINK_COLOR:"#222"},comfy_base:{"fg-color":"#222","bg-color":"#DDD","comfy-menu-bg":"#F5F5F5","comfy-input-bg":"#C9C9C9","input-text":"#222","descrip-text":"#444","drag-text":"#555","error-text":"#F44336","border-color":"#bbb","tr-even-bg-color":"#f9f9f9","tr-odd-bg-color":"#fff","content-bg":"#e0e0e0","content-fg":"#222","content-hover-bg":"#adadad","content-hover-fg":"#222"}}},NODE_COLORS:{red:{color:"#af3535",bgcolor:z,groupcolor:"#A88"},brown:{color:"#38291f",bgcolor:z,groupcolor:"#b06634"},green:{color:"#346434",bgcolor:z,groupcolor:"#8A8"},blue:{color:"#1f1f48",bgcolor:z,groupcolor:"#88A"},pale_blue:{color:"#006691",bgcolor:z,groupcolor:"#3f789e"},cyan:{color:"#008181",bgcolor:z,groupcolor:"#8AA"},purple:{color:"#422342",bgcolor:z,groupcolor:"#a1309b"},yellow:{color:"#c09430",bgcolor:z,groupcolor:"#b58b2a"},black:{color:"rgba(0,0,0,.8)",bgcolor:z,groupcolor:"#444"}}},j={"Workflow created by":"工作流创建者","Watch more video content":"观看更多视频内容","Workflow Guide":"工作流指南","💎 View Checkpoint Info...":"💎 查看 Checkpoint 信息...","💎 View Lora Info...":"💎 查看 Lora 信息...","🔃 Reload Node":"🔃 刷新节点","Updated At:":"最近更新:","Created At:":"首次发布:","✏️ Edit":"✏️ 编辑","💾 Save":"💾 保存","No notes":"当前还没有备注内容","Saving Notes...":"正在保存备注...","Type your notes here":"在这里输入备注内容",ModelName:"模型名称","Models Required":"所需模型","Download Model":"下载模型","Source Url":"模型源地址",Notes:"备注",Type:"类型","Trained Words":"训练词",BaseModel:"基础算法",Details:"详情",Description:"描述",Download:"下载量",Source:"来源","Saving Preview...":"正在保存预览图...","Saving Succeed":"保存成功","Clean SuccessFully":"清理成功","Clean Failed":"清理失败","Saving Failed":"保存失败","No COMBO link":"沒有找到COMBO连接","Reboot ComfyUI":"重启ComfyUI","Are you sure you'd like to reboot the server?":"是否要重启ComfyUI?","Nodes Map":"管理节点组","Nodes map sorting mode":"管理节点组排序模式","No Nodes":"未找到节点","No nodes found in the map":"在工作流程中没有找到节点","Expand All":"展开所有组","Collapse All":"折叠所有组",Close:"关闭","Default automatic sorting, if set to manual, groups can be dragged and dropped and the sorting results saved.":"默认自动排序,如果设置为手动,组可以拖放并保存排序结果。","For drag and drop sorting, please find Nodes map sorting mode in Settings->EasyUse and change it to manual":"如需拖拽排序请在设置->EasyUse节点中找到管理节点组排序模式并修改成 manual",Queue:"队列","Cleanup Of VRAM Usage":"清理显存占用","Please stop all running tasks before cleaning GPU":"请在清理GPU之前停止所有运行中的任务",Always:"启用中",Bypass:"已忽略",Never:"已停用","Auto Sorting":"自动排序","Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never":"点击`启用中/已忽略`可设置组模式, 长按可停用该组节点","Enable Shift+Up/Down/Left/Right key and Shift+Ctrl+Alt+Left/Right to align selected nodes":"启用 Shift+上/下/左/右 和 Shift+Ctrl+Alt+左/右 键对齐选中的节点","Enable Shift+Ctrl+Left/Right key to normalize selected nodes":"启用 Shift+Ctrl+左/右 键规范化选中的节点","Enable Shift+g to add selected nodes to a group":"启用 Shift+g 键将选中的节点添加一个组","Enable Shift+r to unload models and node cache":"启用 Shift+r 键卸载模型和节点缓存","Enable Shift+m to toggle nodes map":"启用 Shift+m 键显隐管理节点组","Enable Up/Down/Left/Right key to jump nearest nodes":"启用 上/下/左/右 键跳转到最近的前后节点","Enable Alt+1~9 to paste nodes from nodes template":"启用 Alt+1~9 从节点模板粘贴到工作流中","Enable contextMenu auto nest subdirectories":"启用上下文菜单自动嵌套子目录","Enable right-click menu to add node A~Z sorting":"启用右键菜单中新建节点A~Z排序","Enable model thumbnails display":"启动模型预览图显示","Enable nodes runtime display":"启动节点运行时间显示","Enable chain get node and set node with parent nodes":"启用将获取点和设置点与父节点链在一起","Maximum number of model thumbnails displayed":"显示的模型缩略图的最大数量","Too many thumbnails will affect the first loading time, set the maximum value to not load the thumbnail function when there are too many models's thumbnail":"太多的缩略图会影响首次加载时间,当模型缩略图太多时,设置最大值以不加载缩略图功能","Too many thumbnails, have closed the display":"模型缩略图太多啦,为您关闭了显示","Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute horizontal/vertical nodes":"Shift+上/下/左/右 可以对齐选中的节点, Shift+Ctrl+Alt+左/右 可以水平/垂直分布节点","Enable Shift+Ctrl+Left key to normalize width and Shift+Ctrl+Right key to normalize height":"启用 Shift+Ctrl+左 键规范化宽度和 Shift+Ctrl+右 键规范化高度","After v1.2.39, Ctrl+g can be used instead of it":"从v1.2.39开始,可以使用Ctrl+g代替","Use three shortcut buttons in the right-click menu":"在右键菜单中使用三个快捷按钮","Enable Nodes Map":"启用节点组管理","You need to refresh the page to update successfully":"您需要刷新页面以成功更新","Get styles list Failed":"获取样式列表失败","Get style image Failed":"获取样式图片失败","Empty All":"清空所有","Type here to search styles ...":"在此处输入以搜索样式 ...","Loading UserInfo...":"正在获取用户信息...","Please set the APIKEY first":"请先设置APIKEY","Setting APIKEY":"设置APIKEY","Save Account Info":"保存账号信息",Choose:"选择",Delete:"删除",Edit:"编辑","At least one account is required":"删除失败: 至少需要一个账户","APIKEY is not Empty":"APIKEY 不能为空","Add Account":"添加账号","Getting Your APIKEY":"获取您的APIKEY","Choose Selected Images":"选择选中的图片","Choose images to continue":"选择图片以继续",Background:"背景",Hat:"帽子",Hair:"头发",Body:"身体",Face:"脸部",Clothes:"衣服",Others:"其他",Glove:"手套",Glasses:"眼镜",Sunglasses:"太阳镜","Upper-clothes":"上衣","Top-clothes":"上衣","Bottom-clothes":"下身装","Torso-skin":"皮肤",Dress:"连衣裙",Coat:"外套",Socks:"袜子",Pants:"裤子",Jumpsuits:"连体衣",Scarf:"围巾",Skirt:"裙子","Left-arm":"左臂","Right-arm":"右臂","Left-leg":"左腿","Right-leg":"右腿","Left-foot":"左脚","Right-foot":"右脚","Left-shoe":"左鞋","Right-shoe":"右鞋",s:"秒","No Node Templates Found":"未找到节点模板预设","Get Node Templates File Failed":"获取节点模板文件失败","Node template with {key} not set":"未设置快捷键为{key}的节点预设","ComfyUI Basic":"ComfyUI 基础节点","Recommend Nodes":"推荐节点","Others A~Z":"其他节点 A~Z"},V=k("AGL.Locale"),Y=(e,t=!1)=>"zh-CN"===(t?navigator.language:V)&&j[e]||e,H={addGroup:{id:"EasyUse.Hotkeys.AddGroup",name:Y("Enable Shift+g to add selected nodes to a group"),tooltip:Y("After v1.2.39, Ctrl+g can be used instead of it"),type:"boolean",defaultValue:!0},cleanVRAMUsed:{id:"EasyUse.Hotkeys.cleanVRAMUsed",name:Y("Enable Shift+r to unload models and node cache"),type:"boolean",defaultValue:!0},toggleSiteMap:{id:"EasyUse.Hotkeys.toggleNodesMap",name:Y("Enable Shift+m to toggle nodes map"),type:"boolean",defaultValue:!0},alignSelectedNodes:{id:"EasyUse.Hotkeys.AlignSelectedNodes",name:Y("Enable Shift+Up/Down/Left/Right key and Shift+Ctrl+Alt+Left/Right to align selected nodes"),tooltip:Y("Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute horizontal/vertical nodes"),type:"boolean",defaultValue:!0},NormalizeSelectedNodes:{id:"EasyUse.Hotkeys.NormalizeSelectedNodes",name:Y("Enable Shift+Ctrl+Left/Right key to normalize selected nodes"),tooltip:Y("Enable Shift+Ctrl+Left key to normalize width and Shift+Ctrl+Right key to normalize height"),type:"boolean",defaultValue:!0},nodesTemplate:{id:"EasyUse.Hotkeys.NodesTemplate",name:Y("Enable Alt+1~9 to paste nodes from nodes template"),type:"boolean",defaultValue:!0},jumpNearestNodes:{id:"EasyUse.Hotkeys.JumpNearestNodes",name:Y("Enable Up/Down/Left/Right key to jump nearest nodes"),type:"boolean",defaultValue:!0},subDirectories:{id:"EasyUse.ContextMenu.SubDirectories",name:Y("Enable contextMenu auto nest subdirectories"),type:"boolean",defaultValue:!1},modelsThumbnails:{id:"EasyUse.ContextMenu.ModelsThumbnails",name:Y("Enable model thumbnails display"),type:"boolean",defaultValue:!1},modelsThumbnailsLimit:{id:"EasyUse.ContextMenu.ModelsThumbnailsLimit",name:Y("Maximum number of model thumbnails displayed"),tooltip:Y("Too many thumbnails will affect the first loading time, set the maximum value to not load the thumbnail function when there are too many models's thumbnail"),type:"slider",attrs:{min:0,max:5e3,step:100},defaultValue:500},rightMenuNodesSort:{id:"EasyUse.ContextMenu.NodesSort",name:Y("Enable right-click menu to add node A~Z sorting"),type:"boolean",defaultValue:!0},quickOptions:{id:"EasyUse.ContextMenu.QuickOptions",name:Y("Use three shortcut buttons in the right-click menu"),type:"combo",options:["At the forefront","At the end","Disable"],defaultValue:"At the forefront"},nodesRuntime:{id:"EasyUse.Nodes.Runtime",name:Y("Enable nodes runtime display"),type:"boolean",defaultValue:!0},chainGetSet:{id:"EasyUse.Nodes.ChainGetSet",name:Y("Enable chain get node and set node with parent nodes"),type:"boolean",defaultValue:!0},nodesMap:{id:"EasyUse.NodesMap.Sorting",name:Y("Nodes map sorting mode"),tooltip:Y("Default automatic sorting, if set to manual, groups can be dragged and dropped and the sorting results saved."),type:"combo",options:["Auto sorting","Manual drag&drop sorting"],defaultValue:"Auto sorting"},enableNodesMap:{id:"EasyUse.NodesMap.Enable",name:Y("Enable Nodes Map"),tooltip:Y("You need to refresh the page to update successfully"),type:"boolean",defaultValue:!0}};function X(e=100,t){return new Promise((s=>{setTimeout((()=>{s(t)}),e)}))}function Z(e,t){if(e="number"==typeof e?e:e instanceof Date?e.getTime():parseInt(e),isNaN(e))return null;let s=new Date(e);(e=s.toString().split(/[\s\:]/g).slice(0,-2))[1]=["01","02","03","04","05","06","07","08","09","10","11","12"][s.getMonth()];let o={MM:1,dd:2,yyyy:3,hh:4,mm:5,ss:6};return t.replace(/([Mmdhs]|y{2})\1/g,(t=>e[o[t]]))}const K=/Mac|iPod|iPhone|iPad/.test(navigator.platform),J=e=>K?e.replace(/Ctrl/g,"⌘").replace(/Alt/g,"⌥").replace(/Shift/g,"⇧"):e,$=f("groups",{state:e=>({groups:[],nodes:[],isWatching:!1}),getters:{groups_nodes(){var e;let t=[],s=[];if((null==(e=this.nodes)?void 0:e.length)>0){this.nodes.map((e=>{let o=e.pos,n=!1;for(let s=0;si.pos[0]&&o[0]i.pos[1]&&o[1]e.pos[0]-t.pos[0])).sort(((e,t)=>e.pos[1]-t.pos[1])))},setNodes(e){this.nodes=_(e)},update(){var e,t,s;(((null==(e=v.extensionManager)?void 0:e.activeSidebarTab)||(null==(s=null==(t=v.extensionManager.sidebarTab)?void 0:t.activeSidebarTab)?void 0:s.id))===P||this.isWatching)&&setTimeout((e=>{this.setGroups(v.canvas.graph._groups),this.setNodes(v.canvas.graph._nodes)}),1)},watchGraph(e=!1){e&&(this.isWatching=!0);let t=this;this.update();const s=v.graph.onNodeAdded;v.graph.onNodeAdded=function(e){t.update();const o=e.onRemoved;return e.onRemoved=function(){return t.update(),null==o?void 0:o.apply(this,arguments)},null==s?void 0:s.apply(this,arguments)},v.canvas.onNodeMoved=function(e){t.update()};const o=LGraphCanvas.onNodeAlign;LGraphCanvas.onNodeAlign=function(e){return t.update(),null==o?void 0:o.apply(this,arguments)};const n=LGraphCanvas.onGroupAdd;LGraphCanvas.onGroupAdd=function(){return t.update(),null==n?void 0:n.apply(this,arguments)};const i=LGraphCanvas.onGroupAlign;LGraphCanvas.onGroupAlign=function(e){return t.update(),null==i?void 0:i.apply(this,arguments)};const a=LGraphCanvas.onMenuNodeRemove;LGraphCanvas.onMenuNodeRemove=function(e){return t.update(),null==a?void 0:a.apply(this,arguments)}},unwatchGraph(){this.isWatching=!1}}});let q=null;const Q=["custom_obsidian","custom_obsidian_dark","custom_milk_white"],ee={"easy positive":"green","easy negative":"red","easy promptList":"cyan","easy promptLine":"cyan","easy promptConcat":"cyan","easy promptReplace":"cyan","easy forLoopStart":"blue","easy forLoopEnd":"blue","easy loadImagesForLoop":"blue"};let te=LGraphCanvas.node_colors,se=null,oe=null,ne=null,ie=null;for(let hs in H){const e="Disabled"==k("Comfy.UseNewMenu")?"👽 "+J(H[hs].name):J(H[hs].name),t=H[hs].tooltip?J(H[hs].tooltip):"";ae={...H[hs],name:e,tooltip:t},v.ui.settings.addSetting(ae)}var ae;function le(e,t=!1){let s="after",o="before";t&&([o,s]=[s,o]),e.label=(e.label??e.name).replace(o,s),e.name=e.label}function re(e,t,s,o,n,i,a){t.strokeStyle=o,t.fillStyle=n;let l=LiteGraph.NODE_TITLE_HEIGHT,r=this.ds.scale<.5,d=e._shape||e.constructor.shape||LiteGraph.ROUND_SHAPE,u=e.constructor.title_mode,c=!0;u==LiteGraph.TRANSPARENT_TITLE||u==LiteGraph.NO_TITLE?c=!1:u==LiteGraph.AUTOHIDE_TITLE&&mouse_over&&(c=!0);let p=new Float32Array(4);p=[0,c?-l:0,s[0]+1,c?s[1]+l:s[1]];let h=t.globalAlpha;if(t.lineWidth=1,t.beginPath(),d==LiteGraph.BOX_SHAPE||r?t.fillRect(p[0],p[1],p[2],p[3]):d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CARD_SHAPE?t.roundRect(p[0],p[1],p[2],p[3],d==LiteGraph.CARD_SHAPE?[this.round_radius,this.round_radius,0,0]:[this.round_radius]):d==LiteGraph.CIRCLE_SHAPE&&t.arc(.5*s[0],.5*s[1],.5*s[0],0,2*Math.PI),t.strokeStyle=LiteGraph.WIDGET_OUTLINE_COLOR,t.stroke(),t.strokeStyle=o,t.fill(),!e.flags.collapsed&&c&&(t.shadowColor="transparent",t.fillStyle="rgba(0,0,0,0.2)",t.fillRect(0,-1,p[2],2)),t.shadowColor="transparent",e.onDrawBackground&&e.onDrawBackground(t,this,this.canvas,this.graph_mouse),c||u==LiteGraph.TRANSPARENT_TITLE){const n="dark"==function(e){let t=e.replace("#","");return s=parseInt(t.substring(0,2),16),o=parseInt(t.substring(2,4),16),n=parseInt(t.substring(4,6),16),.299*s+.587*o+.114*n>127.5?"light":"dark";var s,o,n}((null==e?void 0:e.color)||"#ffffff");if(e.onDrawTitleBar)e.onDrawTitleBar(t,l,s,this.ds.scale,o);else if(u!=LiteGraph.TRANSPARENT_TITLE&&(e.constructor.title_color||this.render_title_colored)){let n=e.constructor.title_color||o;if(e.flags.collapsed&&(t.shadowColor=LiteGraph.DEFAULT_SHADOW_COLOR),this.use_gradients){let e=LGraphCanvas.gradients[n];e||(e=LGraphCanvas.gradients[n]=t.createLinearGradient(0,0,400,0),e.addColorStop(0,n),e.addColorStop(1,"#000")),t.fillStyle=e}else t.fillStyle=n;t.beginPath(),d==LiteGraph.BOX_SHAPE||r?t.rect(0,-l,s[0]+1,l):d!=LiteGraph.ROUND_SHAPE&&d!=LiteGraph.CARD_SHAPE||t.roundRect(0,-l,s[0]+1,l,e.flags.collapsed?[this.round_radius]:[this.round_radius,this.round_radius,0,0]),t.fill(),t.shadowColor="transparent"}let a=!1;LiteGraph.node_box_coloured_by_mode&&LiteGraph.NODE_MODES_COLORS[e.mode]&&(a=LiteGraph.NODE_MODES_COLORS[e.mode]),LiteGraph.node_box_coloured_when_on&&(a=e.action_triggered?"#FFF":e.execute_triggered?"#AAA":a);let c=10;if(e.onDrawTitleBox)e.onDrawTitleBox(t,l,s,this.ds.scale);else if(d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CIRCLE_SHAPE||d==LiteGraph.CARD_SHAPE){const s=n?"#ffffff":LiteGraph.NODE_SELECTED_TITLE_COLOR,o=n?"#eeeeee":e.boxcolor||a||LiteGraph.NODE_DEFAULT_BOXCOLOR;t.fillStyle=i?s:o,t.beginPath(),t.fillRect(10,0-1.05*c-1,1.1*c,.125*c),t.fillRect(10,0-1.45*c-1,1.1*c,.125*c),t.fillRect(10,0-1.85*c-1,1.1*c,.125*c)}else t.fillStyle=e.boxcolor||a||LiteGraph.NODE_DEFAULT_BOXCOLOR,t.fillRect(.5*(l-c),-.5*(l+c),c,c);if(t.globalAlpha=h,e.onDrawTitleText&&e.onDrawTitleText(t,l,s,this.ds.scale,this.title_text_font,i),!r){t.font=this.title_text_font;let s=String(e.getTitle());s&&(t.fillStyle=i?n?"#ffffff":LiteGraph.NODE_SELECTED_TITLE_COLOR:n?"#ffffff":e.constructor.title_text_color||this.node_title_color,e.flags.collapsed?(t.textAlign="left",t.measureText(s),t.fillText(s.substr(0,20),l,LiteGraph.NODE_TITLE_TEXT_Y-l),t.textAlign="left"):(t.textAlign="left",t.fillText(s,l,LiteGraph.NODE_TITLE_TEXT_Y-l)))}if(!e.flags.collapsed&&e.subgraph&&!e.skip_subgraph_button){let s=LiteGraph.NODE_TITLE_HEIGHT,o=e.size[0]-s,n=LiteGraph.isInsideRectangle(this.graph_mouse[0]-e.pos[0],this.graph_mouse[1]-e.pos[1],o+2,2-s,s-4,s-4);t.fillStyle=n?"#888":"#555",d==LiteGraph.BOX_SHAPE||r?t.fillRect(o+2,2-s,s-4,s-4):(t.beginPath(),t.roundRect(o+2,2-s,s-4,s-4,[4]),t.fill()),t.fillStyle="#333",t.beginPath(),t.moveTo(o+.2*s,.6*-s),t.lineTo(o+.8*s,.6*-s),t.lineTo(o+.5*s,.3*-s),t.fill()}e.onDrawTitle&&e.onDrawTitle(t)}if(i){e.onBounding&&e.onBounding(p),u==LiteGraph.TRANSPARENT_TITLE&&(p[1]-=l,p[3]+=l),t.lineWidth=2,t.globalAlpha=.8,t.beginPath();let n=0,i=0,a=1;d==LiteGraph.BOX_SHAPE?t.rect(n+p[0],n+p[1],i+p[2],i+p[3]):d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CARD_SHAPE&&e.flags.collapsed?t.roundRect(n+p[0],n+p[1],i+p[2],i+p[3],[this.round_radius*a]):d==LiteGraph.CARD_SHAPE?t.roundRect(n+p[0],n+p[1],i+p[2],i+p[3],[this.round_radius*a,a,this.round_radius*a,a]):d==LiteGraph.CIRCLE_SHAPE&&t.arc(.5*s[0],.5*s[1],.5*s[0]+6,0,2*Math.PI),t.strokeStyle=LiteGraph.NODE_BOX_OUTLINE_COLOR,t.stroke(),t.strokeStyle=o,t.globalAlpha=1}e.execute_triggered>0&&e.execute_triggered--,e.action_triggered>0&&e.action_triggered--}function de(e,t,s,o){var n,i;if(!e.widgets||!e.widgets.length)return 0;let a=e.size[0],l=(e.size[1],e.widgets);t+=2;let r=LiteGraph.NODE_WIDGET_HEIGHT,d=this.ds.scale>.5;s.save(),s.globalAlpha=this.editor_alpha;let u=LiteGraph.WIDGET_OUTLINE_COLOR,c=LiteGraph.WIDGET_BGCOLOR,p=LiteGraph.WIDGET_TEXT_COLOR,h=LiteGraph.WIDGET_SECONDARY_TEXT_COLOR,m=12;for(let f=0;f1&&(a=1),s.fillStyle=y.options.hasOwnProperty("slider_color")?y.options.slider_color:o==y?u:O,s.beginPath(),s.roundRect(m,_,a*(v-24),r,[.25*r]),s.fill(),y.marker){let e=(y.marker-y.options.min)/t;e<0&&(e=0),e>1&&(e=1),s.fillStyle=y.options.hasOwnProperty("marker_color")?y.options.marker_color:"#AA9",s.roundRect(m+e*(v-24),_,2,r,[.25*r])}if(d){s.textAlign="center",s.fillStyle=p;let e=(y.label||y.name)+" : "+Number(y.value).toFixed(null!=y.options.precision?y.options.precision:3);s.fillText(e,.5*v,_+.7*r)}break;case"number":case"combo":if(s.textAlign="left",s.strokeStyle=u,s.fillStyle=c,s.beginPath(),d?s.roundRect(m,_,v-24,r,[.25*r]):s.rect(m,_,v-24,r),s.fill(),d){y.disabled||s.stroke(),s.fillStyle=p,y.disabled||(s.beginPath(),s.moveTo(24,_+6.5),s.lineTo(18,_+.5*r),s.lineTo(24,_+r-6.5),s.fill(),s.beginPath(),s.moveTo(v-m-12,_+6.5),s.lineTo(v-m-6,_+.5*r),s.lineTo(v-m-12,_+r-6.5),s.fill()),s.fillStyle=h,s.font="10px Inter",s.fillText(y.label||y.name,29,_+.7*r),s.fillStyle=p,s.textAlign="right";let e=6;if("number"==y.type)s.font="10px Inter",s.fillText(Number(y.value).toFixed(void 0!==y.options.precision?y.options.precision:3),v-24-e,_+.7*r);else{let t=y.value;if(y.options.values){let e=y.options.values;e.constructor===Function&&(e=e()),e&&e.constructor!==Array&&(t=e[y.value])}const o=v-48-(s.measureText(y.label||y.name).width+24),n=s.measureText(t).width;if(n>o){const e="…",i=s.measureText(e).width,a=s.measureText("a").width;if(o<=i)t="․";else{t=`${t}`;if(n+i-o+3*a>o){const e=o+3*a,s=Math.floor((e-i)/a);t=t.substr(0,s)}for(;s.measureText(t).width+i>o;)t=t.substr(0,t.length-1);t+=e}}s.fillText(t,v-24-e,_+.7*r)}}break;case"string":case"text":if(s.textAlign="left",s.strokeStyle=u,s.fillStyle=c,s.beginPath(),d?s.roundRect(m,_,v-24,r,[.25*r]):s.rect(m,_,v-24,r),s.fill(),d){y.disabled||s.stroke(),s.save(),s.beginPath(),s.rect(m,_,v-24,r),s.clip(),s.fillStyle=h;const e=y.label||y.name;s.font="10px Inter",null!=e&&s.fillText(e,24,_+.7*r),s.fillStyle=p,s.textAlign="right",s.fillText(String(y.value).substr(0,30),v-24,_+.7*r),s.restore()}break;default:y.draw&&y.draw(s,e,v,_,r)}t+=(y.computeSize?y.computeSize(v)[1]:r)+4,s.globalAlpha=this.editor_alpha}s.restore(),s.textAlign="left"}function ue(e,t,s,o,n){return new LiteGraph.ContextMenu(LiteGraph.NODE_MODES,{event:s,callback:function(e){if(!n)return;var t=Object.values(LiteGraph.NODE_MODES).indexOf(e),s=function(e){t>=0&&LiteGraph.NODE_MODES[t]?e.changeMode(t):e.changeMode(LiteGraph.ALWAYS),q||(q=$()),q.update()},o=LGraphCanvas.active_canvas;if(!o.selected_nodes||Object.keys(o.selected_nodes).length<=1)s(n);else for(var i in o.selected_nodes)s(o.selected_nodes[i])},parentMenu:o,node:n}),!1}function ce(e,t,s,o,n){if(!n)throw"no node for color";var i=[];for(var a in i.push({value:null,content:"No color"}),LGraphCanvas.node_colors){var l=LGraphCanvas.node_colors[a];e={value:a,content:""+a+""};i.push(e)}return new LiteGraph.ContextMenu(i,{event:s,callback:function(e){if(!n)return;var t=e.value?LGraphCanvas.node_colors[e.value]:null,s=function(e){t?e.constructor===LiteGraph.LGraphGroup?e.color=t.groupcolor:(e.color=t.color,e.bgcolor=t.bgcolor):(delete e.color,delete e.bgcolor),q||(q=$()),q.update()},o=LGraphCanvas.active_canvas;if(!o.selected_nodes||Object.keys(o.selected_nodes).length<=1)s(n);else for(var i in o.selected_nodes)s(o.selected_nodes[i]);n.setDirtyCanvas(!0,!0)},parentMenu:o,node:n}),!1}function pe(e,t,s,o,n){var i=e.property||"title",a=n[i],l=document.createElement("div");l.is_modified=!1,l.className="graphdialog",l.innerHTML="",l.close=function(){l.parentNode&&l.parentNode.removeChild(l)},l.querySelector(".name").innerText=i;var r=l.querySelector(".value");r&&(r.value=a,r.addEventListener("blur",(function(e){this.focus()})),r.addEventListener("keydown",(function(e){if(l.is_modified=!0,27==e.keyCode)l.close();else if(13==e.keyCode)m();else if(13!=e.keyCode&&"textarea"!=e.target.localName)return;e.preventDefault(),e.stopPropagation()})));var d=LGraphCanvas.active_canvas.canvas,u=d.getBoundingClientRect(),c=-20,p=-20;u&&(c-=u.left,p-=u.top),event?(l.style.left=event.clientX+c+"px",l.style.top=event.clientY+p+"px"):(l.style.left=.5*d.width+c+"px",l.style.top=.5*d.height+p+"px"),l.querySelector("button").addEventListener("click",m),d.parentNode.appendChild(l),r&&r.focus();var h=null;function m(){r&&function(t){"Number"==e.type?t=Number(t):"Boolean"==e.type&&(t=Boolean(t));n[i]=t,l.parentNode&&l.parentNode.removeChild(l);n.setDirtyCanvas(!0,!0),q||(q=$());q.update()}(r.value)}l.addEventListener("mouseleave",(function(e){LiteGraph.dialog_close_on_mouse_leave&&!l.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(h=setTimeout(l.close,LiteGraph.dialog_close_on_mouse_leave_delay))})),l.addEventListener("mouseenter",(function(e){LiteGraph.dialog_close_on_mouse_leave&&h&&clearTimeout(h)}))}v.registerExtension({name:"Comfy.EasyUse.UI",init(){var e,t;const s="Comfy.CustomColorPalettes",o="Comfy.Settings.Comfy.CustomColorPalettes";if(oe||(oe=k(s,o,{})),ne||(ne=k("Comfy.ColorPalette","Comfy.Settings.Comfy.ColorPalette")||"dark"),(!(null==(e=null==oe?void 0:oe.obsidian)?void 0:e.version)||oe.obsidian.version{(null==e?void 0:e.value)&&(null==e?void 0:e.oldValue)&&(await X(1),Object.assign(v.canvas.default_connection_color_byType,D),Object.assign(LGraphCanvas.link_type_colors,D)),"custom_milk_white"==e.value&&document.body.classList.remove(T)})),setTimeout((e=>he(k("Comfy.UseNewMenu")||"Disabled")),1);const t=null==(e=v.ui.settings.settingsLookup)?void 0:e["Comfy.UseNewMenu"];t&&(t.onChange=e=>he(e))},async nodeCreated(e){var t;if(ee.hasOwnProperty(e.comfyClass)){const t=ee[e.comfyClass],s=te[t];if(!s)return;s.color&&(e.color=s.color),s.bgcolor&&(e.bgcolor=s.bgcolor)}if(se||(se=k("Comfy.WidgetControlMode")),"before"==se){const s="before"==se;if((null==(t=e.widgets)?void 0:t.length)>0)for(const t of e.widgets)if(["control_before_generate","control_after_generate"].includes(t.name)&&(await le(t,s),t.linkedWidgets))for(const e of t.linkedWidgets)await le(e,s)}}});const he=e=>{var t;const s=(null==(t=document.getElementById("crystools-root"))?void 0:t.children)||null,o=k("Comfy.Workflow.WorkflowTabsPosition",null,"");if((null==s?void 0:s.length)>0&&o)if(ie||(ie=document.getElementById("MonitorUI")),"Disabled"==e){document.getElementById("crystools-root").appendChild(ie)}else{let e=document.getElementById("crystools-root-easyuse");if(e)e.appendChild(ie);else{const e=document.getElementsByClassName("comfyui-menu-right");e.length>0&&e[0].before(w("div",{id:"crystools-root-easyuse"},ie))}}};let me={};const ge=(e,t)=>e.widgets.find((e=>e.name===t)),fe=(e,t,s=!1,o="")=>{var n;if(!t||((e,t)=>!!e.inputs&&e.inputs.some((e=>e.name===t)))(e,t.name))return;me[t.name]||(me[t.name]={origType:t.type,origComputeSize:t.computeSize});const i=e.size;t.type=s?me[t.name].origType:"easyHidden"+o,t.computeSize=s?me[t.name].origComputeSize:()=>[0,-4],null==(n=t.linkedWidgets)||n.forEach((o=>fe(e,o,":"+t.name,s)));const a=s?Math.max(e.computeSize()[1],i[1]):e.size[1];e.setSize([e.size[0],a])},ye=(e,t=0)=>{var s,o;if(e)return(null==(s=e.widgets)?void 0:s[t])?e.widgets[t].value:e.widgets_values?null==(o=e.widgets_values)?void 0:o[t]:void 0},_e=e=>e.setSize([e.size[0],e.computeSize()[1]]),ve=(e,t)=>graph.getNodeById(e),be=e=>{var t;try{return Object.values(null==(t=null==graph?void 0:graph.list_of_graphcanvas[0])?void 0:t.selected_nodes)}catch(s){return[]}};function we(e,t,s){return e+(o=s,(.5-.5*Math.cos(Math.PI*o))*(t-e));var o}const Le=(e,t=!0)=>{var s,o;const n=(null==(o=null==(s=e.graph)?void 0:s.list_of_graphcanvas)?void 0:o[0])||null;if(!n)return;const[i,a]=e.pos,[l,r]=e.size;(([e,t],s)=>{const o=s.ds,n=document.body.clientWidth,i=document.body.clientHeight,a=o.scale,l=.5*n/a-e,r=.5*i/a-t,d=Date.now()+250,u=o.offset[0],c=o.offset[1],p=()=>{const e=d-Date.now();if(!(Date.now(){const t=ve(e);t&&Le(t)},Se=(e,t=(()=>graph.links??[])())=>t[e],Ce=e=>e.toLowerCase().replace(/_./g,(e=>e.replace("_","").toUpperCase())),Ae=e=>"easy getNode"===e.type,ke=e=>"easy setNode"===e.type,xe=e=>Ae(e)||ke(e),Ie=(e=(()=>graph._nodes??[])())=>e.filter((e=>xe(e)));let Ne={},Te={};const Oe=(e,t,s=0)=>{e.widgets_values||(e.widgets_values=[]),e.widgets_values[s]=t,e.widgets[s].value=t},De=e=>graph.add(e),Re=e=>graph.remove(e),Me=(e,t=0)=>{var s,o;if("Reroute"!==e.type)return[e,t];const n=e,i=null==(o=null==(s=n.inputs)?void 0:s[0])?void 0:o.link;if(!i)return[n,t];const a=Se(i);if(!a)return[n,t];const l=ve(a.origin_id);return l?(setTimeout((()=>{Re(n)})),Me(l,a.origin_slot)):[n,t]},Ge=e=>{var t,s,o;if("Reroute"!==e.type)return e;const n=e,i=null==(s=null==(t=n.outputs)?void 0:t[0])?void 0:s.links;if(!i)return n;const a=i[0];if(!a)return n;const l=Se(a);if(!l)return n;const r=ve(l.target_id);return r?(1===(null==(o=n.outputs[0].links)?void 0:o.length)&&setTimeout((()=>{Re(n)})),Ge(r)):n},Pe=(e,t="width")=>{var s;const o=e[0],n="width"==t?0:1,i=null==(s=o.size)?void 0:s[n];i&&(e.forEach((e=>{e.size[n]=i})),LGraphCanvas.active_canvas.setDirty(!0,!0))},Fe=(e,t="horizontal")=>{if(e.length<3)return;const s="horizontal"===t?0:1;e.sort(((e,t)=>e.pos[s]-t.pos[s]));const o=Math.min(...e.map((e=>e.pos[s]))),n=(Math.max(...e.map((e=>e.pos[s]+e.size[s])))-o-e.reduce(((e,t)=>e+t.size[s]),0))/(e.length-1);let i=o;e.forEach((e=>{e.pos[s]=i,i+=e.size[s]+n})),LGraphCanvas.active_canvas.setDirty(!0,!0)};const Ue=new class{constructor(){g(this,"element",w(`div.${N}toast`)),g(this,"children",HTMLElement),g(this,"container",document.body),this.container.appendChild(this.element)}async show(e){let t=w(`div.${N}toast-container`,[w("div",[w("span",[...e.icon?[w("i",{className:e.icon})]:[],w("span",e.content)])])]);t.setAttribute("toast-id",e.id),this.element.replaceChildren(t),this.container.appendChild(this.element),await X(64),t.style.marginTop=`-${t.offsetHeight}px`,await X(64),t.classList.add("show"),e.duration&&(await X(e.duration),this.hide(e.id))}async hide(e){const t=document.querySelector(`.${N}toast > [toast-id="${e}"]`);(null==t?void 0:t.classList.contains("show"))&&(t.classList.remove("show"),await X(750)),t&&t.remove()}async clearAllMessages(){let e=document.querySelector(`.${N}container`);e&&(e.innerHTML="")}async info(e,t=3e3,s=[]){this.show({id:"toast-info",icon:`mdi mdi-information ${N}theme`,content:e,duration:t})}async success(e,t=3e3){this.show({id:"toast-success",icon:`mdi mdi-check-circle ${N}success`,content:e,duration:t})}async error(e,t=3e3){this.show({id:"toast-error",icon:`mdi mdi-close-circle ${N}error`,content:e,duration:t})}async warn(e,t=3e3){this.show({id:"toast-warn",icon:`mdi mdi-alert-circle ${N}warning`,content:e,duration:t})}async showLoading(e,t=0){this.show({id:"toast-loading",icon:"mdi mdi-rotate-right loading",content:e,duration:t})}async hideLoading(){this.hide("toast-loading")}},Be=["rescale_after_model","rescale","lora_name","upscale_method","image_output","add_noise","info","sampler_name","ckpt_B_name","ckpt_C_name","save_model","refiner_ckpt_name","num_loras","num_controlnet","mode","toggle","resolution","ratio","target_parameter","input_count","replace_count","downscale_mode","range_mode","text_combine_mode","input_mode","lora_count","ckpt_count","conditioning_mode","preset","use_tiled","use_batch","num_embeds","easing_mode","guider","scheduler","inpaint_mode","t5_type","rem_mode"],ze=["LIGHT - SD1.5 only (low strength)","STANDARD (medium strength)","VIT-G (medium strength)","PLUS (high strength)","PLUS FACE (portraits)","FULL FACE - SD1.5 only (portraits stronger)"],We=["FACEID","FACEID PLUS - SD1.5 only","FACEID PLUS V2","FACEID PLUS KOLORS","FACEID PORTRAIT (style transfer)","FACEID PORTRAIT UNNORM - SDXL only (strong)"],je=["easy seed","easy latentNoisy","easy wildcards","easy preSampling","easy preSamplingAdvanced","easy preSamplingNoiseIn","easy preSamplingSdTurbo","easy preSamplingCascade","easy preSamplingDynamicCFG","easy preSamplingLayerDiffusion","easy fullkSampler","easy fullCascadeKSampler"],Ve=["easy fullLoader","easy a1111Loader","easy comfyLoader","easy hyditLoader","easy pixArtLoader"],Ye=["easy imageSize","easy imageSizeBySide","easy imageSizeByLongerSide","easy imageSizeShow","easy imageRatio","easy imagePixelPerfect"],He=["easy forLoopStart","easy forLoopEnd","easy whileLoopStart","easy whileLoopEnd"],Xe=["easy anythingIndexSwitch","easy imageIndexSwitch","easy textIndexSwitch","easy conditioningIndexSwitch"],Ze=["easy anythingInversedSwitch"],Ke=["easy loadImagesForLoop",...He,...Xe,...Ze],Je={"easy anythingInversedSwitch":"out","easy anythingIndexSwitch":"value","easy imageIndexSwitch":"image","easy textIndexSwitch":"text","easy conditioningIndexSwitch":"cond"};function $e(e,t){const s=e.comfyClass,o=t.value;switch(t.name){case"range_mode":fe(e,ge(e,"step"),"step"==o),fe(e,ge(e,"num_steps"),"num_steps"==o),_e(e);break;case"text_combine_mode":fe(e,ge(e,"replace_text"),"replace"==o);break;case"lora_name":["lora_model_strength","lora_clip_strength"].map((t=>fe(e,ge(e,t),"None"!==o)));break;case"resolution":"自定义 x 自定义"===o&&(t.value="width x height (custom)"),["empty_latent_width","empty_latent_height","width","height"].map((t=>fe(e,ge(e,t),"width x height (custom)"===o)));break;case"ratio":["empty_latent_width","empty_latent_height"].map((t=>fe(e,ge(e,t),"custom"===o)));break;case"num_loras":var n=o+1,i=ge(e,"mode").value;for(let t=0;tfe(e,ge(e,t),"simple"!==i)));for(let t=n;t<21;t++)["lora_"+t+"_name","lora_"+t+"_strength","lora_"+t+"_model_strength","lora_"+t+"_clip_strength"].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"num_controlnet":n=o+1,i=ge(e,"mode").value;for(let t=0;tfe(e,ge(e,t),!0))),["start_percent_"+t,"end_percent_"+t].map((t=>fe(e,ge(e,t),"simple"!==i)));for(let t=n;t<21;t++)["controlnet_"+t,"controlnet_"+t+"_strength","scale_soft_weight_"+t,"start_percent_"+t,"end_percent_"+t].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"mode":switch(null==e?void 0:e.comfyClass){case"easy loraStack":n=ge(e,"num_loras").value+1,i=o;for(let t=0;tfe(e,ge(e,t),"simple"!==i)));_e(e);break;case"easy controlnetStack":n=ge(e,"num_controlnet").value+1,i=o;for(let t=0;tfe(e,ge(e,t),"simple"!==i)));_e(e);break;case"easy icLightApply":i=o;["lighting","remove_bg"].map((t=>fe(e,ge(e,t),"Foreground"===i))),fe(e,ge(e,"source"),"Foreground"!==i),_e(e)}break;case"toggle":t.type="toggle",t.options={on:"Enabled",off:"Disabled"};break;case"t5_type":["clip_name","padding"].map((t=>fe(e,ge(e,t),"sd3"==o))),["t5_name","device","dtype"].map((t=>fe(e,ge(e,t),"t5v11"==o))),_e(e);break;case"preset":if(ze.includes(o)){let t=ge(e,"use_tiled");fe(e,ge(e,"lora_strength")),fe(e,ge(e,"provider")),fe(e,ge(e,"weight_faceidv2")),fe(e,ge(e,"weight_kolors")),fe(e,ge(e,"use_tiled"),!0),fe(e,ge(e,"sharpening"),t&&t.value)}else We.includes(o)&&(fe(e,ge(e,"weight_faceidv2"),!!["FACEID PLUS V2","FACEID PLUS KOLORS"].includes(o)),fe(e,ge(e,"weight_kolors"),!!["FACEID PLUS KOLORS"].includes(t.value)),["FACEID PLUS KOLORS","FACEID PORTRAIT (style transfer)","FACEID PORTRAIT UNNORM - SDXL only (strong)"].includes(o)?fe(e,ge(e,"lora_strength"),!1):fe(e,ge(e,"lora_strength"),!0),fe(e,ge(e,"provider"),!0),fe(e,ge(e,"use_tiled")),fe(e,ge(e,"sharpening")));_e(e);break;case"use_tiled":fe(e,ge(e,"sharpening"),!!o),_e(e);break;case"num_embeds":n=o+1;for(let t=0;tfe(e,ge(e,t),!1)));break;case"brushnet_random":case"brushnet_segmentation":["dtype","scale","start_at","end_at"].map((t=>fe(e,ge(e,t),!0))),["fitting","function"].map((t=>fe(e,ge(e,t),!1)));break;case"powerpaint":["dtype","fitting","function","scale","start_at","end_at"].map((t=>fe(e,ge(e,t),!0)))}_e(e);break;case"image_output":fe(e,ge(e,"link_id"),!!["Sender","Sender&Save"].includes(o)),fe(e,ge(e,"decode_vae_name"),!!["Hide","Hide&Save"].includes(o)),["save_prefix","output_path","embed_workflow","number_padding","overwrite_existing"].map((t=>fe(e,ge(e,t),!!["Save","Hide&Save","Sender&Save"].includes(o))));break;case"add_noise":var a=ge(e,"control_before_generate"),l=ge(e,"control_after_generate")||a;"disable"===o?(fe(e,ge(e,"seed")),l&&(l.last_value=l.value,l.value="fixed",fe(e,l))):(fe(e,ge(e,"seed"),!0),l&&((null==l?void 0:l.last_value)&&(l.value=l.last_value),fe(e,l,!0))),_e(e);break;case"guider":switch(o){case"Basic":["cfg_negative"].map((t=>fe(e,ge(e,t))));break;case"CFG":fe(e,ge(e,"cfg"),!0),fe(e,ge(e,"cfg_negative"));break;case"IP2P+DualCFG":case"DualCFG":["cfg","cfg_negative"].map((t=>fe(e,ge(e,t),!0)))}_e(e);break;case"scheduler":["karrasADV","exponentialADV","polyExponential"].includes(o)?(["sigma_max","sigma_min"].map((t=>fe(e,ge(e,t),!0))),["denoise","beta_d","beta_min","eps_s","coeff"].map((t=>fe(e,ge(e,t))),!1),fe(e,ge(e,"rho"),"exponentialADV"!=o)):"vp"==o?(["sigma_max","sigma_min","denoise","rho","coeff"].map((t=>fe(e,ge(e,t)))),["beta_d","beta_min","eps_s"].map((t=>fe(e,ge(e,t),!0)))):(["sigma_max","sigma_min","beta_d","beta_min","eps_s","rho"].map((t=>fe(e,ge(e,t)))),fe(e,ge(e,"coeff"),"gits"==o),fe(e,ge(e,"denoise"),!0)),_e(e);break;case"conditioning_mode":["replace","concat","combine"].includes(o)?["average_strength","old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t)))):"average"==o?(fe(e,ge(e,"average_strength"),!0),["old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t),!1)))):"timestep"==o&&(["average_strength"].map((t=>fe(e,ge(e,t),!1))),["old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t)))));break;case"rescale":ge(e,"rescale_after_model").value,fe(e,ge(e,"width"),"to Width/Height"===o),fe(e,ge(e,"height"),"to Width/Height"===o),fe(e,ge(e,"percent"),"by percentage"===o),fe(e,ge(e,"longer_side"),"to longer side - maintain aspect"===o),_e(e);break;case"upscale_method":["factor","crop"].map((t=>fe(e,ge(e,t),"None"!==o)));break;case"target_parameter":switch(s){case"easy XYInputs: Steps":["first_step","last_step"].map((t=>fe(e,ge(e,t),"steps"==o))),["first_start_step","last_start_step"].map((t=>fe(e,ge(e,t),"start_at_step"==o))),["first_end_step","last_end_step"].map((t=>fe(e,ge(e,t),"end_at_step"==o)));break;case"easy XYInputs: Sampler/Scheduler":let t=ge(e,"input_count").value+1;for(let s=0;sfe(e,ge(e,t),"strength"==o))),["first_start_percent","last_start_percent"].map((t=>fe(e,ge(e,t),"start_percent"==o))),["first_end_percent","last_end_percent"].map((t=>fe(e,ge(e,t),"end_percent"==o))),["strength","start_percent","end_percent"].map((t=>fe(e,ge(e,t),o!=t))),_e(e)}case"replace_count":n=o+1;for(let t=0;tfe(e,ge(e,t),!r)));for(let t=n;t<11;t++)["lora_name_"+t,"model_str_"+t,"clip_str_"+t].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"ckpt_count":n=o+1;var d=-1!=ge(e,"input_mode").value.indexOf("ClipSkip"),u=-1!=ge(e,"input_mode").value.indexOf("VAE");for(let t=0;tfe(e,ge(e,t),!1)));_e(e);break;case"input_count":n=o+1;var c=ge(e,"target_parameter").value;for(let t=0;tfe(e,ge(e,s),!!t)));["model_strength","clip_strength"].map((s=>fe(e,ge(e,s),!t)));break;case"easy XYInputs: Checkpoint":n=ge(e,"ckpt_count").value+1,d=-1!=ge(e,"input_mode").value.indexOf("ClipSkip"),u=-1!=ge(e,"input_mode").value.indexOf("VAE");for(let s=0;se.name===t));if(-1!==e){for(let t=e;t{var e;const t=this.computeSize();t[0]"info"===e.name));if(-1!==e&&this.widgets[e]){this.widgets[e].value=t}}requestAnimationFrame((()=>{var e;const t=this.computeSize();t[0]"prompt"==e.name));this.addWidget("button","get values from COMBO link","",(()=>{var t,o;const n=(null==(o=null==(t=this.outputs[1])?void 0:t.links)?void 0:o.length)>0?this.outputs[1].links[0]:null,i=s.graph._nodes.find((e=>{var t;return null==(t=e.inputs)?void 0:t.find((e=>e.link==n))}));if(n&&i){const t=i.inputs.find((e=>e.link==n)).widget.name,s=i.widgets.find((e=>e.name==t));let o=(null==s?void 0:s.options.values)||null;o&&(o=o.join("\n"),e.value=o)}else Ue.error(Y("No COMBO link"),3e3)}),{serialize:!1})}),Ve.includes(t.name)){let t=function(e){var t="";for(let s=0;se.name===t+"_prompt")),o="comfy-multiline-input wildcard_"+t+"_"+this.id.toString();if(-1==s&&e){const s=document.createElement("textarea");s.className=o,s.placeholder="Wildcard Prompt ("+t+")";const n=this.addDOMWidget(t+"_prompt","customtext",s,{getValue:e=>s.value,setValue(e){s.value=e},serialize:!1});n.inputEl=s,n.inputEl.readOnly=!0,s.addEventListener("input",(()=>{var e;null==(e=n.callback)||e.call(n,n.value)})),n.value=e}else if(this.widgets[s])if(e){this.widgets[s].value=e}else{this.widgets.splice(s,1);const e=document.getElementsByClassName(o);e&&e[0]&&e[0].remove()}}};e.prototype.onExecuted=function(e){null==l||l.apply(this,arguments);const o=t(e.positive),n=t(e.negative);s.call(this,o,"positive"),s.call(this,n,"negative")}}if(["easy sv3dLoader"].includes(t.name)){let t=function(e,t,s){switch(e){case"azimuth":return s.readOnly=!0,s.style.opacity=.6,"0:(0.0,0.0)"+(t>1?`\n${t-1}:(360.0,0.0)`:"");case"elevation":return s.readOnly=!0,s.style.opacity=.6,"0:(-90.0,0.0)"+(t>1?`\n${t-1}:(90.0,0.0)`:"");case"custom":return s.readOnly=!1,s.style.opacity=1,"0:(0.0,0.0)\n9:(180.0,0.0)\n20:(360.0,0.0)"}};e.prototype.onNodeCreated=async function(){i&&i.apply(this,[]);const e=this.widgets.find((e=>"easing_mode"==e.name)),s=this.widgets.find((e=>"batch_size"==e.name)),o=this.widgets.find((e=>"scheduler"==e.name));setTimeout((n=>{o.value||(o.value=t(e.value,s.value,o.inputEl))}),1),e.callback=e=>{o.value=t(e,s.value,o.inputEl)},s.callback=s=>{o.value=t(e.value,s,o.inputEl)}}}if(je.includes(o)&&(e.prototype.onNodeCreated=async function(){i&&i.apply(this,[]);const e=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),o=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));if("easy seed"==t.name){const t=this.addWidget("button","🎲 Manual Random Seed",null,(t=>{"fixed"!=o.value&&(o.value="fixed"),e.value=Math.floor(Math.random()*R),s.queuePrompt(0,1)}),{serialize:!1});e.linkedWidgets=[t,o]}},e.prototype.onAdded=async function(){n&&n.apply(this,[]);const e=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),t=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));setTimeout((s=>{"control_before_generate"==t.name&&0===e.value&&(e.value=Math.floor(Math.random()*R))}),1)}),"easy convertAnything"==o&&(e.prototype.onNodeCreated=async function(){i&&i.apply(this,[]);const e=this.widgets.find((e=>"output_type"==e.name)),t=t=>{this.outputs[0].type=e.value.toUpperCase(),this.outputs[0].name=e.value,this.outputs[0].label=e.value};setTimeout((e=>t()),10),e.callback=e=>t()}),"easy imageInsetCrop"==o){let t=function(e){const t=e.widgets[0];for(let s=1;s<=4;s++)"Pixels"===t.value?(e.widgets[s].options.step=80,e.widgets[s].options.max=8192):(e.widgets[s].options.step=10,e.widgets[s].options.max=99)};e.prototype.onAdded=async function(e){const s=this.widgets[0];let o=s.callback;s.callback=(...e)=>{t(this),o&&o.apply(s,[...e])},setTimeout((e=>{t(this)}),1)}}if(Ke.includes(o)){const t=e=>{switch(o){case"easy forLoopStart":return 0;case"easy forLoopEnd":return 1}},s=e=>{switch(o){case"easy forLoopStart":return 2;case"easy forLoopEnd":return 0}};e.prototype.onNodeCreated=async function(){if("easy loadImagesForLoop"==o&&(this.outputs[0].shape=5),He.includes(o)){const e=this.inputs.findIndex((e=>"flow"===e.name)),n=this.outputs.findIndex((e=>"flow"===e.name));if(-1!==e&&(this.inputs[e].shape=5),-1!==n&&(this.outputs[n].shape=5),"easy whileLoopStart"==o||"easy whileLoopEnd"==o)return;this.inputs=this.inputs.filter(((e,s)=>s<=t())),this.outputs=this.outputs.filter(((e,t)=>t<=s())),_e(this)}return Xe.includes(o)&&("easy textIndexSwitch"==o&&(this.widgets=this.widgets.filter(((e,t)=>t<=2))),this.inputs=this.inputs.filter(((e,t)=>t<=1)),_e(this)),null==i?void 0:i.apply(this,arguments)},e.prototype.onConnectionsChange=function(e,n,i,a){var l,r;if("easy whileLoopStart"!=o&&"easy whileLoopEnd"!=o&&a)if(1==e){let e=this.inputs.every((e=>null!==e.link)),s=this.inputs.filter((e=>!["condition","index","total"].includes(e.name)));if(He.includes(o)){if(e){if(s.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let e=s[s.length-1],t=parseInt(e.name.split("initial_value")[1])+1;if(this.inputs.find((e=>e.name==="initial_value"+t)))return;let o="initial_value"+t,n="value"+t;this.addInput(o,"*"),this.addOutput(n,"*")}else if(!i){const e=t();let s=this.inputs.findLastIndex((e=>e.link));if(n>=e&&(-1===s||n>=s)){let e=this.inputs[n];if(!e.name||["condition","total"].includes(e.name))return;let t=parseInt(e.name.split("initial_value")[1])+1,s=this.inputs.findIndex((e=>e.name==="initial_value"+t)),o=this.outputs.findIndex((e=>e.name==="value"+t));-1!==s&&this.removeInput(s),-1!==o&&this.removeOutput(o)}}}else if(Xe.includes(o))if(e){if(s.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let e=Je[o]+s.length;this.addInput(e,"*")}else i||n==this.inputs.length-2&&this.removeInput(n+1)}else if(2==e){let e=this.outputs.filter((e=>!["flow","index"].includes(e.name))),t=e.every((e=>{var t;return(null==(t=e.links)?void 0:t.length)>0}));if(He.includes(o)){if(t){if(e.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let t=e[e.length-1],s=parseInt(t.name.split("value")[1])+1;if(this.inputs.find((e=>e.name==="initial_value"+s)))return;if(this.outputs.find((e=>e.name==="value"+s)))return;let o="initial_value"+s,n="value"+s;this.addInput(o,"*"),this.addOutput(n,"*")}else if(!i){const e=s();let t=a.origin_slot,o=this.outputs.findLastIndex((e=>{var t;return(null==(t=e.links)?void 0:t.length)>0}));if(t>=e&&(-1===o||t>=o)){let e=this.outputs[t];if(!e.name||["flow","index"].includes(e.name))return;let s=parseInt(e.name.split("value")[1])+1,o=this.inputs.findIndex((e=>e.name==="initial_value"+s)),n=this.outputs.findIndex((e=>e.name==="value"+s));if(-1!==o&&(null==(l=this.inputs[o])?void 0:l.link))return;-1!==o&&this.removeInput(o),-1!==n&&this.removeOutput(n)}}}else if(Ze.includes(o))if(t){if(e.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let t=Je[o]+e.length;this.addOutput(t,"*")}else if(!i){let t=a.origin_slot;t==this.outputs.length-2&&0==(null==(r=e[t].links)?void 0:r.length)&&this.removeOutput(t+1)}}}}},nodeCreated(e){if(e.comfyClass.startsWith("easy ")){if(e.widgets)for(const s of e.widgets){if(!Be.includes(s.name))continue;let t=s.value;$e(e,s),Object.defineProperty(s,"value",{get:e=>t,set(o){o!==t&&(t=o,$e(e,s))}})}const t=e.comfyClass;if("easy preDetailerFix"==t){const t=e.widgets.find((e=>"customtext"===e.type));if(!t)return;t.dynamicPrompts=!1,t.inputEl.placeholder="wildcard spec: if kept empty, this option will be ignored",t.serializeValue=()=>t.value}if("easy wildcards"==t){const t=e.widgets.find((e=>"text"==e.name));let s=1;Object.defineProperty(e.widgets[s],"value",{set:e=>{if((new Error).stack.includes("inner_value_change")&&"Select the LoRA to add to the text"!=e){let s=e;s.endsWith(".safetensors")&&(s=s.slice(0,-12)),t.value+=``}},get:e=>"Select the LoRA to add to the text"}),Object.defineProperty(e.widgets[s+1],"value",{set:e=>{(new Error).stack.includes("inner_value_change")&&"Select the Wildcard to add to the text"!=e&&(""!=t.value&&(t.value+=", "),t.value+=e)},get:e=>"Select the Wildcard to add to the text"}),e.widgets[s].serializeValue=e=>"Select the LoRA to add to the text",e.widgets[s+1].serializeValue=e=>"Select the Wildcard to add to the text"}if(Ye.includes(t)){const t=document.createElement("textarea");t.className="comfy-multiline-input",t.readOnly=!0;const s=e.addDOMWidget("info","customtext",t,{getValue:e=>t.value,setValue:e=>t.value=e,serialize:!1});s.inputEl=t,t.addEventListener("input",(()=>{var e;null==(e=s.callback)||e.call(s,s.value)}))}}}});const qe=LiteGraph.LGraphNode;v.registerExtension({name:"easy bookmark",registerCustomNodes(){class e extends qe{constructor(){super("🔖"),g(this,"type","easy bookmark"),g(this,"title","🔖"),g(this,"slot_start_y",-20),g(this,"___collapsed_width",0),g(this,"isVirtualNode",!0),g(this,"serialize_widgets",!0),g(this,"keypressBound",null),this.addWidget("text","shortcut_key","1",(e=>{""!==(e=e.trim()[0]||"1")&&(this.title="🔖 "+e)}),{y:8}),this.addWidget("number","zoom",1,(e=>{}),{y:8+LiteGraph.NODE_WIDGET_HEIGHT+4,max:2,min:.5,precision:2}),this.keypressBound=this.onKeypress.bind(this)}get _collapsed_width(){return this.___collapsed_width}set _collapsed_width(e){const t=v.canvas,s=t.canvas.getContext("2d");if(s){const e=s.font;s.font=t.title_text_font,this.___collapsed_width=40+s.measureText(this.title).width,s.font=e}}onAdded(){setTimeout((e=>{const t=this.widgets[0].value;t&&(this.title="🔖 "+t)}),1),window.addEventListener("keydown",this.keypressBound)}onRemoved(){window.removeEventListener("keydown",this.keypressBound)}onKeypress(e){const t=e.target;["input","textarea"].includes(t.localName)||this.widgets[0]&&e.key.toLocaleLowerCase()===this.widgets[0].value.toLocaleLowerCase()&&this.canvasToBookmark()}canvasToBookmark(){var e,t;const s=v.canvas;(null==(e=null==s?void 0:s.ds)?void 0:e.offset)&&(s.ds.offset[0]=16-this.pos[0],s.ds.offset[1]=40-this.pos[1]),null!=(null==(t=null==s?void 0:s.ds)?void 0:t.scale)&&(s.ds.scale=Number(this.widgets[1].value||1)),s.setDirty(!0,!0)}}LiteGraph.registerNodeType("easy bookmark",Object.assign(e,{title:"Bookmark 🔖"})),e.category="EasyUse/Util"}}),v.registerExtension({name:"Comfy.EasyUse.ChainNode",init(){v.canvas._mousemove_callback=e=>{k("EasyUse.Nodes.ChainGetSet",null,!0)&&((e=!1,t={})=>{var s,o,n,i,a;const l=Ie();if(!l||l.length<1)return;const r=be();if(0===r.length)return;let d=t.inputX||160,u=t.ouputX||60;if(r.filter((e=>xe(e))).length>1)return;for(const p of r){let i=t.inputY||10,a=t.outputY||30;const l=[],c=p.id;if(p.graph){Ne[c]||(Ne[c]=[]);for(const e of p.inputs??[]){const t=e.link;if(!t)continue;const{origin_id:s,target_slot:o}=Se(t),n=ve(s);if(!n)continue;if(!xe(n))continue;let a=p.getConnectionPos(!0,o);Ne[c][o]||(Ne[c][o]=a),!Ne[c]||Ne[c][o][1]===a[1]&&Ne[c][o][0]===a[0]||(d=a[0]-Ne[c][o][0],i=a[1]-Ne[c][o][1],n.pos=[n.pos[0]+d,n.pos[1]+i]),Ne[c][o]=a,l.push(n)}Te[c]||(Te[c]=[]);for(const e of p.outputs??[])if(e.links&&p.graph)for(const t of e.links){const{target_id:e,target_slot:o,origin_slot:n}=Se(t),i=ve(e);if(!i)continue;if(!xe(i))continue;const r=null==(s=i.outputs)?void 0:s.links;if((null==r?void 0:r.length)>1)return;const d=p.getConnectionPos(!1,n);Te[c][n]||(Te[c][n]=d),!Te[c]||Te[c][n][0]===d[0]&&Te[c][n][1]===d[1]||(u=d[0]-Te[c][n][0],a=d[1]-Te[c][n][1],i.pos=[i.pos[0]+u,i.pos[1]+a]),Te[c][n]=d,l.push(i)}if(e&&1===r.length){const e=[p,...l];(null==(n=null==(o=p.graph)?void 0:o.list_of_graphcanvas)?void 0:n[0]).selectNodes(e)}}}const c=r[0];if(!c)return;(null==(a=null==(i=c.graph)?void 0:i.list_of_graphcanvas)?void 0:a[0]).setDirty(!0,!0)})()};const e=LGraphCanvas.prototype.showLinkMenu;LGraphCanvas.prototype.showLinkMenu=function(t,s){return s.shiftKey?(((e,t=!1)=>{var s,o,n,i,a,l,r,d,u,c;const{type:p}=e;if("*"===p)return;let{origin_id:h,target_id:m,origin_slot:g,target_slot:f}=e,y=ve(h),_=ve(m);if(!y||!_)return!1;if("Reroute"===y.type){let e=0;[y,e]=Me(y),h=null==y?void 0:y.id,g=e,void 0!==g&&-1!==g||(g=0)}if("Reroute"===_.type&&(_=Ge(_),m=null==_?void 0:_.id,f=null==_?void 0:_.inputs.findIndex((e=>e.type===p)),void 0!==f&&-1!==f||(f=0)),void 0===h||void 0===m||!y||!_)return!1;if(t&&(xe(y)||xe(_)))return!1;let v=Ce((null==(s=_.getInputInfo(f))?void 0:s.name)??p.toLowerCase());v||(v=Ce((null==(n=null==(o=null==y?void 0:y.outputs)?void 0:o[g])?void 0:n.name)??(null==(a=null==(i=null==y?void 0:y.outputs)?void 0:i[g])?void 0:a.type.toString())??v+`_from_${h}_to_${m}`));let b,w=!1,L=!1;if(xe(y))v=ye(y),L=!0;else{const e=null==(r=null==(l=y.outputs)?void 0:l[g])?void 0:r.links;if(e)for(const t of e){const e=ve((null==(d=Se(t))?void 0:d.target_id)??-1);e&&xe(e)&&ke(e)&&(v=ye(e),L=!0)}if(!L){for(const e of Ie()){if(v!==ye(e)||!ke(e))continue;const t=null==(u=e.inputs[0])?void 0:u.link;(null==(c=Se(t))?void 0:c.origin_id)===y.id?L=!0:w=!0}w&&(v+=`_from_${h}_to_${m}`)}}if(!L){b=LiteGraph.createNode("easy setNode"),b.is_auto_link=!0;const e=y.getConnectionPos(!1,g);b.pos=[e[0]+20,e[1]],b.inputs[0].name=v,b.inputs[0].type=p,b.inputs[0].widget=_.inputs[f].widget,Oe(b,v),De(b),b.flags.collapsed=!0;let t=[];y.widgets?t=Object.values(y.widgets).map((e=>e.value)):y.widgets_values&&(t=JSON.parse(JSON.stringify(y.widgets_values))),y.connect(g,b,0),y.widgets_values=t,"PrimitiveNode"===y.type&&setTimeout((()=>{if(y){y.connect(g,b,0);for(const[e,s]of t.entries())Oe(y,s,e);null!==b&&b.setSize(b.computeSize())}}))}const E=LiteGraph.createNode("easy getNode"),S=_.getConnectionPos(!0,f);E.pos=[S[0]-150,S[1]],E.outputs[0].name=v,E.outputs[0].type=p,E.outputs[0].widget=_.inputs[f].widget,De(E),Oe(E,v),null===E||(E.flags.collapsed=!0,E.setSize(E.computeSize()),E.connect(0,_,f))})(t),!1):(e.apply(this,[t,s]),!1)}}});const Qe=async()=>{try{const{Running:e,Pending:t}=await b.getQueue();if(e.length>0||t.length>0)return void Ue.error(Y("Clean Failed")+":"+Y("Please stop all running tasks before cleaning GPU"));200==(await b.fetchApi("/easyuse/cleangpu",{method:"POST"})).status?Ue.success(Y("Clean SuccessFully")):Ue.error(Y("Clean Failed"))}catch(e){}};let et=[];function tt(e,t,s,o,n){var i=LGraphCanvas.active_canvas,a=i.getCanvasWindow(),l=i.graph;if(l)return function e(t,o){var r=LiteGraph.getNodeTypesCategories(i.filter||l.filter).filter((function(e){return e.startsWith(t)})),d=[];r.map((function(s){if(s){var o=new RegExp("^("+t+")"),n=s.replace(o,"").split("/")[0],i=""===t?n+"/":t+n+"/",a=n;-1!=a.indexOf("::")&&(a=a.split("::")[1]),-1===d.findIndex((function(e){return e.value===i}))&&d.push({value:i,content:a,has_submenu:!0,callback:function(t,s,o,n){e(t.value,n)}})}})),LiteGraph.getNodeTypesInCategory(t.slice(0,-1),i.filter||l.filter).map((function(e){if(!e.skip_list){var t={value:e.type,content:e.title,has_submenu:!1,callback:function(e,t,s,o){var a=o.getFirstEvent();i.graph.beforeChange();var l=LiteGraph.createNode(e.value);l&&(l.pos=i.convertEventToCanvasOffset(a),i.graph.add(l)),n&&n(l),i.graph.afterChange()}};d.push(t)}}));const u=k("EasyUse.ContextMenu.NodesSort",null,!0);""===t&&u&&(d=function(e){let t=[],s=[];return e.forEach((e=>{(null==e?void 0:e.value)&&M.includes(e.value.split("/")[0])?t.push(e):s.push(e)})),[{title:Y("ComfyUI Basic"),is_category_title:!0},...t,{title:Y("Others A~Z"),is_category_title:!0},...s.sort(((e,t)=>e.content.localeCompare(t.content)))]}(d)),new LiteGraph.ContextMenu(d,{event:s,parentMenu:o},a)}("",o),!1}v.registerExtension({name:"Comfy.EasyUse.ContextMenu",async setup(){LGraphCanvas.onMenuAdd=tt;const e=k("EasyUse.ContextMenu.ModelsThumbnails",null,!1),t=k("EasyUse.ContextMenu.ModelsThumbnailsLimit",null,500);if(e&&t>0){const e=await b.fetchApi(`/easyuse/models/thumbnail?limit=${t}`);if(200===e.status){let t=await e.json();et=t}else Ue.error(Y("Too many thumbnails, have closed the display"))}const s=LiteGraph.ContextMenu;LiteGraph.ContextMenu=function(e,t){if(k("EasyUse.ContextMenu.SubDirectories",null,!1)&&(null==t?void 0:t.callback)&&!e.some((e=>"string"!=typeof e))){const o=function(e,t){const s=e,o=[...s],n={},i=[],a=[],l=["ckpt","pt","bin","pth","safetensors"];if((null==e?void 0:e.length)>0){const t=it(e[e.length-1]);if(!l.includes(t))return null}for(const r of s){const e=r.indexOf("/")>-1?"/":"\\",t=r.split(e);if(t.length>1){const s=t.shift();n[s]=n[s]||[],n[s].push(t.join(e))}else"CHOOSE"===r||r.startsWith("DISABLE ")?i.push(r):a.push(r)}if(Object.values(n).length>0){const e=t.callback;t.callback=null;const s=(t,s)=>{["None","无","無","なし"].includes(t.content)?e("None",s):e(o.find((e=>e.endsWith(t.content)),s))},r=(e,t="")=>{const o=t?t+"\\"+nt(e):nt(e),n=it(e),i=(new Date).getTime();let a,r="";if(l.includes(n))for(let s=0;s{let s=[],o=[];const i=e.map((e=>{const i={},a=e.indexOf("/")>-1?"/":"\\",l=e.split(a);if(l.length>1){const e=l.shift();i[e]=i[e]||[],i[e].push(l.join(a))}if(Object.values(n).length>0){let t=Object.keys(i)[0];t&&i[t]?s.push({key:t,value:i[t][0]}):o.push(r(e,t))}return r(e,t)}));if(s.length>0){let e={};return s.forEach((t=>{e[t.key]=e[t.key]||[],e[t.key].push(t.value)})),[...Object.entries(e).map((e=>({content:e[0],has_submenu:!0,callback:()=>{},submenu:{options:u(e[1],e[0])}}))),...o]}return i};for(const[t,o]of Object.entries(n))d.push({content:t,has_submenu:!0,callback:()=>{},submenu:{options:u(o,t)}});return d.push(...a.map((e=>r(e,"")))),i.length>0&&d.push(...i.map((e=>r(e,"")))),d}return null}(e,t);return o?s.call(this,o,t):s.apply(this,[...arguments])}if(t.parentMenu);else if(t.extra);else if(t.scale);else{const s=k("EasyUse.ContextMenu.QuickOptions",null,"At the forefront");if(t.hasOwnProperty("extra")&&"Disable"!==s){if("At the forefront"==s?e.unshift(null):e.push(null),o=window.location.host,["192.168.","10.","127.",/^172\.((1[6-9]|2[0-9]|3[0-1])\.)/].some((e=>"string"==typeof e?o.startsWith(e):e.test(o)))){const t={content:`${Y("Reboot ComfyUI")}`,callback:e=>(async()=>{if(confirm(Y("Are you sure you'd like to reboot the server?")))try{b.fetchApi("/easyuse/reboot")}catch(e){}})()};"At the forefront"==s?e.unshift(t):e.push(t)}const t=k("EasyUse.Hotkeys.cleanVRAMUsed",null,!0)?"("+J("Shift+r")+")":"",n={content:`${Y("Cleanup Of VRAM Usage")} ${t}`,callback:e=>Qe()};"At the forefront"==s?e.unshift(n):e.push(n);const i=k("EasyUse.Hotkeys.toggleNodesMap",null,!0)?"("+J("Shift+m")+")":"",a={content:`${Y("Nodes Map")} ${i}`,callback:e=>{var t,s,o;const n=(null==(t=v.extensionManager)?void 0:t.sidebarTab)||v.extensionManager,i=(null==(s=v.extensionManager.sidebarTab)?void 0:s.activeSidebarTabId)||(null==(o=v.extensionManager)?void 0:o.activeSidebarTab);n.activeSidebarTabId=i==P?null:P}};"At the forefront"==s?e.unshift(a):e.push(a)}}return s.apply(this,[...arguments]);var o},LiteGraph.ContextMenu.prototype=s.prototype,k("EasyUse.ContextMenu.NodesSort",null,!0)&&(LiteGraph.ContextMenu.prototype.addItem=ot)}});const st=e=>e&&"object"==typeof e&&"image"in e&&e.content;function ot(e,t,s){var o=this;s=s||{};var n=document.createElement("div");n.className="litemenu-entry submenu";var i,a=!1;function l(e){var t=this.value,n=!0;(o.current_submenu&&o.current_submenu.close(e),s.callback)&&(!0===s.callback.call(this,t,s,e,o,s.node)&&(n=!1));if(t){if(t.callback&&!s.ignore_item_callbacks&&!0!==t.disabled)!0===t.callback.call(this,t,s,e,o,s.extra)&&(n=!1);if(t.submenu){if(!t.submenu.options)throw"ContextMenu submenu needs options";new o.constructor(t.submenu.options,{callback:t.submenu.callback,event:e,parentMenu:o,ignore_item_callbacks:t.submenu.ignore_item_callbacks,title:t.submenu.title,extra:t.submenu.extra,autoopen:s.autoopen}),n=!1}}n&&!o.lock&&o.close()}return null===t?n.classList.add("separator"):t.is_category_title?(n.classList.remove("litemenu-entry"),n.classList.remove("submenu"),n.classList.add("litemenu-title"),n.innerHTML=t.title):(n.innerHTML=t&&t.title?t.title:e,n.value=t,t&&(t.disabled&&(a=!0,n.classList.add("disabled")),(t.submenu||t.has_submenu)&&n.classList.add("has_submenu")),"function"==typeof t?(n.dataset.value=e,n.onclick_callback=t):n.dataset.value=t,t.className&&(n.className+=" "+t.className)),n&&st(t)&&(null==t?void 0:t.image)&&!t.submenu&&(n.textContent+=" *",w("div.pysssss-combo-image",{parent:n,style:{backgroundImage:`url(/pysssss/view/${i=t.image,encodeURIComponent(i).replace(/[!'()*]/g,(e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`))})`}})),this.root.appendChild(n),a||n.addEventListener("click",l),!a&&s.autoopen&&LiteGraph.pointerListenerAdd(n,"enter",(function(e){var t=this.value;if(!t||!t.has_submenu)return;l.call(this,e)})),n}function nt(e){return null==e?void 0:e.substring(0,e.lastIndexOf("."))}function it(e){return null==e?void 0:e.substring(e.lastIndexOf(".")+1)}class at extends L{constructor(){super(),this.element.classList.add("easyuse-model-metadata")}show(e){super.show(w("div",Object.keys(e).map((t=>w("div",[w("label",{textContent:t}),w("span",{textContent:e[t]})])))))}}class lt extends L{constructor(e){super(),this.name=e,this.element.classList.add("easyuse-model-info")}get customNotes(){return this.metadata["easyuse.notes"]}set customNotes(e){this.metadata["easyuse.notes"]=e}get hash(){return this.metadata["easyuse.sha256"]}async show(e,t){this.type=e;const s=b.fetchApi("/easyuse/metadata/"+encodeURIComponent(`${e}/${t}`));this.info=w("div",{style:{flex:"auto"}}),this.imgCurrent=0,this.imgList=w("div.easyuse-preview-list",{style:{display:"none"}}),this.imgWrapper=w("div.easyuse-preview",[w("div.easyuse-preview-group",[this.imgList])]),this.main=w("main",{style:{display:"flex"}},[this.imgWrapper,this.info]),this.content=w("div.easyuse-model-content",[w("div.easyuse-model-header",[w("h2",{textContent:this.name})]),this.main]);const o=w("div",{textContent:"ℹ️ Loading...",parent:this.content});super.show(this.content),this.metadata=await(await s).json(),this.viewMetadata.style.cursor=this.viewMetadata.style.opacity="",this.viewMetadata.removeAttribute("disabled"),o.remove(),this.addInfo()}createButtons(){const e=super.createButtons();return this.viewMetadata=w("button",{type:"button",textContent:"View raw metadata",disabled:"disabled",style:{opacity:.5,cursor:"not-allowed"},onclick:e=>{this.metadata&&(new at).show(this.metadata)}}),e.unshift(this.viewMetadata),e}parseNote(){if(!this.customNotes)return[];let e=[];const t=new RegExp("(\\bhttps?:\\/\\/[^\\s]+)","g");let s,o=0;do{let n;s=t.exec(this.customNotes);let i=0;s?(n=s.index,i=s.index+s[0].length):n=this.customNotes.length;let a=this.customNotes.substring(o,n);a&&(a=a.replaceAll("\n","
"),e.push(w("span",{innerHTML:a}))),s&&e.push(w("a",{href:s[0],textContent:s[0],target:"_blank"})),o=i}while(s);return e}addInfoEntry(e,t){return w("p",{parent:this.info},["string"==typeof e?w("label",{textContent:e+": "}):e,"string"==typeof t?w("span",{textContent:t}):t])}async getCivitaiDetails(){const e=await fetch("https://civitai.com/api/v1/model-versions/by-hash/"+this.hash);if(200===e.status)return await e.json();throw 404===e.status?new Error("Model not found"):new Error(`Error loading info (${e.status}) ${e.statusText}`)}addCivitaiInfo(){const e=this.getCivitaiDetails(),t=w("span",{textContent:"ℹ️ Loading..."});return this.addInfoEntry(w("label",[w("img",{style:{width:"18px",position:"relative",top:"3px",margin:"0 5px 0 0"},src:"https://civitai.com/favicon.ico"}),w("span",{textContent:"Civitai: "})]),t),e.then((e=>{var t,s;this.imgWrapper.style.display="block";let o=this.element.querySelector(".easyuse-model-header");o&&o.replaceChildren(w("h2",{textContent:this.name}),w("div.easyuse-model-header-remark",[w("h5",{textContent:Y("Updated At:")+Z(new Date(e.updatedAt),"yyyy/MM/dd")}),w("h5",{textContent:Y("Created At:")+Z(new Date(e.updatedAt),"yyyy/MM/dd")})]));let n=null,i=this.parseNote.call(this),a=Y("✏️ Edit"),l=w("div.easyuse-model-detail-textarea",[w("p",(null==i?void 0:i.length)>0?i:{textContent:Y("No notes")})]);if(i&&0!=i.length?l.classList.remove("empty"):l.classList.add("empty"),this.info.replaceChildren(w("div.easyuse-model-detail",[w("div.easyuse-model-detail-head.flex-b",[w("span",Y("Notes")),w("a",{textContent:a,href:"#",style:{fontSize:"12px",float:"right",color:"var(--warning-color)",textDecoration:"none"},onclick:async e=>{if(e.preventDefault(),n){if(n.value!=this.customNotes){Ue.showLoading(Y("Saving Notes...")),this.customNotes=n.value;const e=await b.fetchApi("/easyuse/metadata/notes/"+encodeURIComponent(`${this.type}/${this.name}`),{method:"POST",body:this.customNotes});if(Ue.hideLoading(),200!==e.status)return Ue.error(Y("Saving Failed")),void alert(`Error saving notes (${e.status}) ${e.statusText}`);Ue.success(Y("Saving Succeed")),i=this.parseNote.call(this),l.replaceChildren(w("p",(null==i?void 0:i.length)>0?i:{textContent:Y("No notes")})),n.value?l.classList.remove("empty"):l.classList.add("empty")}else l.replaceChildren(w("p",{textContent:Y("No notes")})),l.classList.add("empty");e.target.textContent=a,n.remove(),n=null}else e.target.textContent="💾 Save",n=w("textarea",{placeholder:Y("Type your notes here"),style:{width:"100%",minWidth:"200px",minHeight:"50px",height:"100px"},textContent:this.customNotes}),l.replaceChildren(n),n.focus()}})]),l]),w("div.easyuse-model-detail",[w("div.easyuse-model-detail-head",{textContent:Y("Details")}),w("div.easyuse-model-detail-body",[w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Type")}),w("div.easyuse-model-detail-item-value",{textContent:e.model.type})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("BaseModel")}),w("div.easyuse-model-detail-item-value",{textContent:e.baseModel})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Download")}),w("div.easyuse-model-detail-item-value",{textContent:(null==(t=e.stats)?void 0:t.downloadCount)||0})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Trained Words")}),w("div.easyuse-model-detail-item-value",{textContent:(null==e?void 0:e.trainedWords.join(","))||"-"})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Source")}),w("div.easyuse-model-detail-item-value",[w("label",[w("img",{style:{width:"14px",position:"relative",top:"3px",margin:"0 5px 0 0"},src:"https://civitai.com/favicon.ico"}),w("a",{href:"https://civitai.com/models/"+e.modelId,textContent:"View "+e.model.name,target:"_blank"})])])])])])),null==(s=e.images)?void 0:s.length){this.imgCurrent=0,this.isSaving=!1,e.images.map((e=>e.url&&this.imgList.appendChild(w("div.easyuse-preview-slide",[w("div.easyuse-preview-slide-content",[w("img",{src:e.url}),w("div.save",{textContent:"Save as preview",onclick:async()=>{if(this.isSaving)return;this.isSaving=!0,Ue.showLoading(Y("Saving Preview..."));const t=await(await fetch(e.url)).blob(),s="temp_preview."+new URL(e.url).pathname.split(".")[1],o=new FormData;o.append("image",new File([t],s)),o.append("overwrite","true"),o.append("type","temp");if(200!==(await b.fetchApi("/upload/image",{method:"POST",body:o})).status)return this.isSaving=!1,Ue.error(Y("Saving Failed")),Ue.hideLoading(),void alert(`Error saving preview (${req.status}) ${req.statusText}`);await b.fetchApi("/easyuse/save/"+encodeURIComponent(`${this.type}/${this.name}`),{method:"POST",body:JSON.stringify({filename:s,type:"temp"}),headers:{"content-type":"application/json"}}).then((e=>{Ue.success(Y("Saving Succeed")),Ue.hideLoading()})),this.isSaving=!1,app.refreshComboInNodes()}})])]))));let t=this;this.imgDistance=(-660*this.imgCurrent).toString(),this.imgList.style.display="",this.imgList.style.transform="translate3d("+this.imgDistance+"px, 0px, 0px)",this.slides=this.imgList.querySelectorAll(".easyuse-preview-slide"),this.slideLeftButton=w("button.left",{parent:this.imgWrapper,style:{display:e.images.length<=2?"none":"block"},innerHTML:'',onclick:()=>{e.images.length<=2||(t.imgList.classList.remove("no-transition"),0==t.imgCurrent?(t.imgCurrent=e.images.length/2-1,this.slides[this.slides.length-1].style.transform="translate3d("+(-660*(this.imgCurrent+1)).toString()+"px, 0px, 0px)",this.slides[this.slides.length-2].style.transform="translate3d("+(-660*(this.imgCurrent+1)).toString()+"px, 0px, 0px)",t.imgList.style.transform="translate3d(660px, 0px, 0px)",setTimeout((e=>{this.slides[this.slides.length-1].style.transform="translate3d(0px, 0px, 0px)",this.slides[this.slides.length-2].style.transform="translate3d(0px, 0px, 0px)",t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)",t.imgList.classList.add("no-transition")}),500)):(t.imgCurrent=t.imgCurrent-1,t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)"))}}),this.slideRightButton=w("button.right",{parent:this.imgWrapper,style:{display:e.images.length<=2?"none":"block"},innerHTML:'',onclick:()=>{if(!(e.images.length<=2))if(t.imgList.classList.remove("no-transition"),t.imgCurrent>=e.images.length/2-1){t.imgCurrent=0;const s=e.images.length/2;this.slides[0].style.transform="translate3d("+(660*s).toString()+"px, 0px, 0px)",this.slides[1].style.transform="translate3d("+(660*s).toString()+"px, 0px, 0px)",t.imgList.style.transform="translate3d("+(-660*s).toString()+"px, 0px, 0px)",setTimeout((e=>{this.slides[0].style.transform="translate3d(0px, 0px, 0px)",this.slides[1].style.transform="translate3d(0px, 0px, 0px)",t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)",t.imgList.classList.add("no-transition")}),500)}else t.imgCurrent=t.imgCurrent+1,t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)"}})}return e.description&&w("div",{parent:this.content,innerHTML:e.description,style:{marginTop:"10px"}}),e})).catch((e=>{this.imgWrapper.style.display="none",t.textContent="⚠️ "+e.message})).finally((e=>{}))}}class rt extends lt{async addInfo(){await this.addCivitaiInfo()}}class dt extends lt{getTagFrequency(){if(!this.metadata.ss_tag_frequency)return[];const e=JSON.parse(this.metadata.ss_tag_frequency),t={};for(const s in e){const o=e[s];for(const e in o)e in t?t[e]+=o[e]:t[e]=o[e]}return Object.entries(t).sort(((e,t)=>t[1]-e[1]))}getResolutions(){let e=[];if(this.metadata.ss_bucket_info){const t=JSON.parse(this.metadata.ss_bucket_info);if(null==t?void 0:t.buckets)for(const{resolution:s,count:o}of Object.values(t.buckets))e.push([o,`${s.join("x")} * ${o}`])}e=e.sort(((e,t)=>t[0]-e[0])).map((e=>e[1]));let t=this.metadata.ss_resolution;if(t){const s=t.split(","),o=s[0].replace("(",""),n=s[1].replace(")","");e.push(`${o.trim()}x${n.trim()} (Base res)`)}else(t=this.metadata["modelspec.resolution"])&&e.push(t+" (Base res");return e.length||e.push("⚠️ Unknown"),e}getTagList(e){return e.map((e=>w("li.easyuse-model-tag",{dataset:{tag:e[0]},$:e=>{e.onclick=()=>{e.classList.toggle("easyuse-model-tag--selected")}}},[w("p",{textContent:e[0]}),w("span",{textContent:e[1]})])))}addTags(){let e,t=this.getTagFrequency();if(null==t?void 0:t.length){const s=t.length;let o;s>500&&(t=t.slice(0,500),e=w("p",[w("span",{textContent:"⚠️ Only showing first 500 tags "}),w("a",{href:"#",textContent:`Show all ${s}`,onclick:()=>{o.replaceChildren(...this.getTagList(this.getTagFrequency())),e.remove()}})])),o=w("ol.easyuse-model-tags-list",this.getTagList(t)),this.tags=w("div",[o])}else this.tags=w("p",{textContent:"⚠️ No tag frequency metadata found"});this.content.append(this.tags),e&&this.content.append(e)}async addInfo(){const e=this.addCivitaiInfo();this.addTags();const t=await e;t&&w("div",{parent:this.content,innerHTML:t.description,style:{maxHeight:"250px",overflow:"auto"}})}createButtons(){const e=super.createButtons();function t(e,t){const s=w("textarea",{parent:document.body,style:{position:"fixed"},textContent:t.map((e=>e.dataset.tag)).join(", ")});s.select();try{document.execCommand("copy"),e.target.dataset.text||(e.target.dataset.text=e.target.textContent),e.target.textContent="Copied "+t.length+" tags",setTimeout((()=>{e.target.textContent=e.target.dataset.text}),1e3)}catch(o){prompt("Copy to clipboard: Ctrl+C, Enter",text)}finally{document.body.removeChild(s)}}return e.unshift(w("button",{type:"button",textContent:"Copy Selected",onclick:e=>{t(e,[...this.tags.querySelectorAll(".easyuse-model-tag--selected")])}}),w("button",{type:"button",textContent:"Copy All",onclick:e=>{t(e,[...this.tags.querySelectorAll(".easyuse-model-tag")])}})),e}}const ut={pipe:{category:"Easy Pipe",nodes:["easy pipeIn","easy pipeOut","easy pipeEdit","easy pipeEditPrompt","easy pipeBatchIndex"],input:{pipe:"pipe"},output:{pipe:"pipe"},widget:{optional_positive:"optional_positive",optional_negative:"optional_negative"}},loaders:{category:"Easy Loaders",nodes:["easy fullLoader","easy a1111Loader","easy comfyLoader","easy kolorsLoader","easy hunyuanDiTLoader","easy pixArtLoader","easy fluxLoader"],input:{optional_lora_stack:"optional_lora_stack",optional_controlnet_stack:"optional_controlnet_stack",positive:"positive",negative:"negative"},output:{pipe:"pipe",model:"model",vae:"vae",clip:null,positive:null,negative:null,latent:null},widget:{ckpt_name:"ckpt_name",vae_name:"vae_name",clip_skip:"clip_skip",lora_name:"lora_name",resolution:"resolution",empty_latent_width:"empty_latent_width",empty_latent_height:"empty_latent_height",positive:"positive",negative:"negative",batch_size:"batch_size",a1111_prompt_style:"a1111_prompt_style"}},preSampling:{category:"Easy PreSampling",nodes:["easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG","easy preSamplingNoiseIn","easy preSamplingCustom","easy preSamplingLayerDiffusion","easy fullkSampler"],input:{pipe:"pipe",image_to_latent:"image_to_latent",latent:"latent"},output:{pipe:"pipe"},widget:{steps:"steps",cfg:"cfg",cfg_scale_min:"cfg",sampler_name:"sampler_name",scheduler:"scheduler",denoise:"denoise",seed_num:"seed_num",seed:"seed"}},samplers:{category:"Custom Sampler",nodes:["KSamplerSelect","SamplerEulerAncestral","SamplerEulerAncestralCFG++","SamplerLMS","SamplerDPMPP_3M_SDE","SamplerDPMPP_2M_SDE","SamplerDPMPP_SDE","SamplerDPMAdaptative","SamplerLCMUpscale","SamplerTCD","SamplerTCD EulerA"],output:{SAMPLER:"SAMPLER"}},sigmas:{category:"Custom Sigmas",nodes:["BasicScheduler","KarrasScheduler","ExponentialScheduler","PolyexponentialScheduler","VPScheduler","BetaSamplingScheduler","SDTurboScheduler","SplitSigmas","SplitSigmasDenoise","FlipSigmas","AlignYourStepsScheduler","GITSScheduler"],output:{SIGMAS:"SIGMAS"}},kSampler:{category:"Easy kSampler",nodes:["easy kSampler","easy kSamplerTiled","easy kSamplerCustom","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerLayerDiffusion"],input:{pipe:"pipe",model:"model"},output:{pipe:"pipe",image:"image"},widget:{image_output:"image_output",save_prefix:"save_prefix",link_id:"link_id"}},controlNet:{category:"Easy ControlNet",nodes:["easy controlnetLoader","easy controlnetLoaderADV","easy controlnetLoader++","easy instantIDApply","easy instantIDApplyADV"],input:{pipe:"pipe",image:"image",image_kps:"image_kps",control_net:"control_net",positive:"positive",negative:"negative",mask:"mask"},output:{pipe:"pipe",positive:"positive",negative:"negative"},widget:{control_net_name:"control_net_name",strength:["strength","cn_strength"],scale_soft_weights:["scale_soft_weights","cn_soft_weights"],cn_strength:["strength","cn_strength"],cn_soft_weights:["scale_soft_weights","cn_soft_weights"]}},adapter:{category:"Easy Adapter",nodes:["easy ipadapterApply","easy ipadapterApplyADV","easy ipadapterApplyFaceIDKolors","easy ipadapterStyleComposition","easy ipadapterApplyFromParams","easy pulIDApply","easy pulIDApplyADV"],input:{model:"model",image:"image",image_style:"image",attn_mask:"attn_mask",optional_ipadapter:"optional_ipadapter"},output:{model:"model",tiles:"tiles",masks:"masks",ipadapter:"ipadapter"},widget:{preset:"preset",lora_strength:"lora_strength",provider:"provider",weight:"weight",weight_faceidv2:"weight_faceidv2",start_at:"start_at",end_at:"end_at",cache_mode:"cache_mode",use_tiled:"use_tiled",insightface:"insightface",pulid_file:"pulid_file"}},positive:{category:"Easy Positive",nodes:["easy positive","easy wildcards"],input:{},output:{text:"positive",positive:"text"},widget:{text:"positive",positive:"text"}},loadImage:{category:"Easy LoadImage",nodes:["easy loadImageBase64","LoadImage","LoadImageMask"],input:{pipe:"pipe",image:"image",mask:"mask"},output:{IMAGE:"IMAGE",MASK:"MASK"},widget:{image:"image",base64_data:"base64_data",channel:"channel"}},saveImage:{category:"Save/Preview Image",nodes:["SaveImage","PreviewImage"]},inPaint:{category:"Easy Inpaint",nodes:["easy applyBrushNet","easy applyPowerPaint","easy applyInpaint"],input:{},output:{pipe:"pipe"},widget:{dtype:"dtype",fitting:"fitting",function:"function",scale:"scale",start_at:"start_at",end_at:"end_at"}},showAny:{category:"Show Anything",nodes:["easy showAnything","easy showAnythingLazy"],input:{anything:"anything"},output:{output:"output"}},saveText:{category:"Save Text",nodes:["easy saveText","easy saveTextLazy"],input:{image:"image",text:"text",output_file_path:"output_file_path",file_name:"file_name",file_extension:"file_extension",overwrite:"overwrite"},output:{text:"text",image:"image"},widget:{image:"image",text:"text",output_file_path:"output_file_path",file_name:"file_name",file_extension:"file_extension",overwrite:"overwrite"}},persona:{category:"LLM Party Persona",nodes:["load_persona","classify_persona","classify_persona_plus","custom_persona","translate_persona","flux_persona"],input:{file_content:"file_content"},output:{system_prompt:"system_prompt"},widget:{is_enable:"is_enable"}},llmModelLoader:{category:"LLM Model Loader",nodes:["LLM_api_loader","genai_api_loader","LLM_local_loader"],output:{model:"model"}},llmModelChain:{category:"LLM Model Chain",nodes:["LLM","LLM_local"],input:{model:"model",image:"images",images:"image",extra_parameters:"extra_parameters",system_prompt_input:"system_prompt_input",user_prompt_input:"user_prompt_input",tools:"tools",file_content:"file_content"},output:{assistant_response:"assistant_response",history:"history",tool:"tool",image:"image"},widget:{system_prompt:"system_prompt",user_prompt:"user_prompt",temperature:"temperature",is_memory:"is_memory",is_tools_in_sys_prompt:"is_tools_in_sys_prompt",max_length:"max_length",main_brain:"main_brain",conversation_rounds:"conversation_rounds",history_record:"history_record",is_enable:"is_enable"}},maskModify:{category:"Mask Modify",nodes:["CropMask","ThresholdMask","GrowMask","FeatherMask","LayerMask: MaskGrain","LayerMask: MaskEdgeUltraDetail","LayerMask: MaskEdgeUltraDetail V2"],input:{mask:"mask"},output:{MASK:"MASK",mask:"mask",image:"image"}},maskModifyWAS:{category:"Mask Modify (WAS)",nodes:["Mask Dilate Region","Mask Gaussian Region"],input:{masks:"masks"},output:{MASKS:"MASKS"}}};function ct(e,t,s){return function(){!function(e,t,s){var o;const n=LiteGraph.createNode(t);if(n){if(v.graph.add(n),n.pos=e.pos.slice(),n.size=e.size.slice(),(null==(o=e.widgets)?void 0:o.length)>0&&e.widgets.forEach((e=>{var t,o,i;if(null==(o=null==(t=ut[s])?void 0:t.widget)?void 0:o[e.name]){const t=ut[s].widget[e.name];if(t&&n.widgets){const s=(i=t,n.widgets.find((e=>"object"==typeof i?i.includes(e.name):e.name===i)));s&&(s.value=e.value,"seed_num"==e.name&&(s.linkedWidgets[0].value=e.linkedWidgets[0].value),"converted-widget"==e.type&&_t(n,s,e))}}})),e.inputs&&e.inputs.forEach(((t,o)=>{var i,a,l;if(t&&t.link&&(null==(a=null==(i=ut[s])?void 0:i.input)?void 0:a[t.name])){const o=null==(l=ut[s])?void 0:l.input[t.name];if(null===o)return;const i=n.findInputSlot(o);if(-1!==i){const s=e.graph.links[t.link];if(s){const t=e.graph.getNodeById(s.origin_id);t&&t.connect(s.origin_slot,n,i)}}}})),e.outputs&&e.outputs.forEach(((t,o)=>{var i,a;if(t&&t.links&&(null==(a=null==(i=ut[s])?void 0:i.output)?void 0:a[t.name])){const o=ut[s].output[t.name];if(null===o)return;const i=n.findOutputSlot(o);-1!==i&&t.links.forEach((t=>{const s=e.graph.links[t];if(s){const t=e.graph.getNodeById(s.target_id);t&&n.connect(i,t,s.target_slot)}}))}})),v.graph.remove(e),"easy fullkSampler"==n.type){const e=n.outputs[0].links;if(e&&e[0]){const t=v.graph._nodes.find((t=>t.inputs&&t.inputs[0]&&t.inputs[0].link==e[0]));t&&v.graph.remove(t)}}else if(ut.preSampling.nodes.includes(n.type)){const e=n.outputs[0].links;if(!e||!e[0]){const e=LiteGraph.createNode("easy kSampler");v.graph.add(e),e.pos=n.pos.slice(),e.pos[0]=e.pos[0]+n.size[0]+20;const t=n.findInputSlot("pipe");-1!==t&&n&&n.connect(0,e,t)}}n.setSize([n.size[0],n.computeSize()[1]])}}(e,t,s)}}const pt=(e,t)=>{const s=e.prototype.getExtraMenuOptions;e.prototype.getExtraMenuOptions=function(){const e=s.apply(this,arguments);return t.apply(this,arguments),e}},ht=(e,t,s,o,n=!0)=>{pt(o,(function(o,i){i.unshift({content:e,has_submenu:n,callback:(e,o,n,i,a)=>mt(e,o,n,i,a,t,s)}),"loaders"==t&&(i.unshift({content:Y("💎 View Lora Info..."),callback:(e,t,s,o,n)=>{let i=n.widgets.find((e=>"lora_name"==e.name)).value;i&&"None"!=i&&new dt(i).show("loras",i)}}),i.unshift({content:Y("💎 View Checkpoint Info..."),callback:(e,t,s,o,n)=>{let i=n.widgets[0].value;i&&"None"!=i&&new rt(i).show("checkpoints",i)}}))}))},mt=(e,t,s,o,n,i,a)=>{const l=[];return a.map((e=>{n.type!==e&&l.push({content:`${e}`,callback:ct(n,e,i)})})),new LiteGraph.ContextMenu(l,{event:s,callback:null,parentMenu:o,node:n}),!1},gt="converted-widget",ft=Symbol();function yt(e,t,s=""){if(t.origType=t.type,t.origComputeSize=t.computeSize,t.origSerializeValue=t.serializeValue,t.computeSize=()=>[0,-4],t.type=gt+s,t.serializeValue=()=>{if(!e.inputs)return;let s=e.inputs.find((e=>{var s;return(null==(s=e.widget)?void 0:s.name)===t.name}));return s&&s.link?t.origSerializeValue?t.origSerializeValue():t.value:void 0},t.linkedWidgets)for(const o of t.linkedWidgets)yt(e,o,":"+t.name)}function _t(e,t,s){yt(e,t);const{type:o}=function(e){let t=e[0];t instanceof Array&&(t="COMBO");return{type:t}}(s),n=e.size;t.options&&t.options.forceInput||e.addInput(t.name,o,{widget:{name:t.name,[ft]:()=>s}});for(const i of e.widgets)i.last_y+=LiteGraph.NODE_SLOT_HEIGHT;e.setSize([Math.max(n[0],e.size[0]),Math.max(n[1],e.size[1])])}const vt=function(e){var t,s,o,n;const i=e.constructor.type,a=e.properties.origVals||{},l=a.title||e.title,r=a.color||e.color,d=a.bgcolor||e.bgcolor,u=e,c={size:[...e.size],color:r,bgcolor:d,pos:[...e.pos]};let p=[],h=[];if(e.inputs)for(const y of e.inputs)if(y.link){const t=y.name,s=e.findInputSlot(t),o=e.getInputNode(s),n=e.getInputLink(s);p.push([n.origin_slot,o,t])}if(e.outputs)for(const y of e.outputs)if(y.links){const e=y.name;for(const t of y.links){const s=graph.links[t],o=graph._nodes_by_id[s.target_id];h.push([e,o,s.target_slot])}}v.graph.remove(e);let m=v.graph.add(LiteGraph.createNode(i,l,c));function g(){if(u.widgets)for(let e of u.widgets)if("converted-widget"===e.type){const t=m.widgets.find((t=>t.name===e.name));for(let s of u.inputs)s.name===e.name&&_t(m,t,s.widget)}for(let e of p){const[t,s,o]=e;s.connect(t,m.id,o)}for(let e of h){const[t,s,o]=e;m.connect(t,s,o)}}m.inputs.map(((t,s)=>{m.inputs[s].label=e.inputs[s].label})),m.outputs.map(((t,s)=>{m.outputs[s].label=e.outputs[s].label}));let f=u.widgets_values;if(!f&&(null==(t=m.widgets)?void 0:t.length)>0)return m.widgets.forEach(((e,t)=>{const s=u.widgets[t];e.name===s.name&&e.type===s.type&&(e.value=s.value)})),void g();if(f){let e=function(e,t){var s,o,n,i,a,l;if(!0===e||!1===e){if((null==(s=t.options)?void 0:s.on)&&(null==(o=t.options)?void 0:o.off))return{value:e,pass:!0}}else if("number"==typeof e){if((null==(n=t.options)?void 0:n.min)<=e&&e<=(null==(i=t.options)?void 0:i.max))return{value:e,pass:!0}}else{if(null==(l=null==(a=t.options)?void 0:a.values)?void 0:l.includes(e))return{value:e,pass:!0};if(t.inputEl&&"string"==typeof e)return{value:e,pass:!0}}return{value:t.value,pass:!1}},t=!1;const i=(null==f?void 0:f.length)<=(null==(s=m.widgets)?void 0:s.length);let a=i?0:f.length-1;const l=s=>{var o;const n=u.widgets[s];let l=m.widgets[s];if(l.name===n.name&&l.type===n.type){for(;(i?a=0)&&!t;){let{value:t,pass:s}=e(f[a],l);if(s&&null!==t){l.value=t;break}a+=i?1:-1}a++,i||(a=f.length-((null==(o=m.widgets)?void 0:o.length)-1-s))}};if(i&&(null==(o=m.widgets)?void 0:o.length)>0)for(let s=0;s0)for(let s=m.widgets.length-1;s>=0;s--)l(s)}g()};v.registerExtension({name:"Comfy.EasyUse.ExtraMenu",async beforeRegisterNodeDef(e,t,s){pt(e,(function(e,s){s.unshift({content:Y("🔃 Reload Node"),callback:(e,t,s,o,n)=>{let i=LGraphCanvas.active_canvas;if(!i.selected_nodes||Object.keys(i.selected_nodes).length<=1)vt(n);else for(let a in i.selected_nodes)vt(i.selected_nodes[a])}}),"easy ckptNames"==t.name&&s.unshift({content:Y("💎 View Checkpoint Info..."),callback:(e,t,s,o,n)=>{n.widgets[0].value}})}));for(const o in ut)ut[o].nodes.includes(t.name)&&ht(`↪️ Swap ${ut[o].category}`,o,ut[o].nodes,e)}});const bt=LiteGraph.LGraphNode,wt="➡️";v.registerExtension({name:"easy setNode",registerCustomNodes(){class e extends bt{constructor(t){super("Set"),g(this,"defaultVisibility",!0),g(this,"serialize_widgets",!0),this.properties||(this.properties={previousName:""}),this.properties.showOutputText=e.defaultVisibility;const s=this;s.color=LGraphCanvas.node_colors.blue.color,this.addWidget("text","Constant","",((e,t,o,n,i)=>{s.validateName(s.graph),""!==this.widgets[0].value&&(this.title=wt+this.widgets[0].value),this.properties.previousName=this.widgets[0].value,this.update()}),{}),this.addInput("*","*"),this.onConnectionsChange=function(e,t,o,n,i){if(1!=e||o||(this.inputs[t].type="*",this.inputs[t].name="*",this.title="Set"),n&&s.graph&&1==e&&o){const e=s.graph._nodes.find((e=>e.id==n.origin_id)).outputs[n.origin_slot],t=e.type,o=s.is_auto_link?this.widgets[0].value:e.name;"Set"===this.title&&(this.title=wt+o,this.widgets[0].value=o),"*"===this.widgets[0].value&&(this.widgets[0].value=o),this.validateName(s.graph),this.inputs[0].type=t,this.inputs[0].name=o,setTimeout((e=>{this.title=wt+this.widgets[0].value,this.properties.previousName=this.widgets[0].value}),1)}this.update()},this.validateName=function(e){let t=s.widgets[0].value;if(""!=t){let o=0,n=[];do{n=e._nodes.filter((e=>e!=this&&("easy setNode"==e.type&&e.widgets[0].value===t))),n.length>0&&(t=s.widgets[0].value+o),o++}while(n.length>0);s.widgets[0].value=t,this.update()}},this.clone=function(){const t=e.prototype.clone.apply(this);return t.inputs[0].name="*",t.inputs[0].type="*",t.properties.previousName="",t.size=t.computeSize(),t},this.onAdded=function(e){this.validateName(e)},this.update=function(){if(s.graph){this.findGetters(s.graph).forEach((e=>{e.setType(this.inputs[0].type)})),this.widgets[0].value&&this.findGetters(s.graph,!0).forEach((e=>{e.setName(this.widgets[0].value)}));s.graph._nodes.filter((e=>"easy getNode"==e.type)).forEach((e=>{e.setComboValues&&e.setComboValues()}))}},this.findGetters=function(e,t){const s=t?this.properties.previousName:this.widgets[0].value;return e._nodes.filter((e=>"easy getNode"==e.type&&e.widgets[0].value===s&&""!=s))},this.isVirtualNode=!0}onRemoved(){this.graph._nodes.filter((e=>"easy getNode"==e.type)).forEach((e=>{e.setComboValues&&e.setComboValues([this])}))}}LiteGraph.registerNodeType("easy setNode",Object.assign(e,{title:"Set"})),e.category="EasyUse/Util"}}),v.registerExtension({name:"easy getNode",registerCustomNodes(){class e extends bt{constructor(t){super("Get"),g(this,"defaultVisibility",!0),g(this,"serialize_widgets",!0),this.properties||(this.properties={}),this.properties.showOutputText=e.defaultVisibility;const s=this;s.color=LGraphCanvas.node_colors.blue.color,this.addWidget("combo","Constant","",(e=>{this.onRename()}),{values:()=>s.graph._nodes.filter((e=>"easy setNode"==e.type)).map((e=>e.widgets[0].value)).sort()}),this.addOutput("*","*"),this.onConnectionsChange=function(e,t,s,o,n){this.validateLinks(),2!=e||s?(this.onRename(),setTimeout((e=>{this.title="⬅️"+this.widgets[0].value}),1)):(this.outputs[t].type="*",this.outputs[t].name="*",this.title="Get")},this.setName=function(e){s.widgets[0].value=e,s.onRename(),s.serialize()},this.onRename=function(e=0){const t=this.findSetter(s.graph);if(t){const s=t.inputs[0].type,o=t.inputs[0].name;this.setType(s,o),this.outputs[e].type=s,this.outputs[e].name=o,this.title="⬅️"+t.widgets[0].value}else this.setType("*","*"),this.outputs[e].type="*",this.outputs[e].name="*"},this.clone=function(){const t=e.prototype.clone.apply(this);return t.size=t.computeSize(),t},this.validateLinks=function(){"*"!=this.outputs[0].type&&this.outputs[0].links&&this.outputs[0].links.forEach((e=>{const t=s.graph.links[e];t&&t.type!=this.outputs[0].type&&"*"!=t.type&&s.graph.removeLink(e)}))},this.setType=function(e,t){this.outputs[0].name=t,this.outputs[0].type=e,this.validateLinks()},this.findSetter=function(e){const t=this.widgets[0].value;return e._nodes.find((e=>"easy setNode"==e.type&&e.widgets[0].value===t&&""!=t))},this.isVirtualNode=!0}getInputLink(e){const t=this.findSetter(this.graph);if(t){const s=t.inputs[e];return this.graph.links[s.link]}throw new Error("No setter found for "+this.widgets[0].value+"("+this.type+")")}onAdded(e){}}LiteGraph.registerNodeType("easy getNode",Object.assign(e,{title:"Get"})),e.category="EasyUse/Util"}}),b.addEventListener("easyuse-global-seed",(function(e){let t=app.graph._nodes_by_id;for(let s in t){let o=t[s];if("easy globalSeed"==o.type){if(o.widgets){const t=o.widgets.find((e=>"value"==e.name));o.widgets.find((e=>"last_seed"==e.name)).value=t.value,t.value=e.detail.value}}else if(o.widgets){const t=o.widgets.find((e=>"seed_num"==e.name||"seed"==e.name||"noise_seed"==e.name));t&&null!=e.detail.seed_map[o.id]&&(t.value=e.detail.seed_map[o.id])}}}));const Lt=b.queuePrompt;b.queuePrompt=async function(e,{output:t,workflow:s}){s.seed_widgets={};for(let o in app.graph._nodes_by_id){let e=app.graph._nodes_by_id[o].widgets;if(e)for(let t in e)"seed_num"!=e[t].name&&"seed"!=e[t].name&&"noise_seed"!=e[t].name||"converted-widget"==e[t].type||(s.seed_widgets[o]=parseInt(t))}return await Lt.call(b,e,{output:t,workflow:s})};const Et=["easy imageSave","easy fullkSampler","easy kSampler","easy kSamplerTiled","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerSDTurbo","easy detailerFix"];v.registerExtension({name:"Comfy.EasyUse.SaveImageExtraOutput",async beforeRegisterNodeDef(e,t,s){if(Et.includes(t.name)){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?t.apply(this,arguments):void 0,o=this.widgets.find((e=>"filename_prefix"===e.name||"save_prefix"===e.name));return o.serializeValue=()=>S(s,o.value),e}}else{const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?t.apply(this,arguments):void 0;return this.properties&&"Node name for S&R"in this.properties||this.addProperty("Node name for S&R",this.constructor.type,"string"),e}}}});const St=["easy wildcards","easy positive","easy negative","easy stylesSelector","easy promptConcat","easy promptReplace"],Ct=["easy preSampling","easy preSamplingAdvanced","easy preSamplingNoiseIn","easy preSamplingCustom","easy preSamplingDynamicCFG","easy preSamplingSdTurbo","easy preSamplingLayerDiffusion"],At=["easy kSampler","easy kSamplerTiled","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerSDTurbo"],kt=["easy controlnetLoader","easy controlnetLoaderADV"],xt=["easy instantIDApply","easy instantIDApplyADV"],It=["easy ipadapterApply","easy ipadapterApplyADV","easy ipadapterApplyFaceIDKolors","easy ipadapterStyleComposition"],Nt=["easy pipeIn","easy pipeOut","easy pipeEdit","easy pipeEditPrompt"],Tt=["easy XYPlot","easy XYPlotAdvanced"],Ot=["easy setNode"],Dt=["Reroute","RescaleCFG","LoraLoaderModelOnly","LoraLoader","FreeU","FreeU_v2",...It,...Ot],Rt={"easy seed":{from:{INT:["Reroute",...Ct,"easy fullkSampler"]}},"easy positive":{from:{STRING:["Reroute",...St]}},"easy negative":{from:{STRING:["Reroute",...St]}},"easy wildcards":{from:{STRING:["Reroute","easy showAnything",...St]}},"easy stylesSelector":{from:{STRING:["Reroute","easy showAnything",...St]}},"easy promptConcat":{from:{STRING:["Reroute","easy showAnything",...St]}},"easy promptReplace":{from:{STRING:["Reroute","easy showAnything",...St]}},"easy fullLoader":{from:{PIPE_LINE:["Reroute",...Ct,"easy fullkSampler",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy a1111Loader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy comfyLoader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy hunyuanDiTLoader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy kolorsLoader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy pixArtLoader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy fluxLoader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy svdLoader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy zero123Loader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy sv3dLoader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...St]}},"easy preSampling":{from:{PIPE_LINE:["Reroute",...At,...Nt,...kt,...Tt,...Ot]}},"easy preSamplingAdvanced":{from:{PIPE_LINE:["Reroute",...At,...Nt,...kt,...Tt,...Ot]}},"easy preSamplingDynamicCFG":{from:{PIPE_LINE:["Reroute",...At,...Nt,...kt,...Tt,...Ot]}},"easy preSamplingCustom":{from:{PIPE_LINE:["Reroute",...At,...Nt,...kt,...Tt,...Ot]}},"easy preSamplingLayerDiffusion":{from:{PIPE_LINE:["Reroute","easy kSamplerLayerDiffusion",...At,...Nt,...kt,...Tt,...Ot]}},"easy preSamplingNoiseIn":{from:{PIPE_LINE:["Reroute",...At,...Nt,...kt,...Tt,...Ot]}},"easy fullkSampler":{from:{PIPE_LINE:["Reroute",...Nt.reverse(),"easy preDetailerFix","easy preMaskDetailerFix",...Ct,...Ot]}},"easy kSampler":{from:{PIPE_LINE:["Reroute",...Nt.reverse(),"easy preDetailerFix","easy preMaskDetailerFix","easy hiresFix",...Ct,...Ot]}},"easy controlnetLoader":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot]}},"easy controlnetLoaderADV":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot]}},"easy instantIDApply":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{COMBO:["Reroute","easy promptLine"]}},"easy instantIDApplyADV":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot],MODEL:Dt},to:{COMBO:["Reroute","easy promptLine"]}},"easy ipadapterApply":{to:{COMBO:["Reroute","easy promptLine"]}},"easy ipadapterApplyADV":{to:{STRING:["Reroute","easy sliderControl",...St],COMBO:["Reroute","easy promptLine"]}},"easy ipadapterStyleComposition":{to:{COMBO:["Reroute","easy promptLine"]}},"easy preDetailerFix":{from:{PIPE_LINE:["Reroute","easy detailerFix",...Nt,...Ot]},to:{PIPE_LINE:["Reroute","easy ultralyticsDetectorPipe","easy samLoaderPipe","easy kSampler","easy fullkSampler"]}},"easy preMaskDetailerFix":{from:{PIPE_LINE:["Reroute","easy detailerFix",...Nt,...Ot]}},"easy samLoaderPipe":{from:{PIPE_LINE:["Reroute","easy preDetailerFix",...Nt,...Ot]}},"easy ultralyticsDetectorPipe":{from:{PIPE_LINE:["Reroute","easy preDetailerFix",...Nt,...Ot]}},"easy cascadeLoader":{from:{PIPE_LINE:["Reroute","easy fullCascadeKSampler","easy preSamplingCascade",...kt,...Nt,...Ot],MODEL:Dt.filter((e=>!It.includes(e)))}},"easy fullCascadeKSampler":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced",...Nt,...Ot]}},"easy preSamplingCascade":{from:{PIPE_LINE:["Reroute","easy cascadeKSampler",...Nt,...Ot]}},"easy cascadeKSampler":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced",...Nt,...Ot]}},"easy pipeEdit":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Ot]},to:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot]}},"easy pipeEditPrompt":{from:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Ot]},to:{PIPE_LINE:["Reroute",...Ct,...kt,...xt,...Nt,...Ot]}}};v.registerExtension({name:"Comfy.EasyUse.Suggestions",async setup(e){LGraphCanvas.prototype.createDefaultNodeForSlot=function(e){e=e||{};var t,s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},e),o=s.nodeFrom&&null!==s.slotFrom,n=!o&&s.nodeTo&&null!==s.slotTo;if(!o&&!n)return!1;if(!s.nodeType)return!1;var i=o?s.nodeFrom:s.nodeTo,a=o?s.slotFrom:s.slotTo,l=i.type,r=!1;switch(typeof a){case"string":r=o?i.findOutputSlot(a,!1):i.findInputSlot(a,!1),a=o?i.outputs[a]:i.inputs[a];break;case"object":r=o?i.findOutputSlot(a.name):i.findInputSlot(a.name);break;case"number":r=a,a=o?i.outputs[a]:i.inputs[a];break;default:return!1}var d=a.type==LiteGraph.EVENT?"_event_":a.type,u=o?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(u&&u[d]){a.link;let e=!1;const n=o?"from":"to";if(Rt[l]&&Rt[l][n]&&(null==(t=Rt[l][n][d])?void 0:t.length)>0){for(var c in Rt[l][n][d])if(s.nodeType==Rt[l][n][d][c]||"AUTO"==s.nodeType){e=Rt[l][n][d][c];break}}else if("object"==typeof u[d]||"array"==typeof u[d]){for(var c in u[d])if(s.nodeType==u[d][c]||"AUTO"==s.nodeType){e=u[d][c];break}}else s.nodeType!=u[d]&&"AUTO"!=s.nodeType||(e=u[d]);if(e){var p=!1;"object"==typeof e&&e.node&&(p=e,e=e.node);var h=LiteGraph.createNode(e);if(h){if(p){if(p.properties)for(var m in p.properties)h.addProperty(m,p.properties[m]);if(p.inputs)for(var m in h.inputs=[],p.inputs)h.addOutput(p.inputs[m][0],p.inputs[m][1]);if(p.outputs)for(var m in h.outputs=[],p.outputs)h.addOutput(p.outputs[m][0],p.outputs[m][1]);p.title&&(h.title=p.title),p.json&&h.configure(p.json)}return this.graph.add(h),h.pos=[s.position[0]+s.posAdd[0]+(s.posSizeFix[0]?s.posSizeFix[0]*h.size[0]:0),s.position[1]+s.posAdd[1]+(s.posSizeFix[1]?s.posSizeFix[1]*h.size[1]:0)],o?s.nodeFrom.connectByType(r,h,d):s.nodeTo.connectByTypeOutput(r,h,d),!0}}}return!1},LGraphCanvas.prototype.showConnectionMenu=function(e){e=e||{};var t,s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null,allow_searchbox:this.allow_searchbox,showSearchBox:this.showSearchBox},e),o=this,n=s.nodeFrom&&s.slotFrom,i=!n&&s.nodeTo&&s.slotTo;if(!n&&!i)return!1;var a=n?s.nodeFrom:s.nodeTo,l=n?s.slotFrom:s.slotTo,r=!1;switch(typeof l){case"string":r=n?a.findOutputSlot(l,!1):a.findInputSlot(l,!1),l=n?a.outputs[l]:a.inputs[l];break;case"object":r=n?a.findOutputSlot(l.name):a.findInputSlot(l.name);break;case"number":r=l,l=n?a.outputs[l]:a.inputs[l];break;default:return!1}var d=["Add Node",null];s.allow_searchbox&&(d.push("Search"),d.push(null));var u=l.type==LiteGraph.EVENT?"_event_":l.type,c=n?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in,p=a.type;if(c&&c[u]){const e=n?"from":"to";if(Rt[p]&&Rt[p][e]&&(null==(t=Rt[p][e][u])?void 0:t.length)>0)for(var h in Rt[p][e][u])d.push(Rt[p][e][u][h]);else if("object"==typeof c[u]||"array"==typeof c[u])for(var h in c[u])d.push(c[u][h]);else d.push(c[u])}var m=new LiteGraph.ContextMenu(d,{event:s.e,title:(l&&""!=l.name?l.name+(u?" | ":""):"")+(l&&u?u:""),callback:function(e,t,i){switch(e){case"Add Node":LGraphCanvas.onMenuAdd(null,null,i,m,(function(e){n?s.nodeFrom.connectByType(r,e,u):s.nodeTo.connectByTypeOutput(r,e,u)}));break;case"Search":n?s.showSearchBox(i,{node_from:s.nodeFrom,slot_from:l,type_filter_in:u}):s.showSearchBox(i,{node_to:s.nodeTo,slot_from:l,type_filter_out:u});break;default:o.createDefaultNodeForSlot(Object.assign(s,{position:[s.e.canvasX,s.e.canvasY],nodeType:e}))}}});return!1}}}),v.registerExtension({name:"Comfy.EasyUse.TimeTaken",setup(){let e=new Map,t=0;b.addEventListener("execution_start",(e=>{graph&&graph._nodes.forEach((e=>{e.executionDuration&&delete e.executionDuration}))})),b.addEventListener("executing",(s=>{if(!k("EasyUse.Nodes.Runtime",null,!0))return;const o=(null==s?void 0:s.node)||(null==s?void 0:s.detail)||null,n=e.get(t);if(e.delete(t),t&&n){const e=Date.now()-n,s=ve(t);s&&(s.executionDuration||(s.executionDuration=0),s.executionDuration=s.executionDuration+e/1e3)}t=o,e.set(o,Date.now())}))},beforeRegisterNodeDef(e,t){const s=e.prototype.onDrawForeground;e.prototype.onDrawForeground=function(...e){const[t]=e;return function(e,t){if(!t)return;t=parseFloat(t).toFixed(3)+Y("s"),e.save(),e.fillStyle=LiteGraph.NODE_DEFAULT_BGCOLOR,function(e,t,s,o,n,i){e.beginPath(),e.moveTo(t+i,s),e.lineTo(t+o-i,s),e.arcTo(t+o,s,t+o,s+i,i),e.lineTo(t+o,s+n-i),e.arcTo(t+o,s+n,t+o-i,s+n,i),e.lineTo(t+i,s+n),e.arcTo(t,s+n,t,s+n-i,i),e.lineTo(t,s+i),e.arcTo(t,s,t+i,s,i),e.closePath()}(e,0,-LiteGraph.NODE_TITLE_HEIGHT-20,e.measureText(t).width+10,LiteGraph.NODE_TITLE_HEIGHT-10,4),e.fill(),function(e,t,s,o,n="#000",i=12,a="Inter"){e.font=`${i}px ${a}`,e.fillStyle=n,e.fillText(t,s,o)}(e,t,8,-LiteGraph.NODE_TITLE_HEIGHT-6,LiteGraph.NODE_TITLE_COLOR),e.restore()}(t,this.executionDuration),null==s?void 0:s.apply(this,e)}}});let Mt=null;v.registerExtension({name:"Comfy.EasyUse.HotKeys",setup(){if(void 0!==y){y("up,down,left,right",(function(e,t){var s,o,n,i,a,l,r,d,u,c,p,h,m,g,f;e.preventDefault();if(!k("EasyUse.Hotkeys.JumpNearestNodes",null,!0))return;const y=be();if(0===y.length)return;const _=y[0];switch(t.key){case"up":case"left":let e=null;if(Ae(_)){const e=null==(s=_.widgets_values)?void 0:s[0],t=null==(o=_.graph)?void 0:o._nodes,n=null==t?void 0:t.find((t=>{var s;if(ke(t)){if((null==(s=t.widgets_values)?void 0:s[0])===e)return t}return null}));n&&Le(n)}else if((null==(n=_.inputs)?void 0:n.length)>0){for(let t=0;t<_.inputs.length;t++)if(_.inputs[t].link){e=_.inputs[t].link;break}if(e){const t=null==(i=_.graph)?void 0:i.links;if(t[e]){const s=null==(a=t[e])?void 0:a.origin_id,o=null==(r=null==(l=_.graph)?void 0:l._nodes_by_id)?void 0:r[s];o&&Le(o)}}}break;case"down":case"right":let t=null;if(ke(_)){const e=null==(d=_.widgets_values)?void 0:d[0],t=null==(u=_.graph)?void 0:u._nodes,s=null==t?void 0:t.find((t=>{var s;if(Ae(t)){if((null==(s=t.widgets_values)?void 0:s[0])===e)return t}return null}));s&&Le(s)}else if((null==(c=_.outputs)?void 0:c.length)>0){for(let e=0;e<_.outputs.length;e++)if((null==(p=_.outputs[e].links)?void 0:p.length)>0&&_.outputs[e].links[0]){t=_.outputs[e].links[0];break}if(t){const e=null==(h=_.graph)?void 0:h.links;if(e[t]){const s=null==(m=e[t])?void 0:m.target_id,o=null==(f=null==(g=_.graph)?void 0:g._nodes_by_id)?void 0:f[s];o&&Le(o)}}}}})),y("shift+up,shift+down,shift+left,shift+right,shift+alt+⌘+left,shift+alt+⌘+right,shift+alt+ctrl+left,shift+alt+ctrl+right",(function(e,t){e.preventDefault();if(!k("EasyUse.Hotkeys.AlignSelectedNodes",null,!0))return;const s=be();if(s.length<=1)return;const o=s;switch(t.key){case"shift+up":LGraphCanvas.alignNodes(o,"top",o[0]);break;case"shift+down":LGraphCanvas.alignNodes(o,"bottom",o[0]);break;case"shift+left":LGraphCanvas.alignNodes(o,"left",o[0]);break;case"shift+right":LGraphCanvas.alignNodes(o,"right",o[0]);break;case"shift+alt+ctrl+left":case"shift+alt+⌘+left":Fe(o,"horizontal");break;case"shift+alt+ctrl+right":case"shift+alt+⌘+right":Fe(o,"vertical")}Mt||(Mt=$()),Mt&&Mt.update()})),y("shift+⌘+left,shift+⌘+right,shift+ctrl+left,shift+ctrl+right",(function(e,t){e.preventDefault();if(!k("EasyUse.Hotkeys.NormalizeSelectedNodes",null,!0))return;const s=be();if(s.length<=1)return;const o=s;switch(t.key){case"shift+ctrl+left":case"shift+⌘+left":Pe(o,"width");break;case"shift+ctrl+right":case"shift+⌘+right":Pe(o,"height")}Mt||(Mt=$()),Mt&&Mt.update()})),y("shift+g",(function(e,t){e.preventDefault();k("EasyUse.Hotkeys.AddGroup",null,!0)&&(Pt(),Mt||(Mt=$()),Mt&&Mt.update())})),y("shift+r",(function(e,t){e.preventDefault();k("EasyUse.Hotkeys.cleanVRAMused",null,!0)&&Qe()})),y("shift+m",(function(e,t){var s,o,n;if(!k("EasyUse.Hotkeys.toggleNodesMap",null,!0))return;let i=(null==(s=v.extensionManager)?void 0:s.sidebarTab)||v.extensionManager,a=(null==(o=v.extensionManager.sidebarTab)?void 0:o.activeSidebarTabId)||(null==(n=v.extensionManager)?void 0:n.activeSidebarTab);i.activeSidebarTabId=a==P?null:P}));const e=[];Array.from(Array(10).keys()).forEach((t=>e.push(`alt+${t}`))),y(e.join(","),(async function(e,t){e.preventDefault();if(!k("EasyUse.Hotkeys.NodesTemplate",null,!0))return;const s=t.key;let o=parseInt(s.split("+")[1]);const n=await b.getUserData("comfy.templates.json");let i=null;if(200==n.status)try{i=await n.json()}catch(l){Ue.error(Y("Get Node Templates File Failed"))}else localStorage["Comfy.NodeTemplates"]?i=JSON.parse(localStorage["Comfy.NodeTemplates"]):Ue.warn(Y("No Node Templates Found"));if(!i)return void Ue.warn(Y("No Node Templates Found"));o=0===o?9:o-1;const a=i[o];if(a)try{const e=(null==a?void 0:a.name)||"Group",t=(null==a?void 0:a.data)?JSON.parse(a.data):[];Gt((async()=>{await C.registerFromWorkflow(t.groupNodes,{}),localStorage.litegrapheditor_clipboard=a.data,v.canvas.pasteFromClipboard(),t.groupNodes||Pt(e)}))}catch(l){Ue.error(l)}else Ue.warn(Y("Node template with {key} not set").replace("{key}",s))}));const t=async function(e){if(("b"===e.key||"m"==e.key)&&(e.metaKey||e.ctrlKey)){if(0===be().length)return;Mt||(Mt=$()),Mt&&Mt.update()}};window.addEventListener("keydown",t,!0)}}});const Gt=async e=>{const t=localStorage.litegrapheditor_clipboard;await e(),localStorage.litegrapheditor_clipboard=t},Pt=e=>{const t=be();if(0===t.length)return;const s=t;let o=new LiteGraph.LGraphGroup;o.title=e||"Group",((e,t=[],s=20)=>{var o,n,i,a,l,r,d,u,c,p;for(var h of(n=i=a=l=-1,r=d=u=c=-1,[e._nodes,t]))for(var m in h)r=(p=h[m]).pos[0],d=p.pos[1],u=p.pos[0]+p.size[0],c=p.pos[1]+p.size[1],"Reroute"!=p.type&&(d-=LiteGraph.NODE_TITLE_HEIGHT),(null==(o=p.flags)?void 0:o.collapsed)&&(c=d+LiteGraph.NODE_TITLE_HEIGHT,(null==p?void 0:p._collapsed_width)&&(u=r+Math.round(p._collapsed_width))),(-1==n||ra)&&(a=u),(-1==l||c>l)&&(l=c);i-=Math.round(1.4*e.font_size),e.pos=[n-s,i-s],e.size=[a-n+2*s,l-i+2*s]})(o,s),v.canvas.graph.add(o)};function Ft(e,t,s,o){const n=[];return e.workflow.links.forEach((e=>{s&&e[1]===t&&!n.includes(e[3])&&n.push(e[3]),o&&e[3]===t&&!n.includes(e[1])&&n.push(e[1])})),n}async function Ut(e,t=!1){const s=structuredClone(await v.graphToPrompt()),o=[];if(s.workflow.nodes.forEach((e=>{o.push(e.id)})),s.workflow.links=s.workflow.links.filter((e=>o.includes(e[1])&&o.includes(e[3]))),t)for(;!v.graph._nodes_by_id[e].isChooser;)e=Ft(s,e,!0,!1)[0];const n=function(e,t){const s=[],o=[t];for(;o.length>0;){const t=o.pop();s.push(t),o.push(...Ft(e,t,!0,!1).filter((e=>!(s.includes(e)||o.includes(e)))))}o.push(...s.filter((e=>e!=t)));const n=[t];for(;o.length>0;){const t=o.pop();n.push(t),o.push(...Ft(e,t,!1,!0).filter((e=>!(n.includes(e)||o.includes(e)))))}const i=[];return i.push(...s),i.push(...n.filter((e=>!i.includes(e)))),i}(s,e);s.workflow.nodes=s.workflow.nodes.filter((t=>(t.id===e&&t.inputs.forEach((e=>{e.link=null})),n.includes(t.id)))),s.workflow.links=s.workflow.links.filter((e=>n.includes(e[1])&&n.includes(e[3])));const i={};for(const[r,d]of Object.entries(s.output))n.includes(parseInt(r))&&(i[r]=d);const a={};for(const[r,d]of Object.entries(i[e.toString()].inputs))Array.isArray(d)||(a[r]=d);i[e.toString()].inputs=a,s.output=i;const l=v.graphToPrompt;v.graphToPrompt=()=>(v.graphToPrompt=l,s),v.queuePrompt(0)}const Bt=new class{constructor(){this.current_node_id=void 0,this.class_of_current_node=null,this.current_node_is_chooser=!1}update(){var e,t;return v.runningNodeId!=this.current_node_id&&(this.current_node_id=v.runningNodeId,this.current_node_id?(this.class_of_current_node=null==(t=null==(e=v.graph)?void 0:e._nodes_by_id[v.runningNodeId.toString()])?void 0:t.comfyClass,this.current_node_is_chooser="easy imageChooser"===this.class_of_current_node):(this.class_of_current_node=void 0,this.current_node_is_chooser=!1),!0)}},zt=class e{constructor(){}static idle(){return!v.runningNodeId}static paused(){return!0}static paused_here(t){return e.here(t)}static running(){return!e.idle()}static here(e){return v.runningNodeId==e}static state(){return"Paused"}};g(zt,"cancelling",!1);let Wt=zt;function jt(e,t){const s=new FormData;s.append("message",t),s.append("id",e),b.fetchApi("/easyuse/image_chooser_message",{method:"POST",body:s})}function Vt(){jt(-1,"__cancel__"),Wt.cancelling=!0,b.interrupt(),Wt.cancelling=!1}var Yt=0;function Ht(){Yt+=1}const Xt=["easy kSampler","easy kSamplerTiled","easy fullkSampler"];function Zt(e){const t=v.graph._nodes_by_id[e.detail.id];if(t){t.selected_images=new Set,t.anti_selected=new Set;const s=function(e,t){var s;return e.imgs=[],t.forEach((t=>{const s=new Image;e.imgs.push(s),s.onload=()=>{v.graph.setDirtyCanvas(!0)},s.src=`/view?filename=${encodeURIComponent(t.filename)}&type=temp&subfolder=${v.getPreviewFormatParam()}`})),null==(s=e.setSizeForImage)||s.call(e),e.imgs}(t,e.detail.urls);return{node:t,image:s,isKSampler:Xt.includes(t.type)}}}function Kt(e,t,s){var o;if(e.imageRects)o=e.imageRects[t];else{const t=e.imagey;o=[1,t+1,e.size[0]-2,e.size[1]-t-2]}s.strokeRect(o[0]+1,o[1]+1,o[2]-2,o[3]-2)}class Jt extends L{constructor(){super(),this.node=null,this.select_index=[],this.dialog_div=null}show(e,t){this.select_index=[],this.node=t;const s=e.map(((e,s)=>{const o=w("img",{src:e.src,onclick:e=>{this.select_index.includes(s)?(this.select_index=this.select_index.filter((e=>e!==s)),o.classList.remove("selected")):(this.select_index.push(s),o.classList.add("selected")),t.selected_images.has(s)?t.selected_images.delete(s):t.selected_images.add(s)}});return o}));super.show(w("div.comfyui-easyuse-chooser-dialog",[w("h5.comfyui-easyuse-chooser-dialog-title",Y("Choose images to continue")),w("div.comfyui-easyuse-chooser-dialog-images",s)]))}createButtons(){const e=super.createButtons();return e[0].onclick=e=>{Wt.running()&&Vt(),super.close()},e.unshift(w("button",{type:"button",textContent:Y("Choose Selected Images"),onclick:e=>{jt(this.node.id,[...this.node.selected_images,-1,...this.node.anti_selected]),Wt.idle()&&(Ht(),Ut(this.node.id).then((()=>{jt(this.node.id,[...this.node.selected_images,-1,...this.node.anti_selected])}))),super.close()}})),e}}function $t(){const e=v.graph._nodes_by_id[this.node_id];if(e){const t=[...e.selected_images];(null==t?void 0:t.length)>0&&e.setProperty("values",t),jt(e.id,[...e.selected_images,-1,...e.anti_selected]),Wt.idle()&&(Ht(),Ut(e.id).then((()=>{jt(e.id,[...e.selected_images,-1,...e.anti_selected])})))}}function qt(){Wt.running()&&Vt()}function Qt(e){Object.defineProperty(e,"clicked",{get:function(){return this._clicked},set:function(e){this._clicked=e&&""!=this.name}})}function es(e){e.options||(e.options={}),e.options.serialize=!1}v.registerExtension({name:"Comfy.EasyUse.imageChooser",init(){window.addEventListener("beforeunload",Vt,!0)},setup(e){const t=LGraphCanvas.prototype.draw;LGraphCanvas.prototype.draw=function(){Bt.update()&&e.graph._nodes.forEach((e=>{e.update&&e.update()})),t.apply(this,arguments)},b.addEventListener("easyuse-image-choose",(function(e){const{node:t,image:s,isKSampler:o}=Zt(e);if(o){(new Jt).show(s,t)}}));const s=b.interrupt;b.interrupt=function(){Wt.cancelling||Vt(),s.apply(this,arguments)},b.addEventListener("execution_start",(function(){(Yt>0?(Yt-=1,0):(jt(-1,"__start__"),1))&&e.graph._nodes.forEach((e=>{(e.selected_images||e.anti_selected)&&(e.selected_images.clear(),e.anti_selected.clear(),e.update())}))}))},async nodeCreated(e,t){if("easy imageChooser"==e.comfyClass){e.setProperty("values",[]),void 0===(null==e?void 0:e.imageIndex)&&Object.defineProperty(e,"imageIndex",{get:function(){return null},set:function(t){e.overIndex=t}}),void 0===(null==e?void 0:e.imagey)&&Object.defineProperty(e,"imagey",{get:function(){return null},set:function(t){return e.widgets[e.widgets.length-1].last_y+LiteGraph.NODE_WIDGET_HEIGHT}});const t=e.onMouseDown;e.onMouseDown=function(s,o,n){if(s.isPrimary){const t=function(e,t){var s,o;if((null==(s=e.imgs)?void 0:s.length)>1)for(var n=0;n0&&s0&&oe.imagey)return 0;return-1}(e,o);t>=0&&this.imageClicked(t)}return t&&t.apply(this,arguments)},e.send_button_widget=e.addWidget("button","","",$t),e.cancel_button_widget=e.addWidget("button","","",qt),Qt(e.cancel_button_widget),Qt(e.send_button_widget),es(e.cancel_button_widget),es(e.send_button_widget)}},beforeRegisterNodeDef(e,t,s){if("easy imageChooser"==(null==t?void 0:t.name)){const t=e.prototype.onDrawBackground;e.prototype.onDrawBackground=function(e){t.apply(this,arguments),function(e,t){var s;if(e.imgs){if(e.imageRects)for(let s=0;s{Kt(e,s,t)})),t.strokeStyle="#F88",null==(s=null==e?void 0:e.anti_selected)||s.forEach((s=>{Kt(e,s,t)}))}}(this,e)},e.prototype.imageClicked=function(t){"easy imageChooser"===(null==e?void 0:e.comfyClass)&&(this.selected_images.has(t)?this.selected_images.delete(t):this.selected_images.add(t),this.update())};const s=e.prototype.update;e.prototype.update=function(){var e;if(s&&s.apply(this,arguments),this.send_button_widget){this.send_button_widget.node_id=this.id;const t=(this.selected?this.selected_images.size:0)+(this.anti_selected?this.anti_selected.size:0),s=(null==(e=this.imgs)?void 0:e.length)||0;Wt.paused_here(this.id)&&t>0?this.send_button_widget.name=t>1?"Progress selected ("+t+"/"+s+")":"Progress selected image":this.send_button_widget.name=t>0?t>1?"Progress selected ("+t+"/"+s+")":"Progress selected image as restart":""}if(this.cancel_button_widget){const e=Wt.running();this.cancel_button_widget.name=e?"Cancel current run":""}this.setDirtyCanvas(!0,!0)}}}}),Number.prototype.div=function(e){return function(e,t){let s,o,n=0,i=0,a="string"==typeof e?e:e.toString(),l="string"==typeof t?t:t.toString();try{n=a.toString().split(".")[1].length}catch(r){}try{i=l.toString().split(".")[1].length}catch(r){}return s=Number(a.toString().replace(".","")),o=Number(l.toString().replace(".","")),s/o*Math.pow(10,i-n)}(this,e)};let ts=[],ss=0;const os={sd3:6.5,"sd3-turbo":4};class ns extends L{constructor(){super(),this.lists=[],this.dialog_div=null,this.user_div=null}addItem(e,t){return w("div.easyuse-account-dialog-item",[w("input",{type:"text",placeholder:"Enter name",oninput:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts[t].name=e.target.value},value:ts[e].name}),w("input.key",{type:"text",oninput:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts[t].key=e.target.value},placeholder:"Enter APIKEY",value:ts[e].key}),w("button.choose",{textContent:Y("Choose"),onclick:async e=>{var s,o,n;const i=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);let a=ts[i].name,l=ts[i].key;if(!a)return void Ue.error(Y("Please enter the account name"));if(!l)return void Ue.error(Y("Please enter the APIKEY"));let r=!0;for(let t=0;t{(new ns).show(t)}},[w("div.user",[w("div.avatar",i?[w("img",{src:i})]:"😀"),w("div.info",[w("h5.name",a),w("h6.remark","Credits: "+l)])]),w("div.edit",{textContent:Y("Edit")})])),Ue.success(Y("Save Succeed"))}else Ue.success(Y("Save Succeed"));this.close()}else Ue.error(Y("Save Failed"))}}),w("button.delete",{textContent:Y("Delete"),onclick:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts.length<=1?Ue.error(Y("At least one account is required")):(ts.splice(t,1),this.dialog_div.removeChild(e.target.parentNode))}})])}show(e){ts.forEach(((t,s)=>{this.lists.push(this.addItem(s,e))})),this.dialog_div=w("div.easyuse-account-dialog",this.lists),super.show(w("div.easyuse-account-dialog-main",[w("div",[w("a",{href:"https://platform.stability.ai/account/keys",target:"_blank",textContent:Y("Getting Your APIKEY")})]),this.dialog_div]))}createButtons(){const e=super.createButtons();return e.unshift(w("button",{type:"button",textContent:Y("Save Account Info"),onclick:e=>{let t=!0;for(let s=0;s{200==e.status?Ue.success(Y("Save Succeed")):Ue.error(Y("Save Failed"))}))}else Ue.error(Y("APIKEY is not Empty"))}})),e.unshift(w("button",{type:"button",textContent:Y("Add Account"),onclick:e=>{const t="Account "+ts.length.toString();ts.push({name:t,key:""});const s=this.addItem(ts.length-1);this.lists.push(s),this.dialog_div.appendChild(s)}})),e}}v.registerExtension({name:"Comfy.EasyUse.API.SD3",async beforeRegisterNodeDef(e,t,s){if("easy stableDiffusion3API"==t.name){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=async function(){var e,s,o;t&&(null==t||t.apply(this,arguments));const n=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),i=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));let a=this.widgets.find((e=>"model"==e.name));a.callback=e=>{l.value="-"+os[e]};const l=this.addWidget("text","cost_credit","0",(e=>{}),{serialize:!1});l.disabled=!0,setTimeout((e=>{"control_before_generate"==i.name&&0===n.value&&(n.value=Math.floor(4294967294*Math.random())),l.value="-"+os[a.value]}),100);let r=w("div.easyuse-account-user",[Y("Loading UserInfo...")]);this.addDOMWidget("account","btn",w("div.easyuse-account",r)),b.addEventListener("stable-diffusion-api-generate-succeed",(async({detail:e})=>{var t;let s=r.querySelectorAll(".remark");if(s&&s[0]){const t=(null==e?void 0:e.model)?os[e.model]:0;if(t){let e=function(e,t){let s,o,n,i,a,l;a="string"==typeof e?e:e.toString(),l="string"==typeof t?t:t.toString();try{s=a.split(".")[1].length}catch(r){s=0}try{o=l.split(".")[1].length}catch(r){o=0}return n=Math.pow(10,Math.max(s,o)),i=s>=o?s:o,((e*n-t*n)/n).toFixed(i)}(parseFloat(s[0].innerText.replace(/Credits: /g,"")),t);e>0&&(s[0].innerText="Credits: "+e.toString())}}await X(1e4);const o=await b.fetchApi("/easyuse/stability/balance");if(200==o.status){const e=await o.json();if(null==e?void 0:e.balance){const o=(null==(t=e.balance)?void 0:t.credits)||0;s&&s[0]&&(s[0].innerText="Credits: "+o)}}}));const d=await b.fetchApi("/easyuse/stability/api_keys");if(200==d.status){let t=await d.json();if(ts=t.keys,ss=t.current,ts.length>0&&void 0!==ss){const t=ts[ss].key,n=ts[ss].name;if(t){const t=await b.fetchApi("/easyuse/stability/user_info");if(200==t.status){const n=await t.json();if((null==n?void 0:n.account)&&(null==n?void 0:n.balance)){const t=(null==(e=n.account)?void 0:e.profile_picture)||null,i=(null==(s=n.account)?void 0:s.email)||null,a=(null==(o=n.balance)?void 0:o.credits)||0;r.replaceChildren(w("div.easyuse-account-user-info",{onclick:e=>{(new ns).show(r)}},[w("div.user",[w("div.avatar",t?[w("img",{src:t})]:"😀"),w("div.info",[w("h5.name",i),w("h6.remark","Credits: "+a)])]),w("div.edit",{textContent:Y("Edit")})]))}}}else r.replaceChildren(w("div.easyuse-account-user-info",{onclick:e=>{(new ns).show(r)}},[w("div.user",[w("div.avatar","😀"),w("div.info",[w("h5.name",n),w("h6.remark",Y("Click to set the APIKEY first"))])]),w("div.edit",{textContent:Y("Edit")})]))}}}}}});let is=null;function as(){is&&(is.removeEventListeners(),is.dropdown.remove(),is=null)}function ls(e,t,s,o=!1){as(),new rs(e,t,s,o)}class rs{constructor(e,t,s,o=!1){this.dropdown=document.createElement("ul"),this.dropdown.setAttribute("role","listbox"),this.dropdown.classList.add("easy-dropdown"),this.selectedIndex=-1,this.inputEl=e,this.suggestions=t,this.onSelect=s,this.isDict=o,this.focusedDropdown=this.dropdown,this.buildDropdown(),this.onKeyDownBound=this.onKeyDown.bind(this),this.onWheelBound=this.onWheel.bind(this),this.onClickBound=this.onClick.bind(this),this.addEventListeners()}buildDropdown(){this.isDict?this.buildNestedDropdown(this.suggestions,this.dropdown):this.suggestions.forEach(((e,t)=>{this.addListItem(e,t,this.dropdown)}));const e=this.inputEl.getBoundingClientRect();this.dropdown.style.top=e.top+e.height-10+"px",this.dropdown.style.left=e.left+"px",document.body.appendChild(this.dropdown),is=this}buildNestedDropdown(e,t){let s=0;Object.keys(e).forEach((o=>{const n=e[o];if("object"==typeof n&&null!==n){const e=document.createElement("ul");e.setAttribute("role","listbox"),e.classList.add("easy-nested-dropdown");const i=document.createElement("li");i.classList.add("folder"),i.textContent=o,i.appendChild(e),i.addEventListener("mouseover",this.onMouseOver.bind(this,s,t)),t.appendChild(i),this.buildNestedDropdown(n,e),s+=1}else{const e=document.createElement("li");e.classList.add("item"),e.setAttribute("role","option"),e.textContent=o,e.addEventListener("mouseover",this.onMouseOver.bind(this,s,t)),e.addEventListener("mousedown",this.onMouseDown.bind(this,o)),t.appendChild(e),s+=1}}))}addListItem(e,t,s){const o=document.createElement("li");o.setAttribute("role","option"),o.textContent=e,o.addEventListener("mouseover",this.onMouseOver.bind(this,t)),o.addEventListener("mousedown",this.onMouseDown.bind(this,e)),s.appendChild(o)}addEventListeners(){document.addEventListener("keydown",this.onKeyDownBound),this.dropdown.addEventListener("wheel",this.onWheelBound),document.addEventListener("click",this.onClickBound)}removeEventListeners(){document.removeEventListener("keydown",this.onKeyDownBound),this.dropdown.removeEventListener("wheel",this.onWheelBound),document.removeEventListener("click",this.onClickBound)}onMouseOver(e,t){t&&(this.focusedDropdown=t),this.selectedIndex=e,this.updateSelection()}onMouseOut(){this.selectedIndex=-1,this.updateSelection()}onMouseDown(e,t){t.preventDefault(),this.onSelect(e),this.dropdown.remove(),this.removeEventListeners()}onKeyDown(e){const t=Array.from(this.focusedDropdown.children),s=t[this.selectedIndex];if(is)if(38===e.keyCode)e.preventDefault(),this.selectedIndex=Math.max(0,this.selectedIndex-1),this.updateSelection();else if(40===e.keyCode)e.preventDefault(),this.selectedIndex=Math.min(t.length-1,this.selectedIndex+1),this.updateSelection();else if(39===e.keyCode){if(e.preventDefault(),s&&s.classList.contains("folder")){const e=s.querySelector(".easy-nested-dropdown");e&&(this.focusedDropdown=e,this.selectedIndex=0,this.updateSelection())}}else if(37===e.keyCode&&this.focusedDropdown!==this.dropdown){const e=this.focusedDropdown.closest(".easy-dropdown, .easy-nested-dropdown").parentNode.closest(".easy-dropdown, .easy-nested-dropdown");e&&(this.focusedDropdown=e,this.selectedIndex=Array.from(e.children).indexOf(this.focusedDropdown.parentNode),this.updateSelection())}else if((13===e.keyCode||9===e.keyCode)&&this.selectedIndex>=0){e.preventDefault(),s.classList.contains("item")&&(this.onSelect(t[this.selectedIndex].textContent),this.dropdown.remove(),this.removeEventListeners());const o=s.querySelector(".easy-nested-dropdown");o&&(this.focusedDropdown=o,this.selectedIndex=0,this.updateSelection())}else 27===e.keyCode&&(this.dropdown.remove(),this.removeEventListeners())}onWheel(e){const t=parseInt(this.dropdown.style.top);localStorage.getItem("Comfy.Settings.Comfy.InvertMenuScrolling")?this.dropdown.style.top=t+(e.deltaY<0?10:-10)+"px":this.dropdown.style.top=t+(e.deltaY<0?-10:10)+"px"}onClick(e){this.dropdown.contains(e.target)||e.target===this.inputEl||(this.dropdown.remove(),this.removeEventListeners())}updateSelection(){Array.from(this.focusedDropdown.children).forEach(((e,t)=>{t===this.selectedIndex?e.classList.add("selected"):e.classList.remove("selected")}))}}function ds(e){const t=e.min||0,s=e.max||0,o=e.step||1;if(0===o)return[];const n=[];let i=t;for(;i<=s;){if(Number.isInteger(o))n.push(Math.round(i)+"; ");else{let e=i.toFixed(3);-0==e&&(e="0.000"),/\.\d{3}$/.test(e)||(e+="0"),n.push(e+"; ")}i+=o}return s>=0&&t>=0?n:n.reverse()}let us={},cs={};function ps(e,t){String(e.id);const s=t.name,o=t.value.replace(/^(loader|preSampling):\s/,"");cs[s]?cs[s]!=us[o]&&(cs[s]=us[o]):cs={...cs,[s]:us[o]}}v.registerExtension({name:"Comfy.EasyUse.XYPlot",async beforeRegisterNodeDef(e,t,s){if("easy XYPlot"===t.name){us=t.input.hidden.plot_dict[0];for(const e in us){const t=us[e];if(Array.isArray(t)){let s=[];for(const e of t)s.push(e+"; ");us[e]=s}else us[e]="object"==typeof t?"seed"==e?t+"; ":ds(t):t+"; "}us.None=[],us["---------------------"]=[]}},nodeCreated(e){"easy XYPlot"===e.comfyClass&&(function(e){if(e.widgets)for(const t of e.widgets)if("x_axis"===t.name||"y_axis"===t.name){let s=t.value;Object.defineProperty(t,"value",{get:()=>s,set(o){o!==s&&(s=o,ps(e,t))}})}}(e),function(e){if(e.widgets){const t=e.widgets.filter((e=>"customtext"===e.type&&!1!==e.dynamicPrompts||e.dynamicPrompts));for(const e of t){let t=function(e,t,o,n){return e&&(t[o]=e),t.map((e=>s(e,n))).filter((e=>""!==e)).join("")},s=function(e,t){if(e=o(e),n(e,t))return e+"; ";let s=i(e,t);return 1===s.length||2===s.length?s[0]:n(a(e),t)?a(e)+"; ":""},o=function(e){return e.replace(/(\n|;| )/g,"")},n=function(e,t){return t.includes(e+"; ")},i=function(e,t){return t.filter((t=>t.toLowerCase().includes(e.toLowerCase())))},a=function(e){return Number(e)?Number(e).toFixed(3):["0","0.","0.0","0.00","00"].includes(e)?"0.000":e};const l=function(){const s=e.name[0]+"_axis";let o=(null==cs?void 0:cs[s])||[];if(0===o.length)return;const n=e.inputEl.value,i=e.inputEl.selectionStart;let a=n.split("; ");const l=n.substring(0,i).split("; ").length-1,r=a[l].replace(/\n/g,"").toLowerCase(),d=o.filter((e=>e.toLowerCase().includes(r))).map((e=>e.replace(/; /g,"")));if(d.length>0)ls(e.inputEl,d,(s=>{const n=t(s,a,l,o);e.inputEl.value=n}));else{as();const s=t(null,a,l,o);e.inputEl.value=s}};e.inputEl.removeEventListener("input",l),e.inputEl.addEventListener("input",l),e.inputEl.removeEventListener("mouseup",l),e.inputEl.addEventListener("mouseup",l)}}}(e))}});export{Y as $,G as N,b as a,v as b,fe as c,k as d,Qe as e,P as f,ge as g,x as h,Ee as j,V as l,X as s,Ue as t,$ as u}; diff --git a/web_version/v2/assets/extensions-DCFcU_BW.js b/web_version/v2/assets/extensions-DCFcU_BW.js new file mode 100644 index 0000000..416f0d5 --- /dev/null +++ b/web_version/v2/assets/extensions-DCFcU_BW.js @@ -0,0 +1 @@ +var e,t,s,n,i,o,a,l,r,d,u,c,p,h,m=Object.defineProperty,g=(e,t,s)=>((e,t,s)=>t in e?m(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s)(e,"symbol"!=typeof t?t+"":t,s);import{d as f,h as y}from"./vendor-DT1J-jWa.js";import{c as _}from"./lodash-CZi7izHi.js";let v=(null==(t=null==(e=window.comfyAPI)?void 0:e.app)?void 0:t.app)||null,b=(null==(n=null==(s=window.comfyAPI)?void 0:s.api)?void 0:n.api)||null,w=(null==(o=null==(i=window.comfyAPI)?void 0:i.ui)?void 0:o.$el)||null,L=(null==(l=null==(a=window.comfyAPI)?void 0:a.dialog)?void 0:l.ComfyDialog)||null,S=(null==(d=null==(r=window.comfyAPI)?void 0:r.widgets)?void 0:d.ComfyWidgets)||null,E=(null==(c=null==(u=window.comfyAPI)?void 0:u.utils)?void 0:c.applyTextReplacements)||null,C=(null==(h=null==(p=window.comfyAPI)?void 0:p.groupNode)?void 0:h.GroupNodeConfig)||null;const k=(e,t=void 0)=>{var s,n;return e?null==(n=null==(s=null==v?void 0:v.ui)?void 0:s.settings)?void 0:n.getSettingValue(e,t):null};function A(e,t=null,s=void 0){try{let n=e?k(e,s):null;return null==n&&(n=t?localStorage[t]:localStorage[e]||null),n}catch(n){return null}}function x(e,t=e=>{}){var s;const n=null==(s=v.ui.settings.settingsLookup)?void 0:s[e];n&&(n.onChange=e=>t(e))}async function I(e,t,s=null){var n,i;try{(null==(i=null==(n=null==v?void 0:v.ui)?void 0:n.settings)?void 0:i.setSettingValue)?v.ui.settings.setSettingValue(e,t):await b.storeSetting(e,t),s&&(localStorage[s]="object"==typeof t?JSON.stringify(t):t)}catch(o){}}const N="comfyui-easyuse-",T="dark-theme",O="#236692",D={PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd",FLOW_CONTROL:"#373780"},R=0x4000000000000,G=["loaders","latent","image","mask","sampling","_for_testing","advanced","utils","api"],M={ALWAYS:0,NEVER:2,BYPASS:4},P="easyuse_nodes_map",F=LGraphCanvas.node_colors.bgcolor,U={ColorPalette:{version:105,id:"obsidian",name:"Obsidian",colors:{node_slot:{CLIP:"#FFD500",CLIP_VISION:"#A8DADC",CLIP_VISION_OUTPUT:"#ad7452",CONDITIONING:"#FFA931",CONTROL_NET:"#6EE7B7",IMAGE:"#64B5F6",LATENT:"#FF9CF9",MASK:"#81C784",MODEL:"#B39DDB",STYLE_MODEL:"#C2FFAE",VAE:"#FF6E6E",TAESD:"#DCC274",PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd"},litegraph_base:{BACKGROUND_IMAGE:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",CLEAR_BACKGROUND_COLOR:"#222222",NODE_TITLE_COLOR:"#d4d4d8",NODE_SELECTED_TITLE_COLOR:"#ffffff",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#ffffff",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#09090b",NODE_DEFAULT_BGCOLOR:"rgba(24,24,27,.9)",NODE_DEFAULT_BOXCOLOR:"rgba(255,255,255,.75)",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:O,DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#242427",WIDGET_OUTLINE_COLOR:"#3f3f46",WIDGET_TEXT_COLOR:"#d4d4d8",WIDGET_SECONDARY_TEXT_COLOR:"#d4d4d8",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#A86",CONNECTING_LINK_COLOR:"#AFA"},comfy_base:{"fg-color":"#fff","bg-color":"#09090b","comfy-menu-bg":"rgba(24,24,24,.9)","comfy-input-bg":"#262626","input-text":"#ddd","descrip-text":"#999","drag-text":"#ccc","error-text":"#ff4444","border-color":"#29292c","tr-even-bg-color":"rgba(28,28,28,.9)","tr-odd-bg-color":"rgba(19,19,19,.9)"}}},NODE_COLORS:{red:{color:"#af3535",bgcolor:F,groupcolor:"#A88"},brown:{color:"#38291f",bgcolor:F,groupcolor:"#b06634"},green:{color:"#346434",bgcolor:F,groupcolor:"#8A8"},blue:{color:"#1f1f48",bgcolor:F,groupcolor:"#88A"},pale_blue:{color:"#006691",bgcolor:F,groupcolor:"#3f789e"},cyan:{color:"#008181",bgcolor:F,groupcolor:"#8AA"},purple:{color:"#422342",bgcolor:F,groupcolor:"#a1309b"},yellow:{color:"#c09430",bgcolor:F,groupcolor:"#b58b2a"},black:{color:"rgba(0,0,0,.8)",bgcolor:F,groupcolor:"#444"}}};let B=JSON.parse(JSON.stringify(U));delete B.NODE_COLORS,B.ColorPalette.id="obsidian_dark",B.ColorPalette.name="Obsidian Dark",B.ColorPalette.colors.litegraph_base.BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg==",B.ColorPalette.colors.litegraph_base.CLEAR_BACKGROUND_COLOR="#09090b";const z=LGraphCanvas.node_colors.bgcolor,W={ColorPalette:{id:"milk_white",name:"Milk White",colors:{node_slot:{CLIP:"#FFA726",CLIP_VISION:"#5C6BC0",CLIP_VISION_OUTPUT:"#8D6E63",CONDITIONING:"#EF5350",CONTROL_NET:"#66BB6A",IMAGE:"#42A5F5",LATENT:"#AB47BC",MASK:"#9CCC65",MODEL:"#7E57C2",STYLE_MODEL:"#D4E157",VAE:"#FF7043",PIPE_LINE:"#7737AA",PIPE_LINE_SDXL:"#7737AA",INT:"#29699C",X_Y:"#38291f",XYPLOT:"#74DA5D",LORA_STACK:"#94dccd",CONTROL_NET_STACK:"#94dccd"},litegraph_base:{BACKGROUND_IMAGE:"data:image/gif;base64,R0lGODlhZABkALMAAAAAAP///+vr6+rq6ujo6Ofn5+bm5uXl5d3d3f///wAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAkALAAAAABkAGQAAAT/UMhJq7046827HkcoHkYxjgZhnGG6si5LqnIM0/fL4qwwIMAg0CAsEovBIxKhRDaNy2GUOX0KfVFrssrNdpdaqTeKBX+dZ+jYvEaTf+y4W66mC8PUdrE879f9d2mBeoNLfH+IhYBbhIx2jkiHiomQlGKPl4uZe3CaeZifnnijgkESBqipqqusra6vsLGys62SlZO4t7qbuby7CLa+wqGWxL3Gv3jByMOkjc2lw8vOoNSi0czAncXW3Njdx9Pf48/Z4Kbbx+fQ5evZ4u3k1fKR6cn03vHlp7T9/v8A/8Gbp4+gwXoFryXMB2qgwoMMHyKEqA5fxX322FG8tzBcRnMW/zlulPbRncmQGidKjMjyYsOSKEF2FBlJQMCbOHP6c9iSZs+UnGYCdbnSo1CZI5F64kn0p1KnTH02nSoV3dGTV7FFHVqVq1dtWcMmVQZTbNGu72zqXMuW7danVL+6e4t1bEy6MeueBYLXrNO5Ze36jQtWsOG97wIj1vt3St/DjTEORss4nNq2mDP3e7w4r1bFkSET5hy6s2TRlD2/mSxXtSHQhCunXo26NevCpmvD/UU6tuullzULH76q92zdZG/Ltv1a+W+osI/nRmyc+fRi1Xdbh+68+0vv10dH3+77KD/i6IdnX669/frn5Zsjh4/2PXju8+8bzc9/6fj27LFnX11/+IUnXWl7BJfegm79FyB9JOl3oHgSklefgxAC+FmFGpqHIYcCfkhgfCohSKKJVo044YUMttggiBkmp6KFXw1oII24oYhjiDByaKOOHcp3Y5BD/njikSkO+eBREQAAOw==",CLEAR_BACKGROUND_COLOR:"lightgray",NODE_TITLE_COLOR:"#222",NODE_SELECTED_TITLE_COLOR:"#000",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#444",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#F7F7F7",NODE_DEFAULT_BGCOLOR:"#F5F5F5",NODE_DEFAULT_BOXCOLOR:"#555",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:"#000",DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0.1)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#D4D4D4",WIDGET_OUTLINE_COLOR:"#999",WIDGET_TEXT_COLOR:"#222",WIDGET_SECONDARY_TEXT_COLOR:"#555",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#FF9800",CONNECTING_LINK_COLOR:"#222"},comfy_base:{"fg-color":"#222","bg-color":"#DDD","comfy-menu-bg":"#F5F5F5","comfy-input-bg":"#C9C9C9","input-text":"#222","descrip-text":"#444","drag-text":"#555","error-text":"#F44336","border-color":"#bbb","tr-even-bg-color":"#f9f9f9","tr-odd-bg-color":"#fff","content-bg":"#e0e0e0","content-fg":"#222","content-hover-bg":"#adadad","content-hover-fg":"#222"}}},NODE_COLORS:{red:{color:"#af3535",bgcolor:z,groupcolor:"#A88"},brown:{color:"#38291f",bgcolor:z,groupcolor:"#b06634"},green:{color:"#346434",bgcolor:z,groupcolor:"#8A8"},blue:{color:"#1f1f48",bgcolor:z,groupcolor:"#88A"},pale_blue:{color:"#006691",bgcolor:z,groupcolor:"#3f789e"},cyan:{color:"#008181",bgcolor:z,groupcolor:"#8AA"},purple:{color:"#422342",bgcolor:z,groupcolor:"#a1309b"},yellow:{color:"#c09430",bgcolor:z,groupcolor:"#b58b2a"},black:{color:"rgba(0,0,0,.8)",bgcolor:z,groupcolor:"#444"}}},j={"Workflow created by":"工作流创建者","Watch more video content":"观看更多视频内容","Workflow Guide":"工作流指南","💎 View Checkpoint Info...":"💎 查看 Checkpoint 信息...","💎 View Lora Info...":"💎 查看 Lora 信息...","🔃 Reload Node":"🔃 刷新节点","Updated At:":"最近更新:","Created At:":"首次发布:","✏️ Edit":"✏️ 编辑","💾 Save":"💾 保存","No notes":"当前还没有备注内容","Saving Notes...":"正在保存备注...","Type your notes here":"在这里输入备注内容",ModelName:"模型名称","Models Required":"所需模型","Download Model":"下载模型","Source Url":"模型源地址",Notes:"备注",Type:"类型","Trained Words":"训练词",BaseModel:"基础算法",Details:"详情",Description:"描述",Download:"下载量",Source:"来源","Saving Preview...":"正在保存预览图...","Saving Succeed":"保存成功","Clean SuccessFully":"清理成功","Clean Failed":"清理失败","Saving Failed":"保存失败","No COMBO link":"沒有找到COMBO连接","Reboot ComfyUI":"重启ComfyUI","Are you sure you'd like to reboot the server?":"是否要重启ComfyUI?","Nodes Map":"管理节点组","Nodes map sorting mode":"管理节点组排序模式","No Nodes":"未找到节点","No nodes found in the map":"在工作流程中没有找到节点","Expand All":"展开所有组","Collapse All":"折叠所有组",Close:"关闭","Default automatic sorting, if set to manual, groups can be dragged and dropped and the sorting results saved.":"默认自动排序,如果设置为手动,组可以拖放并保存排序结果。","For drag and drop sorting, please find Nodes map sorting mode in Settings->EasyUse and change it to manual":"如需拖拽排序请在设置->EasyUse节点中找到管理节点组排序模式并修改成 manual",Queue:"队列","Cleanup Of VRAM Usage":"清理显存占用","Please stop all running tasks before cleaning GPU":"请在清理GPU之前停止所有运行中的任务",Always:"启用中",Bypass:"已忽略",Never:"已停用","Auto Sorting":"自动排序","Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never":"点击`启用中/已忽略`可设置组模式, 长按可停用该组节点","Enable Shift+Up/Down/Left/Right key and Shift+Ctrl+Alt+Left/Right to align selected nodes":"启用 Shift+上/下/左/右 和 Shift+Ctrl+Alt+左/右 键对齐选中的节点","Enable Shift+Ctrl+Left/Right key to normalize selected nodes":"启用 Shift+Ctrl+左/右 键规范化选中的节点","Enable Shift+g to add selected nodes to a group":"启用 Shift+g 键将选中的节点添加一个组","Enable Shift+r to unload models and node cache":"启用 Shift+r 键卸载模型和节点缓存","Enable Shift+m to toggle nodes map":"启用 Shift+m 键显隐管理节点组","Enable Up/Down/Left/Right key to jump nearest nodes":"启用 上/下/左/右 键跳转到最近的前后节点","Enable Alt+1~9 to paste nodes from nodes template":"启用 Alt+1~9 从节点模板粘贴到工作流中","Enable contextMenu auto nest subdirectories":"启用上下文菜单自动嵌套子目录","Enable right-click menu to add node A~Z sorting":"启用右键菜单中新建节点A~Z排序","Enable model thumbnails display":"启动模型预览图显示","Enable nodes runtime display":"启动节点运行时间显示","Enable chain get node and set node with parent nodes":"启用将获取点和设置点与父节点链在一起","Maximum number of model thumbnails displayed":"显示的模型缩略图的最大数量","Too many thumbnails will affect the first loading time, set the maximum value to not load the thumbnail function when there are too many models's thumbnail":"太多的缩略图会影响首次加载时间,当模型缩略图太多时,设置最大值以不加载缩略图功能","Too many thumbnails, have closed the display":"模型缩略图太多啦,为您关闭了显示","Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute horizontal/vertical nodes":"Shift+上/下/左/右 可以对齐选中的节点, Shift+Ctrl+Alt+左/右 可以水平/垂直分布节点","Enable Shift+Ctrl+Left key to normalize width and Shift+Ctrl+Right key to normalize height":"启用 Shift+Ctrl+左 键规范化宽度和 Shift+Ctrl+右 键规范化高度","After v1.2.39, Ctrl+g can be used instead of it":"从v1.2.39开始,可以使用Ctrl+g代替","Use three shortcut buttons in the right-click menu":"在右键菜单中使用三个快捷按钮","Enable Nodes Map":"启用节点组管理","You need to refresh the page to update successfully":"您需要刷新页面以成功更新","Get styles list Failed":"获取样式列表失败","Get style image Failed":"获取样式图片失败","Empty All":"清空所有","Type here to search styles ...":"在此处输入以搜索样式 ...","Loading UserInfo...":"正在获取用户信息...","Please set the APIKEY first":"请先设置APIKEY","Setting APIKEY":"设置APIKEY","Save Account Info":"保存账号信息",Choose:"选择",Delete:"删除",Edit:"编辑","At least one account is required":"删除失败: 至少需要一个账户","APIKEY is not Empty":"APIKEY 不能为空","Add Account":"添加账号","Getting Your APIKEY":"获取您的APIKEY","Choose Selected Images":"选择选中的图片","Choose images to continue":"选择图片以继续",Background:"背景",Hat:"帽子",Hair:"头发",Body:"身体",Face:"脸部",Clothes:"衣服",Others:"其他",Glove:"手套",Glasses:"眼镜",Sunglasses:"太阳镜","Upper-clothes":"上衣","Top-clothes":"上衣","Bottom-clothes":"下身装","Torso-skin":"皮肤",Dress:"连衣裙",Coat:"外套",Socks:"袜子",Pants:"裤子",Jumpsuits:"连体衣",Scarf:"围巾",Skirt:"裙子","Left-arm":"左臂","Right-arm":"右臂","Left-leg":"左腿","Right-leg":"右腿","Left-foot":"左脚","Right-foot":"右脚","Left-shoe":"左鞋","Right-shoe":"右鞋",s:"秒","No Node Templates Found":"未找到节点模板预设","Get Node Templates File Failed":"获取节点模板文件失败","Node template with {key} not set":"未设置快捷键为{key}的节点预设","ComfyUI Basic":"ComfyUI 基础节点","Recommend Nodes":"推荐节点","Others A~Z":"其他节点 A~Z"},V=A("AGL.Locale"),Y=(e,t=!1)=>"zh-CN"===(t?navigator.language:V)&&j[e]||e,H={addGroup:{id:"EasyUse.Hotkeys.AddGroup",name:Y("Enable Shift+g to add selected nodes to a group"),tooltip:Y("After v1.2.39, Ctrl+g can be used instead of it"),type:"boolean",defaultValue:!0},cleanVRAMUsed:{id:"EasyUse.Hotkeys.cleanVRAMUsed",name:Y("Enable Shift+r to unload models and node cache"),type:"boolean",defaultValue:!0},toggleSiteMap:{id:"EasyUse.Hotkeys.toggleNodesMap",name:Y("Enable Shift+m to toggle nodes map"),type:"boolean",defaultValue:!0},alignSelectedNodes:{id:"EasyUse.Hotkeys.AlignSelectedNodes",name:Y("Enable Shift+Up/Down/Left/Right key and Shift+Ctrl+Alt+Left/Right to align selected nodes"),tooltip:Y("Shift+Up/Down/Left/Right can align selected nodes, Shift+Ctrl+Alt+Left/Right can distribute horizontal/vertical nodes"),type:"boolean",defaultValue:!0},NormalizeSelectedNodes:{id:"EasyUse.Hotkeys.NormalizeSelectedNodes",name:Y("Enable Shift+Ctrl+Left/Right key to normalize selected nodes"),tooltip:Y("Enable Shift+Ctrl+Left key to normalize width and Shift+Ctrl+Right key to normalize height"),type:"boolean",defaultValue:!0},nodesTemplate:{id:"EasyUse.Hotkeys.NodesTemplate",name:Y("Enable Alt+1~9 to paste nodes from nodes template"),type:"boolean",defaultValue:!0},jumpNearestNodes:{id:"EasyUse.Hotkeys.JumpNearestNodes",name:Y("Enable Up/Down/Left/Right key to jump nearest nodes"),type:"boolean",defaultValue:!0},subDirectories:{id:"EasyUse.ContextMenu.SubDirectories",name:Y("Enable contextMenu auto nest subdirectories"),type:"boolean",defaultValue:!1},modelsThumbnails:{id:"EasyUse.ContextMenu.ModelsThumbnails",name:Y("Enable model thumbnails display"),type:"boolean",defaultValue:!1},modelsThumbnailsLimit:{id:"EasyUse.ContextMenu.ModelsThumbnailsLimit",name:Y("Maximum number of model thumbnails displayed"),tooltip:Y("Too many thumbnails will affect the first loading time, set the maximum value to not load the thumbnail function when there are too many models's thumbnail"),type:"slider",attrs:{min:0,max:5e3,step:100},defaultValue:500},rightMenuNodesSort:{id:"EasyUse.ContextMenu.NodesSort",name:Y("Enable right-click menu to add node A~Z sorting"),type:"boolean",defaultValue:!0},quickOptions:{id:"EasyUse.ContextMenu.QuickOptions",name:Y("Use three shortcut buttons in the right-click menu"),type:"combo",options:["At the forefront","At the end","Disable"],defaultValue:"At the forefront"},nodesRuntime:{id:"EasyUse.Nodes.Runtime",name:Y("Enable nodes runtime display"),type:"boolean",defaultValue:!0},chainGetSet:{id:"EasyUse.Nodes.ChainGetSet",name:Y("Enable chain get node and set node with parent nodes"),type:"boolean",defaultValue:!0},nodesMap:{id:"EasyUse.NodesMap.Sorting",name:Y("Nodes map sorting mode"),tooltip:Y("Default automatic sorting, if set to manual, groups can be dragged and dropped and the sorting results saved."),type:"combo",options:["Auto sorting","Manual drag&drop sorting"],defaultValue:"Auto sorting"},enableNodesMap:{id:"EasyUse.NodesMap.Enable",name:Y("Enable Nodes Map"),tooltip:Y("You need to refresh the page to update successfully"),type:"boolean",defaultValue:!0}};function X(e=100,t){return new Promise((s=>{setTimeout((()=>{s(t)}),e)}))}function Z(e,t){if(e="number"==typeof e?e:e instanceof Date?e.getTime():parseInt(e),isNaN(e))return null;let s=new Date(e);(e=s.toString().split(/[\s\:]/g).slice(0,-2))[1]=["01","02","03","04","05","06","07","08","09","10","11","12"][s.getMonth()];let n={MM:1,dd:2,yyyy:3,hh:4,mm:5,ss:6};return t.replace(/([Mmdhs]|y{2})\1/g,(t=>e[n[t]]))}const K=/Mac|iPod|iPhone|iPad/.test(navigator.platform),J=e=>K?e.replace(/Ctrl/g,"⌘").replace(/Alt/g,"⌥").replace(/Shift/g,"⇧"):e,$=f("groups",{state:e=>({groups:[],nodes:[],isWatching:!1}),getters:{groups_nodes(){var e;let t=[],s=[];if((null==(e=this.nodes)?void 0:e.length)>0){this.nodes.map((e=>{let n=e.pos,i=!1;for(let s=0;so.pos[0]&&n[0]o.pos[1]&&n[1]e.pos[0]-t.pos[0])).sort(((e,t)=>e.pos[1]-t.pos[1])))},setNodes(e){this.nodes=_(e)},update(){var e,t,s;(((null==(e=v.extensionManager)?void 0:e.activeSidebarTab)||(null==(s=null==(t=v.extensionManager.sidebarTab)?void 0:t.activeSidebarTab)?void 0:s.id))===P||this.isWatching)&&setTimeout((e=>{this.setGroups(v.canvas.graph._groups),this.setNodes(v.canvas.graph._nodes)}),1)},watchGraph(e=!1){e&&(this.isWatching=!0);let t=this;this.update();const s=v.graph.onNodeAdded;v.graph.onNodeAdded=function(e){t.update();const n=e.onRemoved;return e.onRemoved=function(){return t.update(),null==n?void 0:n.apply(this,arguments)},null==s?void 0:s.apply(this,arguments)},v.canvas.onNodeMoved=function(e){t.update()};const n=LGraphCanvas.onNodeAlign;LGraphCanvas.onNodeAlign=function(e){return t.update(),null==n?void 0:n.apply(this,arguments)};const i=LGraphCanvas.onGroupAdd;LGraphCanvas.onGroupAdd=function(){return t.update(),null==i?void 0:i.apply(this,arguments)};const o=LGraphCanvas.onGroupAlign;LGraphCanvas.onGroupAlign=function(e){return t.update(),null==o?void 0:o.apply(this,arguments)};const a=LGraphCanvas.onMenuNodeRemove;LGraphCanvas.onMenuNodeRemove=function(e){return t.update(),null==a?void 0:a.apply(this,arguments)}},unwatchGraph(){this.isWatching=!1}}});let q=null;const Q=["custom_obsidian","custom_obsidian_dark","custom_milk_white"],ee={"easy positive":"green","easy negative":"red","easy promptList":"cyan","easy promptLine":"cyan","easy promptConcat":"cyan","easy promptReplace":"cyan","easy forLoopStart":"blue","easy forLoopEnd":"blue","easy loadImagesForLoop":"blue"};let te=LGraphCanvas.node_colors,se=null,ne=null,ie=null,oe=null;for(let hs in H){const e="Disabled"==A("Comfy.UseNewMenu")?"👽 "+J(H[hs].name):J(H[hs].name),t=H[hs].tooltip?J(H[hs].tooltip):"";ae={...H[hs],name:e,tooltip:t},v.ui.settings.addSetting(ae)}var ae;function le(e,t=!1){let s="after",n="before";t&&([n,s]=[s,n]),e.label=(e.label??e.name).replace(n,s),e.name=e.label}function re(e,t,s,n,i,o,a){t.strokeStyle=n,t.fillStyle=i;let l=LiteGraph.NODE_TITLE_HEIGHT,r=this.ds.scale<.5,d=e._shape||e.constructor.shape||LiteGraph.ROUND_SHAPE,u=e.constructor.title_mode,c=!0;u==LiteGraph.TRANSPARENT_TITLE||u==LiteGraph.NO_TITLE?c=!1:u==LiteGraph.AUTOHIDE_TITLE&&mouse_over&&(c=!0);let p=new Float32Array(4);p=[0,c?-l:0,s[0]+1,c?s[1]+l:s[1]];let h=t.globalAlpha;if(t.lineWidth=1,t.beginPath(),d==LiteGraph.BOX_SHAPE||r?t.fillRect(p[0],p[1],p[2],p[3]):d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CARD_SHAPE?t.roundRect(p[0],p[1],p[2],p[3],d==LiteGraph.CARD_SHAPE?[this.round_radius,this.round_radius,0,0]:[this.round_radius]):d==LiteGraph.CIRCLE_SHAPE&&t.arc(.5*s[0],.5*s[1],.5*s[0],0,2*Math.PI),t.strokeStyle=LiteGraph.WIDGET_OUTLINE_COLOR,t.stroke(),t.strokeStyle=n,t.fill(),!e.flags.collapsed&&c&&(t.shadowColor="transparent",t.fillStyle="rgba(0,0,0,0.2)",t.fillRect(0,-1,p[2],2)),t.shadowColor="transparent",e.onDrawBackground&&e.onDrawBackground(t,this,this.canvas,this.graph_mouse),c||u==LiteGraph.TRANSPARENT_TITLE){const i="dark"==function(e){let t=e.replace("#","");return s=parseInt(t.substring(0,2),16),n=parseInt(t.substring(2,4),16),i=parseInt(t.substring(4,6),16),.299*s+.587*n+.114*i>127.5?"light":"dark";var s,n,i}((null==e?void 0:e.color)||"#ffffff");if(e.onDrawTitleBar)e.onDrawTitleBar(t,l,s,this.ds.scale,n);else if(u!=LiteGraph.TRANSPARENT_TITLE&&(e.constructor.title_color||this.render_title_colored)){let i=e.constructor.title_color||n;if(e.flags.collapsed&&(t.shadowColor=LiteGraph.DEFAULT_SHADOW_COLOR),this.use_gradients){let e=LGraphCanvas.gradients[i];e||(e=LGraphCanvas.gradients[i]=t.createLinearGradient(0,0,400,0),e.addColorStop(0,i),e.addColorStop(1,"#000")),t.fillStyle=e}else t.fillStyle=i;t.beginPath(),d==LiteGraph.BOX_SHAPE||r?t.rect(0,-l,s[0]+1,l):d!=LiteGraph.ROUND_SHAPE&&d!=LiteGraph.CARD_SHAPE||t.roundRect(0,-l,s[0]+1,l,e.flags.collapsed?[this.round_radius]:[this.round_radius,this.round_radius,0,0]),t.fill(),t.shadowColor="transparent"}let a=!1;LiteGraph.node_box_coloured_by_mode&&LiteGraph.NODE_MODES_COLORS[e.mode]&&(a=LiteGraph.NODE_MODES_COLORS[e.mode]),LiteGraph.node_box_coloured_when_on&&(a=e.action_triggered?"#FFF":e.execute_triggered?"#AAA":a);let c=10;if(e.onDrawTitleBox)e.onDrawTitleBox(t,l,s,this.ds.scale);else if(d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CIRCLE_SHAPE||d==LiteGraph.CARD_SHAPE){const s=i?"#ffffff":LiteGraph.NODE_SELECTED_TITLE_COLOR,n=i?"#eeeeee":e.boxcolor||a||LiteGraph.NODE_DEFAULT_BOXCOLOR;t.fillStyle=o?s:n,t.beginPath(),t.fillRect(10,0-1.05*c-1,1.1*c,.125*c),t.fillRect(10,0-1.45*c-1,1.1*c,.125*c),t.fillRect(10,0-1.85*c-1,1.1*c,.125*c)}else t.fillStyle=e.boxcolor||a||LiteGraph.NODE_DEFAULT_BOXCOLOR,t.fillRect(.5*(l-c),-.5*(l+c),c,c);if(t.globalAlpha=h,e.onDrawTitleText&&e.onDrawTitleText(t,l,s,this.ds.scale,this.title_text_font,o),!r){t.font=this.title_text_font;let s=String(e.getTitle());s&&(t.fillStyle=o?i?"#ffffff":LiteGraph.NODE_SELECTED_TITLE_COLOR:i?"#ffffff":e.constructor.title_text_color||this.node_title_color,e.flags.collapsed?(t.textAlign="left",t.measureText(s),t.fillText(s.substr(0,20),l,LiteGraph.NODE_TITLE_TEXT_Y-l),t.textAlign="left"):(t.textAlign="left",t.fillText(s,l,LiteGraph.NODE_TITLE_TEXT_Y-l)))}if(!e.flags.collapsed&&e.subgraph&&!e.skip_subgraph_button){let s=LiteGraph.NODE_TITLE_HEIGHT,n=e.size[0]-s,i=LiteGraph.isInsideRectangle(this.graph_mouse[0]-e.pos[0],this.graph_mouse[1]-e.pos[1],n+2,2-s,s-4,s-4);t.fillStyle=i?"#888":"#555",d==LiteGraph.BOX_SHAPE||r?t.fillRect(n+2,2-s,s-4,s-4):(t.beginPath(),t.roundRect(n+2,2-s,s-4,s-4,[4]),t.fill()),t.fillStyle="#333",t.beginPath(),t.moveTo(n+.2*s,.6*-s),t.lineTo(n+.8*s,.6*-s),t.lineTo(n+.5*s,.3*-s),t.fill()}e.onDrawTitle&&e.onDrawTitle(t)}if(o){e.onBounding&&e.onBounding(p),u==LiteGraph.TRANSPARENT_TITLE&&(p[1]-=l,p[3]+=l),t.lineWidth=2,t.globalAlpha=.8,t.beginPath();let i=0,o=0,a=1;d==LiteGraph.BOX_SHAPE?t.rect(i+p[0],i+p[1],o+p[2],o+p[3]):d==LiteGraph.ROUND_SHAPE||d==LiteGraph.CARD_SHAPE&&e.flags.collapsed?t.roundRect(i+p[0],i+p[1],o+p[2],o+p[3],[this.round_radius*a]):d==LiteGraph.CARD_SHAPE?t.roundRect(i+p[0],i+p[1],o+p[2],o+p[3],[this.round_radius*a,a,this.round_radius*a,a]):d==LiteGraph.CIRCLE_SHAPE&&t.arc(.5*s[0],.5*s[1],.5*s[0]+6,0,2*Math.PI),t.strokeStyle=LiteGraph.NODE_BOX_OUTLINE_COLOR,t.stroke(),t.strokeStyle=n,t.globalAlpha=1}e.execute_triggered>0&&e.execute_triggered--,e.action_triggered>0&&e.action_triggered--}function de(e,t,s,n){var i,o;if(!e.widgets||!e.widgets.length)return 0;let a=e.size[0],l=(e.size[1],e.widgets);t+=2;let r=LiteGraph.NODE_WIDGET_HEIGHT,d=this.ds.scale>.5;s.save(),s.globalAlpha=this.editor_alpha;let u=LiteGraph.WIDGET_OUTLINE_COLOR,c=LiteGraph.WIDGET_BGCOLOR,p=LiteGraph.WIDGET_TEXT_COLOR,h=LiteGraph.WIDGET_SECONDARY_TEXT_COLOR,m=12;for(let f=0;f1&&(a=1),s.fillStyle=y.options.hasOwnProperty("slider_color")?y.options.slider_color:n==y?u:O,s.beginPath(),s.roundRect(m,_,a*(v-24),r,[.25*r]),s.fill(),y.marker){let e=(y.marker-y.options.min)/t;e<0&&(e=0),e>1&&(e=1),s.fillStyle=y.options.hasOwnProperty("marker_color")?y.options.marker_color:"#AA9",s.roundRect(m+e*(v-24),_,2,r,[.25*r])}if(d){s.textAlign="center",s.fillStyle=p;let e=(y.label||y.name)+" : "+Number(y.value).toFixed(null!=y.options.precision?y.options.precision:3);s.fillText(e,.5*v,_+.7*r)}break;case"number":case"combo":if(s.textAlign="left",s.strokeStyle=u,s.fillStyle=c,s.beginPath(),d?s.roundRect(m,_,v-24,r,[.25*r]):s.rect(m,_,v-24,r),s.fill(),d){y.disabled||s.stroke(),s.fillStyle=p,y.disabled||(s.beginPath(),s.moveTo(24,_+6.5),s.lineTo(18,_+.5*r),s.lineTo(24,_+r-6.5),s.fill(),s.beginPath(),s.moveTo(v-m-12,_+6.5),s.lineTo(v-m-6,_+.5*r),s.lineTo(v-m-12,_+r-6.5),s.fill()),s.fillStyle=h,s.font="10px Inter",s.fillText(y.label||y.name,29,_+.7*r),s.fillStyle=p,s.textAlign="right";let e=6;if("number"==y.type)s.font="10px Inter",s.fillText(Number(y.value).toFixed(void 0!==y.options.precision?y.options.precision:3),v-24-e,_+.7*r);else{let t=y.value;if(y.options.values){let e=y.options.values;e.constructor===Function&&(e=e()),e&&e.constructor!==Array&&(t=e[y.value])}const n=v-48-(s.measureText(y.label||y.name).width+24),i=s.measureText(t).width;if(i>n){const e="…",o=s.measureText(e).width,a=s.measureText("a").width;if(n<=o)t="․";else{t=`${t}`;if(i+o-n+3*a>n){const e=n+3*a,s=Math.floor((e-o)/a);t=t.substr(0,s)}for(;s.measureText(t).width+o>n;)t=t.substr(0,t.length-1);t+=e}}s.fillText(t,v-24-e,_+.7*r)}}break;case"string":case"text":if(s.textAlign="left",s.strokeStyle=u,s.fillStyle=c,s.beginPath(),d?s.roundRect(m,_,v-24,r,[.25*r]):s.rect(m,_,v-24,r),s.fill(),d){y.disabled||s.stroke(),s.save(),s.beginPath(),s.rect(m,_,v-24,r),s.clip(),s.fillStyle=h;const e=y.label||y.name;s.font="10px Inter",null!=e&&s.fillText(e,24,_+.7*r),s.fillStyle=p,s.textAlign="right",s.fillText(String(y.value).substr(0,30),v-24,_+.7*r),s.restore()}break;default:y.draw&&y.draw(s,e,v,_,r)}t+=(y.computeSize?y.computeSize(v)[1]:r)+4,s.globalAlpha=this.editor_alpha}s.restore(),s.textAlign="left"}function ue(e,t,s,n,i){return new LiteGraph.ContextMenu(LiteGraph.NODE_MODES,{event:s,callback:function(e){if(!i)return;var t=Object.values(LiteGraph.NODE_MODES).indexOf(e),s=function(e){t>=0&&LiteGraph.NODE_MODES[t]?e.changeMode(t):e.changeMode(LiteGraph.ALWAYS),q||(q=$()),q.update()},n=LGraphCanvas.active_canvas;if(!n.selected_nodes||Object.keys(n.selected_nodes).length<=1)s(i);else for(var o in n.selected_nodes)s(n.selected_nodes[o])},parentMenu:n,node:i}),!1}function ce(e,t,s,n,i){if(!i)throw"no node for color";var o=[];for(var a in o.push({value:null,content:"No color"}),LGraphCanvas.node_colors){var l=LGraphCanvas.node_colors[a];e={value:a,content:""+a+""};o.push(e)}return new LiteGraph.ContextMenu(o,{event:s,callback:function(e){if(!i)return;var t=e.value?LGraphCanvas.node_colors[e.value]:null,s=function(e){t?e.constructor===LiteGraph.LGraphGroup?e.color=t.groupcolor:(e.color=t.color,e.bgcolor=t.bgcolor):(delete e.color,delete e.bgcolor),q||(q=$()),q.update()},n=LGraphCanvas.active_canvas;if(!n.selected_nodes||Object.keys(n.selected_nodes).length<=1)s(i);else for(var o in n.selected_nodes)s(n.selected_nodes[o]);i.setDirtyCanvas(!0,!0)},parentMenu:n,node:i}),!1}function pe(e,t,s,n,i){var o=e.property||"title",a=i[o],l=document.createElement("div");l.is_modified=!1,l.className="graphdialog",l.innerHTML="",l.close=function(){l.parentNode&&l.parentNode.removeChild(l)},l.querySelector(".name").innerText=o;var r=l.querySelector(".value");r&&(r.value=a,r.addEventListener("blur",(function(e){this.focus()})),r.addEventListener("keydown",(function(e){if(l.is_modified=!0,27==e.keyCode)l.close();else if(13==e.keyCode)m();else if(13!=e.keyCode&&"textarea"!=e.target.localName)return;e.preventDefault(),e.stopPropagation()})));var d=LGraphCanvas.active_canvas.canvas,u=d.getBoundingClientRect(),c=-20,p=-20;u&&(c-=u.left,p-=u.top),event?(l.style.left=event.clientX+c+"px",l.style.top=event.clientY+p+"px"):(l.style.left=.5*d.width+c+"px",l.style.top=.5*d.height+p+"px"),l.querySelector("button").addEventListener("click",m),d.parentNode.appendChild(l),r&&r.focus();var h=null;function m(){r&&function(t){"Number"==e.type?t=Number(t):"Boolean"==e.type&&(t=Boolean(t));i[o]=t,l.parentNode&&l.parentNode.removeChild(l);i.setDirtyCanvas(!0,!0),q||(q=$());q.update()}(r.value)}l.addEventListener("mouseleave",(function(e){LiteGraph.dialog_close_on_mouse_leave&&!l.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(h=setTimeout(l.close,LiteGraph.dialog_close_on_mouse_leave_delay))})),l.addEventListener("mouseenter",(function(e){LiteGraph.dialog_close_on_mouse_leave&&h&&clearTimeout(h)}))}v.registerExtension({name:"Comfy.EasyUse.UI",init(){var e,t;const s="Comfy.CustomColorPalettes",n="Comfy.Settings.Comfy.CustomColorPalettes";if(ne||(ne=A(s,n,{})),ie||(ie=A("Comfy.ColorPalette","Comfy.Settings.Comfy.ColorPalette")||"dark"),(!(null==(e=null==ne?void 0:ne.obsidian)?void 0:e.version)||ne.obsidian.version{(null==e?void 0:e.value)&&(null==e?void 0:e.oldValue)&&(await X(1),Object.assign(v.canvas.default_connection_color_byType,D),Object.assign(LGraphCanvas.link_type_colors,D)),"custom_milk_white"==e.value&&document.body.classList.remove(T)})),setTimeout((e=>he(A("Comfy.UseNewMenu")||"Disabled")),1);const t=null==(e=v.ui.settings.settingsLookup)?void 0:e["Comfy.UseNewMenu"];t&&(t.onChange=e=>he(e))},async nodeCreated(e){var t;if(ee.hasOwnProperty(e.comfyClass)){const t=ee[e.comfyClass],s=te[t];if(!s)return;s.color&&(e.color=s.color),s.bgcolor&&(e.bgcolor=s.bgcolor)}if(se||(se=A("Comfy.WidgetControlMode")),"before"==se){const s="before"==se;if((null==(t=e.widgets)?void 0:t.length)>0)for(const t of e.widgets)if(["control_before_generate","control_after_generate"].includes(t.name)&&(await le(t,s),t.linkedWidgets))for(const e of t.linkedWidgets)await le(e,s)}}});const he=e=>{var t;const s=(null==(t=document.getElementById("crystools-root"))?void 0:t.children)||null,n=A("Comfy.Workflow.WorkflowTabsPosition",null,"");if((null==s?void 0:s.length)>0&&n)if(oe||(oe=document.getElementById("MonitorUI")),"Disabled"==e){document.getElementById("crystools-root").appendChild(oe)}else{let e=document.getElementById("crystools-root-easyuse");if(e)e.appendChild(oe);else{const e=document.getElementsByClassName("comfyui-menu-right");e.length>0&&e[0].before(w("div",{id:"crystools-root-easyuse"},oe))}}};let me={};const ge=(e,t)=>e.widgets.find((e=>e.name===t)),fe=(e,t,s=!1,n="")=>{var i;if(!t||((e,t)=>!!e.inputs&&e.inputs.some((e=>e.name===t)))(e,t.name))return;me[t.name]||(me[t.name]={origType:t.type,origComputeSize:t.computeSize});const o=e.size;t.type=s?me[t.name].origType:"easyHidden"+n,t.computeSize=s?me[t.name].origComputeSize:()=>[0,-4],null==(i=t.linkedWidgets)||i.forEach((n=>fe(e,n,":"+t.name,s)));const a=s?Math.max(e.computeSize()[1],o[1]):e.size[1];e.setSize([e.size[0],a])},ye=(e,t=0)=>{var s,n;if(e)return(null==(s=e.widgets)?void 0:s[t])?e.widgets[t].value:e.widgets_values?null==(n=e.widgets_values)?void 0:n[t]:void 0},_e=e=>e.setSize([e.size[0],e.computeSize()[1]]),ve=(e,t)=>graph.getNodeById(e),be=e=>{var t;try{return Object.values(null==(t=null==graph?void 0:graph.list_of_graphcanvas[0])?void 0:t.selected_nodes)}catch(s){return[]}};function we(e,t,s){return e+(n=s,(.5-.5*Math.cos(Math.PI*n))*(t-e));var n}const Le=(e,t=!0)=>{var s,n;const i=(null==(n=null==(s=e.graph)?void 0:s.list_of_graphcanvas)?void 0:n[0])||null;if(!i)return;const[o,a]=e.pos,[l,r]=e.size;(([e,t],s)=>{const n=s.ds,i=document.body.clientWidth,o=document.body.clientHeight,a=n.scale,l=.5*i/a-e,r=.5*o/a-t,d=Date.now()+250,u=n.offset[0],c=n.offset[1],p=()=>{const e=d-Date.now();if(!(Date.now(){const t=ve(e);t&&Le(t)},Ee=(e,t=(()=>graph.links??[])())=>t[e],Ce=e=>e.toLowerCase().replace(/_./g,(e=>e.replace("_","").toUpperCase())),ke=e=>"easy getNode"===e.type,Ae=e=>"easy setNode"===e.type,xe=e=>ke(e)||Ae(e),Ie=(e=(()=>graph._nodes??[])())=>e.filter((e=>xe(e)));let Ne={},Te={};const Oe=(e,t,s=0)=>{e.widgets_values||(e.widgets_values=[]),e.widgets_values[s]=t,e.widgets[s].value=t},De=e=>graph.add(e),Re=e=>graph.remove(e),Ge=(e,t=0)=>{var s,n;if("Reroute"!==e.type)return[e,t];const i=e,o=null==(n=null==(s=i.inputs)?void 0:s[0])?void 0:n.link;if(!o)return[i,t];const a=Ee(o);if(!a)return[i,t];const l=ve(a.origin_id);return l?(setTimeout((()=>{Re(i)})),Ge(l,a.origin_slot)):[i,t]},Me=e=>{var t,s,n;if("Reroute"!==e.type)return e;const i=e,o=null==(s=null==(t=i.outputs)?void 0:t[0])?void 0:s.links;if(!o)return i;const a=o[0];if(!a)return i;const l=Ee(a);if(!l)return i;const r=ve(l.target_id);return r?(1===(null==(n=i.outputs[0].links)?void 0:n.length)&&setTimeout((()=>{Re(i)})),Me(r)):i},Pe=(e,t="width")=>{var s;const n=e[0],i="width"==t?0:1,o=null==(s=n.size)?void 0:s[i];o&&(e.forEach((e=>{e.size[i]=o})),LGraphCanvas.active_canvas.setDirty(!0,!0))},Fe=(e,t="horizontal")=>{if(e.length<3)return;const s="horizontal"===t?0:1;e.sort(((e,t)=>e.pos[s]-t.pos[s]));const n=Math.min(...e.map((e=>e.pos[s]))),i=(Math.max(...e.map((e=>e.pos[s]+e.size[s])))-n-e.reduce(((e,t)=>e+t.size[s]),0))/(e.length-1);let o=n;e.forEach((e=>{e.pos[s]=o,o+=e.size[s]+i})),LGraphCanvas.active_canvas.setDirty(!0,!0)};const Ue=new class{constructor(){g(this,"element",w(`div.${N}toast`)),g(this,"children",HTMLElement),g(this,"container",document.body),this.container.appendChild(this.element)}async show(e){let t=w(`div.${N}toast-container`,[w("div",[w("span",[...e.icon?[w("i",{className:e.icon})]:[],w("span",e.content)])])]);t.setAttribute("toast-id",e.id),this.element.replaceChildren(t),this.container.appendChild(this.element),await X(64),t.style.marginTop=`-${t.offsetHeight}px`,await X(64),t.classList.add("show"),e.duration&&(await X(e.duration),this.hide(e.id))}async hide(e){const t=document.querySelector(`.${N}toast > [toast-id="${e}"]`);(null==t?void 0:t.classList.contains("show"))&&(t.classList.remove("show"),await X(750)),t&&t.remove()}async clearAllMessages(){let e=document.querySelector(`.${N}container`);e&&(e.innerHTML="")}async info(e,t=3e3,s=[]){this.show({id:"toast-info",icon:`mdi mdi-information ${N}theme`,content:e,duration:t})}async success(e,t=3e3){this.show({id:"toast-success",icon:`mdi mdi-check-circle ${N}success`,content:e,duration:t})}async error(e,t=3e3){this.show({id:"toast-error",icon:`mdi mdi-close-circle ${N}error`,content:e,duration:t})}async warn(e,t=3e3){this.show({id:"toast-warn",icon:`mdi mdi-alert-circle ${N}warning`,content:e,duration:t})}async showLoading(e,t=0){this.show({id:"toast-loading",icon:"mdi mdi-rotate-right loading",content:e,duration:t})}async hideLoading(){this.hide("toast-loading")}},Be=["rescale_after_model","rescale","lora_name","upscale_method","image_output","add_noise","info","sampler_name","ckpt_B_name","ckpt_C_name","save_model","refiner_ckpt_name","num_loras","num_controlnet","mode","toggle","resolution","ratio","target_parameter","input_count","replace_count","downscale_mode","range_mode","text_combine_mode","input_mode","lora_count","ckpt_count","conditioning_mode","preset","use_tiled","use_batch","num_embeds","easing_mode","guider","scheduler","inpaint_mode","t5_type","rem_mode","encode"],ze=["LIGHT - SD1.5 only (low strength)","STANDARD (medium strength)","VIT-G (medium strength)","REGULAR - FLUX and SD3.5 only (high strength)","PLUS (high strength)","PLUS (kolors genernal)","PLUS FACE (portraits)","FULL FACE - SD1.5 only (portraits stronger)","COMPOSITION"],We=["FACEID","FACEID PLUS - SD1.5 only","FACEID PLUS V2","FACEID PLUS KOLORS","FACEID PORTRAIT (style transfer)","FACEID PORTRAIT UNNORM - SDXL only (strong)"],je=["easy seed","easy latentNoisy","easy wildcards","easy preSampling","easy preSamplingAdvanced","easy preSamplingNoiseIn","easy preSamplingSdTurbo","easy preSamplingCascade","easy preSamplingDynamicCFG","easy preSamplingLayerDiffusion","easy fullkSampler","easy fullCascadeKSampler"],Ve=["easy fullLoader","easy a1111Loader","easy comfyLoader","easy hyditLoader","easy pixArtLoader"],Ye=["easy imageSize","easy imageSizeBySide","easy imageSizeByLongerSide","easy imageSizeShow","easy imageRatio","easy imagePixelPerfect"],He=["easy forLoopStart","easy forLoopEnd","easy whileLoopStart","easy whileLoopEnd"],Xe=["easy anythingIndexSwitch","easy imageIndexSwitch","easy textIndexSwitch","easy conditioningIndexSwitch"],Ze=["easy anythingInversedSwitch"],Ke=["easy loadImagesForLoop",...He,...Xe,...Ze],Je={"easy anythingInversedSwitch":"out","easy anythingIndexSwitch":"value","easy imageIndexSwitch":"image","easy textIndexSwitch":"text","easy conditioningIndexSwitch":"cond"};function $e(e,t){const s=e.comfyClass;let n=t.value;switch(t.name){case"range_mode":fe(e,ge(e,"step"),"step"==n),fe(e,ge(e,"num_steps"),"num_steps"==n),_e(e);break;case"text_combine_mode":fe(e,ge(e,"replace_text"),"replace"==n);break;case"lora_name":["lora_model_strength","lora_clip_strength"].map((t=>fe(e,ge(e,t),"None"!==n)));break;case"resolution":"自定义 x 自定义"===n&&(t.value="width x height (custom)"),["empty_latent_width","empty_latent_height","width","height"].map((t=>fe(e,ge(e,t),"width x height (custom)"===n)));break;case"ratio":["empty_latent_width","empty_latent_height"].map((t=>fe(e,ge(e,t),"custom"===n)));break;case"num_loras":var i=n+1,o=ge(e,"mode").value;for(let t=0;tfe(e,ge(e,t),"simple"!==o)));for(let t=i;t<21;t++)["lora_"+t+"_name","lora_"+t+"_strength","lora_"+t+"_model_strength","lora_"+t+"_clip_strength"].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"num_controlnet":i=n+1,o=ge(e,"mode").value;for(let t=0;tfe(e,ge(e,t),!0))),["start_percent_"+t,"end_percent_"+t].map((t=>fe(e,ge(e,t),"simple"!==o)));for(let t=i;t<21;t++)["controlnet_"+t,"controlnet_"+t+"_strength","scale_soft_weight_"+t,"start_percent_"+t,"end_percent_"+t].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"mode":switch(null==e?void 0:e.comfyClass){case"easy loraStack":i=ge(e,"num_loras").value+1,o=n;for(let t=0;tfe(e,ge(e,t),"simple"!==o)));_e(e);break;case"easy controlnetStack":i=ge(e,"num_controlnet").value+1,o=n;for(let t=0;tfe(e,ge(e,t),"simple"!==o)));_e(e);break;case"easy icLightApply":o=n;["lighting","remove_bg"].map((t=>fe(e,ge(e,t),"Foreground"===o))),fe(e,ge(e,"source"),"Foreground"!==o),_e(e)}break;case"toggle":t.type="toggle",t.options={on:"Enabled",off:"Disabled"};break;case"t5_type":["clip_name","padding"].map((t=>fe(e,ge(e,t),"sd3"==n))),["t5_name","device","dtype"].map((t=>fe(e,ge(e,t),"t5v11"==n))),_e(e);break;case"preset":if("FLUX.1-dev"==n&&(t.value="REGULAR - FLUX and SD3.5 only (high strength)"),ze.includes(n)){let t=ge(e,"use_tiled");fe(e,ge(e,"lora_strength")),fe(e,ge(e,"provider"),!!["REGULAR - FLUX and SD3.5 only (high strength)"].includes(n)),fe(e,ge(e,"weight_faceidv2")),fe(e,ge(e,"weight_kolors")),fe(e,ge(e,"use_tiled"),!0),fe(e,ge(e,"sharpening"),t&&t.value)}else We.includes(n)&&(fe(e,ge(e,"weight_faceidv2"),!!["FACEID PLUS V2","FACEID PLUS KOLORS"].includes(n)),fe(e,ge(e,"weight_kolors"),!!["FACEID PLUS KOLORS"].includes(t.value)),["FACEID PLUS KOLORS","FACEID PORTRAIT (style transfer)","FACEID PORTRAIT UNNORM - SDXL only (strong)"].includes(n)?fe(e,ge(e,"lora_strength"),!1):fe(e,ge(e,"lora_strength"),!0),fe(e,ge(e,"provider"),!0),fe(e,ge(e,"use_tiled")),fe(e,ge(e,"sharpening")));_e(e);break;case"use_tiled":fe(e,ge(e,"sharpening"),!!n),_e(e);break;case"num_embeds":i=n+1;for(let t=0;tfe(e,ge(e,t),!1)));break;case"brushnet_random":case"brushnet_segmentation":["dtype","scale","start_at","end_at"].map((t=>fe(e,ge(e,t),!0))),["fitting","function"].map((t=>fe(e,ge(e,t),!1)));break;case"powerpaint":["dtype","fitting","function","scale","start_at","end_at"].map((t=>fe(e,ge(e,t),!0)))}_e(e);break;case"encode":fe(e,ge(e,"noise_mask"),!!["inpaint_model_conditioning","different_diffusion"].includes(n)),_e(e);break;case"image_output":fe(e,ge(e,"link_id"),!!["Sender","Sender&Save"].includes(n)),fe(e,ge(e,"decode_vae_name"),!!["Hide","Hide&Save"].includes(n)),["save_prefix","output_path","embed_workflow","number_padding","overwrite_existing"].map((t=>fe(e,ge(e,t),!!["Save","Hide&Save","Sender&Save"].includes(n))));break;case"add_noise":var a=ge(e,"control_before_generate"),l=ge(e,"control_after_generate")||a;"disable"===n?(fe(e,ge(e,"seed")),l&&(l.last_value=l.value,l.value="fixed",fe(e,l))):("enable"===n&&(t.value="enable (CPU)"),fe(e,ge(e,"seed"),!0),l&&((null==l?void 0:l.last_value)&&(l.value=l.last_value),fe(e,l,!0))),_e(e);break;case"guider":switch(n){case"Basic":case"IP2P+Basic":["cfg_negative"].map((t=>fe(e,ge(e,t))));break;case"CFG":case"IP2P+CFG":fe(e,ge(e,"cfg"),!0),fe(e,ge(e,"cfg_negative"));break;case"DualCFG":case"IP2P+DualCFG":["cfg","cfg_negative"].map((t=>fe(e,ge(e,t),!0)))}_e(e);break;case"scheduler":["karrasADV","exponentialADV","polyExponential"].includes(n)?(["sigma_max","sigma_min"].map((t=>fe(e,ge(e,t),!0))),["denoise","beta_d","beta_min","eps_s","coeff"].map((t=>fe(e,ge(e,t))),!1),fe(e,ge(e,"rho"),"exponentialADV"!=n)):"vp"==n?(["sigma_max","sigma_min","denoise","rho","coeff"].map((t=>fe(e,ge(e,t)))),["beta_d","beta_min","eps_s"].map((t=>fe(e,ge(e,t),!0)))):(["sigma_max","sigma_min","beta_d","beta_min","eps_s","rho"].map((t=>fe(e,ge(e,t)))),fe(e,ge(e,"coeff"),"gits"==n),fe(e,ge(e,"denoise"),!0)),_e(e);break;case"conditioning_mode":["replace","concat","combine"].includes(n)?["average_strength","old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t)))):"average"==n?(fe(e,ge(e,"average_strength"),!0),["old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t),!1)))):"timestep"==n&&(["average_strength"].map((t=>fe(e,ge(e,t),!1))),["old_cond_start","old_cond_end","new_cond_start","new_cond_end"].map((t=>fe(e,ge(e,t)))));break;case"rescale":ge(e,"rescale_after_model").value,fe(e,ge(e,"width"),"to Width/Height"===n),fe(e,ge(e,"height"),"to Width/Height"===n),fe(e,ge(e,"percent"),"by percentage"===n),fe(e,ge(e,"longer_side"),"to longer side - maintain aspect"===n),_e(e);break;case"upscale_method":["factor","crop"].map((t=>fe(e,ge(e,t),"None"!==n)));break;case"target_parameter":switch(s){case"easy XYInputs: Steps":["first_step","last_step"].map((t=>fe(e,ge(e,t),"steps"==n))),["first_start_step","last_start_step"].map((t=>fe(e,ge(e,t),"start_at_step"==n))),["first_end_step","last_end_step"].map((t=>fe(e,ge(e,t),"end_at_step"==n)));break;case"easy XYInputs: Sampler/Scheduler":let t=ge(e,"input_count").value+1;for(let s=0;sfe(e,ge(e,t),"strength"==n))),["first_start_percent","last_start_percent"].map((t=>fe(e,ge(e,t),"start_percent"==n))),["first_end_percent","last_end_percent"].map((t=>fe(e,ge(e,t),"end_percent"==n))),["strength","start_percent","end_percent"].map((t=>fe(e,ge(e,t),n!=t))),_e(e)}case"replace_count":i=n+1;for(let t=0;tfe(e,ge(e,t),!r)));for(let t=i;t<11;t++)["lora_name_"+t,"model_str_"+t,"clip_str_"+t].map((t=>fe(e,ge(e,t),!1)));_e(e);break;case"ckpt_count":i=n+1;var d=-1!=ge(e,"input_mode").value.indexOf("ClipSkip"),u=-1!=ge(e,"input_mode").value.indexOf("VAE");for(let t=0;tfe(e,ge(e,t),!1)));_e(e);break;case"input_count":i=n+1;var c=ge(e,"target_parameter").value;for(let t=0;tfe(e,ge(e,s),!!t)));["model_strength","clip_strength"].map((s=>fe(e,ge(e,s),!t)));break;case"easy XYInputs: Checkpoint":i=ge(e,"ckpt_count").value+1,d=-1!=ge(e,"input_mode").value.indexOf("ClipSkip"),u=-1!=ge(e,"input_mode").value.indexOf("VAE");for(let s=0;se.name===t));if(-1!==e){for(let t=e;t{var e;const t=this.computeSize();t[0]"info"===e.name));if(-1!==e&&this.widgets[e]){this.widgets[e].value=t}}requestAnimationFrame((()=>{var e;const t=this.computeSize();t[0]"prompt"==e.name));this.addWidget("button","get values from COMBO link","",(()=>{var t,n;const i=(null==(n=null==(t=this.outputs[1])?void 0:t.links)?void 0:n.length)>0?this.outputs[1].links[0]:null,o=s.graph._nodes.find((e=>{var t;return null==(t=e.inputs)?void 0:t.find((e=>e.link==i))}));if(i&&o){const t=o.inputs.find((e=>e.link==i)).widget.name,s=o.widgets.find((e=>e.name==t));let n=(null==s?void 0:s.options.values)||null;n&&(n=n.join("\n"),e.value=n)}else Ue.error(Y("No COMBO link"),3e3)}),{serialize:!1})}),Ve.includes(t.name)){let t=function(e){var t="";for(let s=0;se.name===t+"_prompt")),n="comfy-multiline-input wildcard_"+t+"_"+this.id.toString();if(-1==s&&e){const s=document.createElement("textarea");s.className=n,s.placeholder="Wildcard Prompt ("+t+")";const i=this.addDOMWidget(t+"_prompt","customtext",s,{getValue:e=>s.value,setValue(e){s.value=e},serialize:!1});i.inputEl=s,i.inputEl.readOnly=!0,s.addEventListener("input",(()=>{var e;null==(e=i.callback)||e.call(i,i.value)})),i.value=e}else if(this.widgets[s])if(e){this.widgets[s].value=e}else{this.widgets.splice(s,1);const e=document.getElementsByClassName(n);e&&e[0]&&e[0].remove()}}};e.prototype.onExecuted=function(e){null==l||l.apply(this,arguments);const n=t(e.positive),i=t(e.negative);s.call(this,n,"positive"),s.call(this,i,"negative")}}if(["easy sv3dLoader"].includes(t.name)){let t=function(e,t,s){switch(e){case"azimuth":return s.readOnly=!0,s.style.opacity=.6,"0:(0.0,0.0)"+(t>1?`\n${t-1}:(360.0,0.0)`:"");case"elevation":return s.readOnly=!0,s.style.opacity=.6,"0:(-90.0,0.0)"+(t>1?`\n${t-1}:(90.0,0.0)`:"");case"custom":return s.readOnly=!1,s.style.opacity=1,"0:(0.0,0.0)\n9:(180.0,0.0)\n20:(360.0,0.0)"}};e.prototype.onNodeCreated=async function(){o&&o.apply(this,[]);const e=this.widgets.find((e=>"easing_mode"==e.name)),s=this.widgets.find((e=>"batch_size"==e.name)),n=this.widgets.find((e=>"scheduler"==e.name));setTimeout((i=>{n.value||(n.value=t(e.value,s.value,n.inputEl))}),1),e.callback=e=>{n.value=t(e,s.value,n.inputEl)},s.callback=s=>{n.value=t(e.value,s,n.inputEl)}}}if(je.includes(n)&&(e.prototype.onNodeCreated=async function(){o&&o.apply(this,[]);const e=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),n=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));if("easy seed"==t.name){const t=this.addWidget("button","🎲 Manual Random Seed",null,(t=>{"fixed"!=n.value&&(n.value="fixed"),e.value=Math.floor(Math.random()*R),s.queuePrompt(0,1)}),{serialize:!1});e.linkedWidgets=[t,n]}},e.prototype.onAdded=async function(){i&&i.apply(this,[]);const e=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),t=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));setTimeout((s=>{"control_before_generate"==t.name&&0===e.value&&(e.value=Math.floor(Math.random()*R))}),1)}),"easy convertAnything"==n&&(e.prototype.onNodeCreated=async function(){o&&o.apply(this,[]);const e=this.widgets.find((e=>"output_type"==e.name)),t=t=>{this.outputs[0].type=e.value.toUpperCase(),this.outputs[0].name=e.value,this.outputs[0].label=e.value};setTimeout((e=>t()),10),e.callback=e=>t()}),"easy imageInsetCrop"==n){let t=function(e){const t=e.widgets[0];for(let s=1;s<=4;s++)"Pixels"===t.value?(e.widgets[s].options.step=80,e.widgets[s].options.max=8192):(e.widgets[s].options.step=10,e.widgets[s].options.max=99)};e.prototype.onAdded=async function(e){const s=this.widgets[0];let n=s.callback;s.callback=(...e)=>{t(this),n&&n.apply(s,[...e])},setTimeout((e=>{t(this)}),1)}}if(Ke.includes(n)){const t=e=>{switch(n){case"easy forLoopStart":return 0;case"easy forLoopEnd":return 1}},s=e=>{switch(n){case"easy forLoopStart":return 2;case"easy forLoopEnd":return 0}};e.prototype.onNodeCreated=async function(){if("easy loadImagesForLoop"==n&&(this.outputs[0].shape=5),He.includes(n)){const e=this.inputs.findIndex((e=>"flow"===e.name)),i=this.outputs.findIndex((e=>"flow"===e.name));if(-1!==e&&(this.inputs[e].shape=5),-1!==i&&(this.outputs[i].shape=5),"easy whileLoopStart"==n||"easy whileLoopEnd"==n)return;this.inputs=this.inputs.filter(((e,s)=>s<=t())),this.outputs=this.outputs.filter(((e,t)=>t<=s())),_e(this)}return Xe.includes(n)&&("easy textIndexSwitch"==n&&(this.widgets=this.widgets.filter(((e,t)=>t<=2))),this.inputs=this.inputs.filter(((e,t)=>t<=1)),_e(this)),null==o?void 0:o.apply(this,arguments)},e.prototype.onConnectionsChange=function(e,i,o,a){var l,r;if("easy whileLoopStart"!=n&&"easy whileLoopEnd"!=n&&a)if(1==e){let e=this.inputs.every((e=>null!==e.link)),s=this.inputs.filter((e=>!["condition","index","total"].includes(e.name)));if(He.includes(n)){if(e){if(s.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let e=s[s.length-1],t=parseInt(e.name.split("initial_value")[1])+1;if(this.inputs.find((e=>e.name==="initial_value"+t)))return;let n="initial_value"+t,i="value"+t;this.addInput(n,"*"),this.addOutput(i,"*")}else if(!o){const e=t();let s=this.inputs.findLastIndex((e=>e.link));if(i>=e&&(-1===s||i>=s)){let e=this.inputs[i];if(!e.name||["condition","total"].includes(e.name))return;let t=parseInt(e.name.split("initial_value")[1])+1,s=this.inputs.findIndex((e=>e.name==="initial_value"+t)),n=this.outputs.findIndex((e=>e.name==="value"+t));-1!==s&&this.removeInput(s),-1!==n&&this.removeOutput(n)}}}else if(Xe.includes(n))if(e){if(s.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let e=Je[n]+s.length;this.addInput(e,"*")}else o||i==this.inputs.length-2&&this.removeInput(i+1)}else if(2==e){let e=this.outputs.filter((e=>!["flow","index"].includes(e.name))),t=e.every((e=>{var t;return(null==(t=e.links)?void 0:t.length)>0}));if(He.includes(n)){if(t){if(e.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let t=e[e.length-1],s=parseInt(t.name.split("value")[1])+1;if(this.inputs.find((e=>e.name==="initial_value"+s)))return;if(this.outputs.find((e=>e.name==="value"+s)))return;let n="initial_value"+s,i="value"+s;this.addInput(n,"*"),this.addOutput(i,"*")}else if(!o){const e=s();let t=a.origin_slot,n=this.outputs.findLastIndex((e=>{var t;return(null==(t=e.links)?void 0:t.length)>0}));if(t>=e&&(-1===n||t>=n)){let e=this.outputs[t];if(!e.name||["flow","index"].includes(e.name))return;let s=parseInt(e.name.split("value")[1])+1,n=this.inputs.findIndex((e=>e.name==="initial_value"+s)),i=this.outputs.findIndex((e=>e.name==="value"+s));if(-1!==n&&(null==(l=this.inputs[n])?void 0:l.link))return;-1!==n&&this.removeInput(n),-1!==i&&this.removeOutput(i)}}}else if(Ze.includes(n))if(t){if(e.length>=10)return void Ue.warn(Y("The maximum number of inputs is 10"));let t=Je[n]+e.length;this.addOutput(t,"*")}else if(!o){let t=a.origin_slot;t==this.outputs.length-2&&0==(null==(r=e[t].links)?void 0:r.length)&&this.removeOutput(t+1)}}}}["easy fluxLoader","easy fullLoader"].includes(n)&&(e.prototype.onConnectionsChange=async function(e,t){r&&r.apply(this,[]);const s=this.inputs.find((e=>"model_override"===e.name)),n=this.inputs.find((e=>"vae_override"===e.name));fe(this,ge(this,"ckpt_name"),!(null==s?void 0:s.link)),fe(this,ge(this,"vae_name"),!(null==n?void 0:n.link))})},nodeCreated(e){if(e.comfyClass.startsWith("easy ")){if(e.widgets)for(const s of e.widgets){if(!Be.includes(s.name))continue;let t=s.value;$e(e,s),Object.defineProperty(s,"value",{get:e=>t,set(n){n!==t&&(t=n,$e(e,s))}})}const t=e.comfyClass;if("easy preDetailerFix"==t){const t=e.widgets.find((e=>"customtext"===e.type));if(!t)return;t.dynamicPrompts=!1,t.inputEl.placeholder="wildcard spec: if kept empty, this option will be ignored",t.serializeValue=()=>t.value}if("easy wildcards"==t){const t=e.widgets.find((e=>"text"==e.name));let s=1;Object.defineProperty(e.widgets[s],"value",{set:e=>{if("Select the LoRA to add to the text"!=e){let s=e;s.endsWith(".safetensors")&&(s=s.slice(0,-12)),t.value+=``}},get:e=>"Select the LoRA to add to the text"}),Object.defineProperty(e.widgets[s+1],"value",{set:e=>{"Select the Wildcard to add to the text"!=e&&(""!=t.value&&(t.value+=", "),t.value+=e)},get:e=>"Select the Wildcard to add to the text"}),e.widgets[s].serializeValue=e=>"Select the LoRA to add to the text",e.widgets[s+1].serializeValue=e=>"Select the Wildcard to add to the text"}if(Ye.includes(t)){const t=document.createElement("textarea");t.className="comfy-multiline-input",t.readOnly=!0;const s=e.addDOMWidget("info","customtext",t,{getValue:e=>t.value,setValue:e=>t.value=e,serialize:!1});s.inputEl=t,t.addEventListener("input",(()=>{var e;null==(e=s.callback)||e.call(s,s.value)}))}}}});const qe=LiteGraph.LGraphNode;v.registerExtension({name:"easy bookmark",registerCustomNodes(){class e extends qe{constructor(){super("🔖"),g(this,"type","easy bookmark"),g(this,"title","🔖"),g(this,"slot_start_y",-20),g(this,"___collapsed_width",0),g(this,"isVirtualNode",!0),g(this,"serialize_widgets",!0),g(this,"keypressBound",null),this.addWidget("text","shortcut_key","1",(e=>{""!==(e=e.trim()[0]||"1")&&(this.title="🔖 "+e)}),{y:8}),this.addWidget("number","zoom",1,(e=>{}),{y:8+LiteGraph.NODE_WIDGET_HEIGHT+4,max:2,min:.5,precision:2}),this.keypressBound=this.onKeypress.bind(this)}get _collapsed_width(){return this.___collapsed_width}set _collapsed_width(e){const t=v.canvas,s=t.canvas.getContext("2d");if(s){const e=s.font;s.font=t.title_text_font,this.___collapsed_width=40+s.measureText(this.title).width,s.font=e}}onAdded(){setTimeout((e=>{const t=this.widgets[0].value;t&&(this.title="🔖 "+t)}),1),window.addEventListener("keydown",this.keypressBound)}onRemoved(){window.removeEventListener("keydown",this.keypressBound)}onKeypress(e){const t=e.target;["input","textarea"].includes(t.localName)||this.widgets[0]&&e.key.toLocaleLowerCase()===this.widgets[0].value.toLocaleLowerCase()&&this.canvasToBookmark()}canvasToBookmark(){var e,t;const s=v.canvas;(null==(e=null==s?void 0:s.ds)?void 0:e.offset)&&(s.ds.offset[0]=16-this.pos[0],s.ds.offset[1]=40-this.pos[1]),null!=(null==(t=null==s?void 0:s.ds)?void 0:t.scale)&&(s.ds.scale=Number(this.widgets[1].value||1)),s.setDirty(!0,!0)}}LiteGraph.registerNodeType("easy bookmark",Object.assign(e,{title:"Bookmark 🔖"})),e.category="EasyUse/Util"}}),v.registerExtension({name:"Comfy.EasyUse.ChainNode",init(){v.canvas._mousemove_callback=e=>{A("EasyUse.Nodes.ChainGetSet",null,!0)&&((e=!1,t={})=>{var s,n,i,o,a;const l=Ie();if(!l||l.length<1)return;const r=be();if(0===r.length)return;let d=t.inputX||160,u=t.ouputX||60;if(r.filter((e=>xe(e))).length>1)return;for(const p of r){let o=t.inputY||10,a=t.outputY||30;const l=[],c=p.id;if(p.graph){Ne[c]||(Ne[c]=[]);for(const e of p.inputs??[]){const t=e.link;if(!t)continue;const{origin_id:s,target_slot:n}=Ee(t),i=ve(s);if(!i)continue;if(!xe(i))continue;let a=p.getConnectionPos(!0,n);Ne[c][n]||(Ne[c][n]=a),!Ne[c]||Ne[c][n][1]===a[1]&&Ne[c][n][0]===a[0]||(d=a[0]-Ne[c][n][0],o=a[1]-Ne[c][n][1],i.pos=[i.pos[0]+d,i.pos[1]+o]),Ne[c][n]=a,l.push(i)}Te[c]||(Te[c]=[]);for(const e of p.outputs??[])if(e.links&&p.graph)for(const t of e.links){const{target_id:e,target_slot:n,origin_slot:i}=Ee(t),o=ve(e);if(!o)continue;if(!xe(o))continue;const r=null==(s=o.outputs)?void 0:s.links;if((null==r?void 0:r.length)>1)return;const d=p.getConnectionPos(!1,i);Te[c][i]||(Te[c][i]=d),!Te[c]||Te[c][i][0]===d[0]&&Te[c][i][1]===d[1]||(u=d[0]-Te[c][i][0],a=d[1]-Te[c][i][1],o.pos=[o.pos[0]+u,o.pos[1]+a]),Te[c][i]=d,l.push(o)}if(e&&1===r.length){const e=[p,...l];(null==(i=null==(n=p.graph)?void 0:n.list_of_graphcanvas)?void 0:i[0]).selectNodes(e)}}}const c=r[0];if(!c)return;(null==(a=null==(o=c.graph)?void 0:o.list_of_graphcanvas)?void 0:a[0]).setDirty(!0,!0)})()};const e=LGraphCanvas.prototype.showLinkMenu;LGraphCanvas.prototype.showLinkMenu=function(t,s){return s.shiftKey?(((e,t=!1)=>{var s,n,i,o,a,l,r,d,u,c;const{type:p}=e;if("*"===p)return;let{origin_id:h,target_id:m,origin_slot:g,target_slot:f}=e,y=ve(h),_=ve(m);if(!y||!_)return!1;if("Reroute"===y.type){let e=0;[y,e]=Ge(y),h=null==y?void 0:y.id,g=e,void 0!==g&&-1!==g||(g=0)}if("Reroute"===_.type&&(_=Me(_),m=null==_?void 0:_.id,f=null==_?void 0:_.inputs.findIndex((e=>e.type===p)),void 0!==f&&-1!==f||(f=0)),void 0===h||void 0===m||!y||!_)return!1;if(t&&(xe(y)||xe(_)))return!1;let v=Ce((null==(s=_.getInputInfo(f))?void 0:s.name)??p.toLowerCase());v||(v=Ce((null==(i=null==(n=null==y?void 0:y.outputs)?void 0:n[g])?void 0:i.name)??(null==(a=null==(o=null==y?void 0:y.outputs)?void 0:o[g])?void 0:a.type.toString())??v+`_from_${h}_to_${m}`));let b,w=!1,L=!1;if(xe(y))v=ye(y),L=!0;else{const e=null==(r=null==(l=y.outputs)?void 0:l[g])?void 0:r.links;if(e)for(const t of e){const e=ve((null==(d=Ee(t))?void 0:d.target_id)??-1);e&&xe(e)&&Ae(e)&&(v=ye(e),L=!0)}if(!L){for(const e of Ie()){if(v!==ye(e)||!Ae(e))continue;const t=null==(u=e.inputs[0])?void 0:u.link;(null==(c=Ee(t))?void 0:c.origin_id)===y.id?L=!0:w=!0}w&&(v+=`_from_${h}_to_${m}`)}}if(!L){b=LiteGraph.createNode("easy setNode"),b.is_auto_link=!0;const e=y.getConnectionPos(!1,g);b.pos=[e[0]+20,e[1]],b.inputs[0].name=v,b.inputs[0].type=p,b.inputs[0].widget=_.inputs[f].widget,Oe(b,v),De(b),b.flags.collapsed=!0;let t=[];y.widgets?t=Object.values(y.widgets).map((e=>e.value)):y.widgets_values&&(t=JSON.parse(JSON.stringify(y.widgets_values))),y.connect(g,b,0),y.widgets_values=t,"PrimitiveNode"===y.type&&setTimeout((()=>{if(y){y.connect(g,b,0);for(const[e,s]of t.entries())Oe(y,s,e);null!==b&&b.setSize(b.computeSize())}}))}const S=LiteGraph.createNode("easy getNode"),E=_.getConnectionPos(!0,f);S.pos=[E[0]-150,E[1]],S.outputs[0].name=v,S.outputs[0].type=p,S.outputs[0].widget=_.inputs[f].widget,De(S),Oe(S,v),null===S||(S.flags.collapsed=!0,S.setSize(S.computeSize()),S.connect(0,_,f))})(t),!1):(e.apply(this,[t,s]),!1)}}});const Qe=async()=>{try{const{Running:e,Pending:t}=await b.getQueue();if(e.length>0||t.length>0)return void Ue.error(Y("Clean Failed")+":"+Y("Please stop all running tasks before cleaning GPU"));200==(await b.fetchApi("/easyuse/cleangpu",{method:"POST"})).status?Ue.success(Y("Clean SuccessFully")):Ue.error(Y("Clean Failed"))}catch(e){}};let et=[];function tt(e,t,s,n,i){var o=LGraphCanvas.active_canvas,a=o.getCanvasWindow(),l=o.graph;if(l)return function e(t,n){var r=LiteGraph.getNodeTypesCategories(o.filter||l.filter).filter((function(e){return e.startsWith(t)})),d=[];r.map((function(s){if(s){var n=new RegExp("^("+t+")"),i=s.replace(n,"").split("/")[0],o=""===t?i+"/":t+i+"/",a=i;-1!=a.indexOf("::")&&(a=a.split("::")[1]),-1===d.findIndex((function(e){return e.value===o}))&&d.push({value:o,content:a,has_submenu:!0,callback:function(t,s,n,i){e(t.value,i)}})}})),LiteGraph.getNodeTypesInCategory(t.slice(0,-1),o.filter||l.filter).map((function(e){if(!e.skip_list){var t={value:e.type,content:e.title,has_submenu:!1,callback:function(e,t,s,n){var a=n.getFirstEvent();o.graph.beforeChange();var l=LiteGraph.createNode(e.value);l&&(l.pos=o.convertEventToCanvasOffset(a),o.graph.add(l)),i&&i(l),o.graph.afterChange()}};d.push(t)}}));const u=A("EasyUse.ContextMenu.NodesSort",null,!0);""===t&&u&&(d=function(e){let t=[],s=[];return e.forEach((e=>{(null==e?void 0:e.value)&&G.includes(e.value.split("/")[0])?t.push(e):s.push(e)})),[{title:Y("ComfyUI Basic"),is_category_title:!0},...t,{title:Y("Others A~Z"),is_category_title:!0},...s.sort(((e,t)=>e.content.localeCompare(t.content)))]}(d)),new LiteGraph.ContextMenu(d,{event:s,parentMenu:n},a)}("",n),!1}v.registerExtension({name:"Comfy.EasyUse.ContextMenu",async setup(){LGraphCanvas.onMenuAdd=tt;const e=A("EasyUse.ContextMenu.ModelsThumbnails",null,!1),t=A("EasyUse.ContextMenu.ModelsThumbnailsLimit",null,500);if(e&&t>0){const e=await b.fetchApi(`/easyuse/models/thumbnail?limit=${t}`);if(200===e.status){let t=await e.json();et=t}else Ue.error(Y("Too many thumbnails, have closed the display"))}const s=LiteGraph.ContextMenu;LiteGraph.ContextMenu=function(e,t){if(A("EasyUse.ContextMenu.SubDirectories",null,!1)&&(null==t?void 0:t.callback)&&!e.some((e=>"string"!=typeof e))){const n=function(e,t){const s=e,n=[...s],i={},o=[],a=[],l=["ckpt","pt","bin","pth","safetensors"];if((null==e?void 0:e.length)>0){const t=ot(e[e.length-1]);if(!l.includes(t))return null}for(const r of s){const e=r.indexOf("/")>-1?"/":"\\",t=r.split(e);if(t.length>1){const s=t.shift();i[s]=i[s]||[],i[s].push(t.join(e))}else"CHOOSE"===r||r.startsWith("DISABLE ")?o.push(r):a.push(r)}if(Object.values(i).length>0){const e=t.callback;t.callback=null;const s=(t,s)=>{["None","无","無","なし"].includes(t.content)?e("None",s):e(n.find((e=>e.endsWith(t.content)),s))},r=(e,t="")=>{const n=t?t+"\\"+it(e):it(e),i=ot(e),o=(new Date).getTime();let a,r="";if(l.includes(i))for(let s=0;s{let s=[],n=[];const o=e.map((e=>{const o={},a=e.indexOf("/")>-1?"/":"\\",l=e.split(a);if(l.length>1){const e=l.shift();o[e]=o[e]||[],o[e].push(l.join(a))}if(Object.values(i).length>0){let t=Object.keys(o)[0];t&&o[t]?s.push({key:t,value:o[t][0]}):n.push(r(e,t))}return r(e,t)}));if(s.length>0){let e={};return s.forEach((t=>{e[t.key]=e[t.key]||[],e[t.key].push(t.value)})),[...Object.entries(e).map((e=>({content:e[0],has_submenu:!0,callback:()=>{},submenu:{options:u(e[1],e[0])}}))),...n]}return o};for(const[t,n]of Object.entries(i))d.push({content:t,has_submenu:!0,callback:()=>{},submenu:{options:u(n,t)}});return d.push(...a.map((e=>r(e,"")))),o.length>0&&d.push(...o.map((e=>r(e,"")))),d}return null}(e,t);return n?s.call(this,n,t):s.apply(this,[...arguments])}if(t.parentMenu);else if(t.extra);else if(t.scale);else{const s=A("EasyUse.ContextMenu.QuickOptions",null,"At the forefront");if(t.hasOwnProperty("extra")&&"Disable"!==s){if("At the forefront"==s?e.unshift(null):e.push(null),n=window.location.host,["192.168.","10.","127.",/^172\.((1[6-9]|2[0-9]|3[0-1])\.)/].some((e=>"string"==typeof e?n.startsWith(e):e.test(n)))){const t={content:`${Y("Reboot ComfyUI")}`,callback:e=>(async()=>{if(confirm(Y("Are you sure you'd like to reboot the server?")))try{b.fetchApi("/easyuse/reboot")}catch(e){}})()};"At the forefront"==s?e.unshift(t):e.push(t)}const t=A("EasyUse.Hotkeys.cleanVRAMUsed",null,!0)?"("+J("Shift+r")+")":"",i={content:`${Y("Cleanup Of VRAM Usage")} ${t}`,callback:e=>Qe()};"At the forefront"==s?e.unshift(i):e.push(i);const o=A("EasyUse.Hotkeys.toggleNodesMap",null,!0)?"("+J("Shift+m")+")":"",a={content:`${Y("Nodes Map")} ${o}`,callback:e=>{var t,s,n;const i=(null==(t=v.extensionManager)?void 0:t.sidebarTab)||v.extensionManager,o=(null==(s=v.extensionManager.sidebarTab)?void 0:s.activeSidebarTabId)||(null==(n=v.extensionManager)?void 0:n.activeSidebarTab);i.activeSidebarTabId=o==P?null:P}};"At the forefront"==s?e.unshift(a):e.push(a)}}return s.apply(this,[...arguments]);var n},LiteGraph.ContextMenu.prototype=s.prototype,A("EasyUse.ContextMenu.NodesSort",null,!0)&&(LiteGraph.ContextMenu.prototype.addItem=nt)}});const st=e=>e&&"object"==typeof e&&"image"in e&&e.content;function nt(e,t,s){var n=this;s=s||{};var i=document.createElement("div");i.className="litemenu-entry submenu";var o,a=!1;function l(e){var t=this.value,i=!0;(n.current_submenu&&n.current_submenu.close(e),s.callback)&&(!0===s.callback.call(this,t,s,e,n,s.node)&&(i=!1));if(t){if(t.callback&&!s.ignore_item_callbacks&&!0!==t.disabled)!0===t.callback.call(this,t,s,e,n,s.extra)&&(i=!1);if(t.submenu){if(!t.submenu.options)throw"ContextMenu submenu needs options";new n.constructor(t.submenu.options,{callback:t.submenu.callback,event:e,parentMenu:n,ignore_item_callbacks:t.submenu.ignore_item_callbacks,title:t.submenu.title,extra:t.submenu.extra,autoopen:s.autoopen}),i=!1}}i&&!n.lock&&n.close()}return null===t?i.classList.add("separator"):t.is_category_title?(i.classList.remove("litemenu-entry"),i.classList.remove("submenu"),i.classList.add("litemenu-title"),i.innerHTML=t.title):(i.innerHTML=t&&t.title?t.title:e,i.value=t,t&&(t.disabled&&(a=!0,i.classList.add("disabled")),(t.submenu||t.has_submenu)&&i.classList.add("has_submenu")),"function"==typeof t?(i.dataset.value=e,i.onclick_callback=t):i.dataset.value=t,t.className&&(i.className+=" "+t.className)),i&&st(t)&&(null==t?void 0:t.image)&&!t.submenu&&(i.textContent+=" *",w("div.pysssss-combo-image",{parent:i,style:{backgroundImage:`url(/pysssss/view/${o=t.image,encodeURIComponent(o).replace(/[!'()*]/g,(e=>`%${e.charCodeAt(0).toString(16).toUpperCase()}`))})`}})),this.root.appendChild(i),a||i.addEventListener("click",l),!a&&s.autoopen&&LiteGraph.pointerListenerAdd(i,"enter",(function(e){var t=this.value;if(!t||!t.has_submenu)return;l.call(this,e)})),i}function it(e){return null==e?void 0:e.substring(0,e.lastIndexOf("."))}function ot(e){return null==e?void 0:e.substring(e.lastIndexOf(".")+1)}class at extends L{constructor(){super(),this.element.classList.add("easyuse-model-metadata")}show(e){super.show(w("div",Object.keys(e).map((t=>w("div",[w("label",{textContent:t}),w("span",{textContent:e[t]})])))))}}class lt extends L{constructor(e){super(),this.name=e,this.element.classList.add("easyuse-model-info")}get customNotes(){return this.metadata["easyuse.notes"]}set customNotes(e){this.metadata["easyuse.notes"]=e}get hash(){return this.metadata["easyuse.sha256"]}async show(e,t){this.type=e;const s=b.fetchApi("/easyuse/metadata/"+encodeURIComponent(`${e}/${t}`));this.info=w("div",{style:{flex:"auto"}}),this.imgCurrent=0,this.imgList=w("div.easyuse-preview-list",{style:{display:"none"}}),this.imgWrapper=w("div.easyuse-preview",[w("div.easyuse-preview-group",[this.imgList])]),this.main=w("main",{style:{display:"flex"}},[this.imgWrapper,this.info]),this.content=w("div.easyuse-model-content",[w("div.easyuse-model-header",[w("h2",{textContent:this.name})]),this.main]);const n=w("div",{textContent:"ℹ️ Loading...",parent:this.content});super.show(this.content),this.metadata=await(await s).json(),this.viewMetadata.style.cursor=this.viewMetadata.style.opacity="",this.viewMetadata.removeAttribute("disabled"),n.remove(),this.addInfo()}createButtons(){const e=super.createButtons();return this.viewMetadata=w("button",{type:"button",textContent:"View raw metadata",disabled:"disabled",style:{opacity:.5,cursor:"not-allowed"},onclick:e=>{this.metadata&&(new at).show(this.metadata)}}),e.unshift(this.viewMetadata),e}parseNote(){if(!this.customNotes)return[];let e=[];const t=new RegExp("(\\bhttps?:\\/\\/[^\\s]+)","g");let s,n=0;do{let i;s=t.exec(this.customNotes);let o=0;s?(i=s.index,o=s.index+s[0].length):i=this.customNotes.length;let a=this.customNotes.substring(n,i);a&&(a=a.replaceAll("\n","
"),e.push(w("span",{innerHTML:a}))),s&&e.push(w("a",{href:s[0],textContent:s[0],target:"_blank"})),n=o}while(s);return e}addInfoEntry(e,t){return w("p",{parent:this.info},["string"==typeof e?w("label",{textContent:e+": "}):e,"string"==typeof t?w("span",{textContent:t}):t])}async getCivitaiDetails(){const e=await fetch("https://civitai.com/api/v1/model-versions/by-hash/"+this.hash);if(200===e.status)return await e.json();throw 404===e.status?new Error("Model not found"):new Error(`Error loading info (${e.status}) ${e.statusText}`)}addCivitaiInfo(){const e=this.getCivitaiDetails(),t=w("span",{textContent:"ℹ️ Loading..."});return this.addInfoEntry(w("label",[w("img",{style:{width:"18px",position:"relative",top:"3px",margin:"0 5px 0 0"},src:"https://civitai.com/favicon.ico"}),w("span",{textContent:"Civitai: "})]),t),e.then((e=>{var t,s;this.imgWrapper.style.display="block";let n=this.element.querySelector(".easyuse-model-header");n&&n.replaceChildren(w("h2",{textContent:this.name}),w("div.easyuse-model-header-remark",[w("h5",{textContent:Y("Updated At:")+Z(new Date(e.updatedAt),"yyyy/MM/dd")}),w("h5",{textContent:Y("Created At:")+Z(new Date(e.updatedAt),"yyyy/MM/dd")})]));let i=null,o=this.parseNote.call(this),a=Y("✏️ Edit"),l=w("div.easyuse-model-detail-textarea",[w("p",(null==o?void 0:o.length)>0?o:{textContent:Y("No notes")})]);if(o&&0!=o.length?l.classList.remove("empty"):l.classList.add("empty"),this.info.replaceChildren(w("div.easyuse-model-detail",[w("div.easyuse-model-detail-head.flex-b",[w("span",Y("Notes")),w("a",{textContent:a,href:"#",style:{fontSize:"12px",float:"right",color:"var(--warning-color)",textDecoration:"none"},onclick:async e=>{if(e.preventDefault(),i){if(i.value!=this.customNotes){Ue.showLoading(Y("Saving Notes...")),this.customNotes=i.value;const e=await b.fetchApi("/easyuse/metadata/notes/"+encodeURIComponent(`${this.type}/${this.name}`),{method:"POST",body:this.customNotes});if(Ue.hideLoading(),200!==e.status)return Ue.error(Y("Saving Failed")),void alert(`Error saving notes (${e.status}) ${e.statusText}`);Ue.success(Y("Saving Succeed")),o=this.parseNote.call(this),l.replaceChildren(w("p",(null==o?void 0:o.length)>0?o:{textContent:Y("No notes")})),i.value?l.classList.remove("empty"):l.classList.add("empty")}else l.replaceChildren(w("p",{textContent:Y("No notes")})),l.classList.add("empty");e.target.textContent=a,i.remove(),i=null}else e.target.textContent="💾 Save",i=w("textarea",{placeholder:Y("Type your notes here"),style:{width:"100%",minWidth:"200px",minHeight:"50px",height:"100px"},textContent:this.customNotes}),l.replaceChildren(i),i.focus()}})]),l]),w("div.easyuse-model-detail",[w("div.easyuse-model-detail-head",{textContent:Y("Details")}),w("div.easyuse-model-detail-body",[w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Type")}),w("div.easyuse-model-detail-item-value",{textContent:e.model.type})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("BaseModel")}),w("div.easyuse-model-detail-item-value",{textContent:e.baseModel})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Download")}),w("div.easyuse-model-detail-item-value",{textContent:(null==(t=e.stats)?void 0:t.downloadCount)||0})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Trained Words")}),w("div.easyuse-model-detail-item-value",{textContent:(null==e?void 0:e.trainedWords.join(","))||"-"})]),w("div.easyuse-model-detail-item",[w("div.easyuse-model-detail-item-label",{textContent:Y("Source")}),w("div.easyuse-model-detail-item-value",[w("label",[w("img",{style:{width:"14px",position:"relative",top:"3px",margin:"0 5px 0 0"},src:"https://civitai.com/favicon.ico"}),w("a",{href:"https://civitai.com/models/"+e.modelId,textContent:"View "+e.model.name,target:"_blank"})])])])])])),null==(s=e.images)?void 0:s.length){this.imgCurrent=0,this.isSaving=!1,e.images.map((e=>e.url&&this.imgList.appendChild(w("div.easyuse-preview-slide",[w("div.easyuse-preview-slide-content",[w("img",{src:e.url}),w("div.save",{textContent:"Save as preview",onclick:async()=>{if(this.isSaving)return;this.isSaving=!0,Ue.showLoading(Y("Saving Preview..."));const t=await(await fetch(e.url)).blob(),s="temp_preview."+new URL(e.url).pathname.split(".")[1],n=new FormData;n.append("image",new File([t],s)),n.append("overwrite","true"),n.append("type","temp");if(200!==(await b.fetchApi("/upload/image",{method:"POST",body:n})).status)return this.isSaving=!1,Ue.error(Y("Saving Failed")),Ue.hideLoading(),void alert(`Error saving preview (${req.status}) ${req.statusText}`);await b.fetchApi("/easyuse/save/"+encodeURIComponent(`${this.type}/${this.name}`),{method:"POST",body:JSON.stringify({filename:s,type:"temp"}),headers:{"content-type":"application/json"}}).then((e=>{Ue.success(Y("Saving Succeed")),Ue.hideLoading()})),this.isSaving=!1,app.refreshComboInNodes()}})])]))));let t=this;this.imgDistance=(-660*this.imgCurrent).toString(),this.imgList.style.display="",this.imgList.style.transform="translate3d("+this.imgDistance+"px, 0px, 0px)",this.slides=this.imgList.querySelectorAll(".easyuse-preview-slide"),this.slideLeftButton=w("button.left",{parent:this.imgWrapper,style:{display:e.images.length<=2?"none":"block"},innerHTML:'',onclick:()=>{e.images.length<=2||(t.imgList.classList.remove("no-transition"),0==t.imgCurrent?(t.imgCurrent=e.images.length/2-1,this.slides[this.slides.length-1].style.transform="translate3d("+(-660*(this.imgCurrent+1)).toString()+"px, 0px, 0px)",this.slides[this.slides.length-2].style.transform="translate3d("+(-660*(this.imgCurrent+1)).toString()+"px, 0px, 0px)",t.imgList.style.transform="translate3d(660px, 0px, 0px)",setTimeout((e=>{this.slides[this.slides.length-1].style.transform="translate3d(0px, 0px, 0px)",this.slides[this.slides.length-2].style.transform="translate3d(0px, 0px, 0px)",t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)",t.imgList.classList.add("no-transition")}),500)):(t.imgCurrent=t.imgCurrent-1,t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)"))}}),this.slideRightButton=w("button.right",{parent:this.imgWrapper,style:{display:e.images.length<=2?"none":"block"},innerHTML:'',onclick:()=>{if(!(e.images.length<=2))if(t.imgList.classList.remove("no-transition"),t.imgCurrent>=e.images.length/2-1){t.imgCurrent=0;const s=e.images.length/2;this.slides[0].style.transform="translate3d("+(660*s).toString()+"px, 0px, 0px)",this.slides[1].style.transform="translate3d("+(660*s).toString()+"px, 0px, 0px)",t.imgList.style.transform="translate3d("+(-660*s).toString()+"px, 0px, 0px)",setTimeout((e=>{this.slides[0].style.transform="translate3d(0px, 0px, 0px)",this.slides[1].style.transform="translate3d(0px, 0px, 0px)",t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)",t.imgList.classList.add("no-transition")}),500)}else t.imgCurrent=t.imgCurrent+1,t.imgDistance=(-660*this.imgCurrent).toString(),t.imgList.style.transform="translate3d("+t.imgDistance+"px, 0px, 0px)"}})}return e.description&&w("div",{parent:this.content,innerHTML:e.description,style:{marginTop:"10px"}}),e})).catch((e=>{this.imgWrapper.style.display="none",t.textContent="⚠️ "+e.message})).finally((e=>{}))}}class rt extends lt{async addInfo(){await this.addCivitaiInfo()}}class dt extends lt{getTagFrequency(){if(!this.metadata.ss_tag_frequency)return[];const e=JSON.parse(this.metadata.ss_tag_frequency),t={};for(const s in e){const n=e[s];for(const e in n)e in t?t[e]+=n[e]:t[e]=n[e]}return Object.entries(t).sort(((e,t)=>t[1]-e[1]))}getResolutions(){let e=[];if(this.metadata.ss_bucket_info){const t=JSON.parse(this.metadata.ss_bucket_info);if(null==t?void 0:t.buckets)for(const{resolution:s,count:n}of Object.values(t.buckets))e.push([n,`${s.join("x")} * ${n}`])}e=e.sort(((e,t)=>t[0]-e[0])).map((e=>e[1]));let t=this.metadata.ss_resolution;if(t){const s=t.split(","),n=s[0].replace("(",""),i=s[1].replace(")","");e.push(`${n.trim()}x${i.trim()} (Base res)`)}else(t=this.metadata["modelspec.resolution"])&&e.push(t+" (Base res");return e.length||e.push("⚠️ Unknown"),e}getTagList(e){return e.map((e=>w("li.easyuse-model-tag",{dataset:{tag:e[0]},$:e=>{e.onclick=()=>{e.classList.toggle("easyuse-model-tag--selected")}}},[w("p",{textContent:e[0]}),w("span",{textContent:e[1]})])))}addTags(){let e,t=this.getTagFrequency();if(null==t?void 0:t.length){const s=t.length;let n;s>500&&(t=t.slice(0,500),e=w("p",[w("span",{textContent:"⚠️ Only showing first 500 tags "}),w("a",{href:"#",textContent:`Show all ${s}`,onclick:()=>{n.replaceChildren(...this.getTagList(this.getTagFrequency())),e.remove()}})])),n=w("ol.easyuse-model-tags-list",this.getTagList(t)),this.tags=w("div",[n])}else this.tags=w("p",{textContent:"⚠️ No tag frequency metadata found"});this.content.append(this.tags),e&&this.content.append(e)}async addInfo(){const e=this.addCivitaiInfo();this.addTags();const t=await e;t&&w("div",{parent:this.content,innerHTML:t.description,style:{maxHeight:"250px",overflow:"auto"}})}createButtons(){const e=super.createButtons();function t(e,t){const s=w("textarea",{parent:document.body,style:{position:"fixed"},textContent:t.map((e=>e.dataset.tag)).join(", ")});s.select();try{document.execCommand("copy"),e.target.dataset.text||(e.target.dataset.text=e.target.textContent),e.target.textContent="Copied "+t.length+" tags",setTimeout((()=>{e.target.textContent=e.target.dataset.text}),1e3)}catch(n){prompt("Copy to clipboard: Ctrl+C, Enter",text)}finally{document.body.removeChild(s)}}return e.unshift(w("button",{type:"button",textContent:"Copy Selected",onclick:e=>{t(e,[...this.tags.querySelectorAll(".easyuse-model-tag--selected")])}}),w("button",{type:"button",textContent:"Copy All",onclick:e=>{t(e,[...this.tags.querySelectorAll(".easyuse-model-tag")])}})),e}}const ut={pipe:{category:"Easy Pipe",nodes:["easy pipeIn","easy pipeOut","easy pipeEdit","easy pipeEditPrompt","easy pipeBatchIndex"],input:{pipe:"pipe"},output:{pipe:"pipe"},widget:{optional_positive:"optional_positive",optional_negative:"optional_negative"}},loaders:{category:"Easy Loaders",nodes:["easy fullLoader","easy a1111Loader","easy comfyLoader","easy kolorsLoader","easy hunyuanDiTLoader","easy pixArtLoader","easy fluxLoader"],input:{optional_lora_stack:"optional_lora_stack",optional_controlnet_stack:"optional_controlnet_stack",positive:"positive",negative:"negative"},output:{pipe:"pipe",model:"model",vae:"vae",clip:null,positive:null,negative:null,latent:null},widget:{ckpt_name:"ckpt_name",vae_name:"vae_name",clip_skip:"clip_skip",lora_name:"lora_name",resolution:"resolution",empty_latent_width:"empty_latent_width",empty_latent_height:"empty_latent_height",positive:"positive",negative:"negative",batch_size:"batch_size",a1111_prompt_style:"a1111_prompt_style"}},preSampling:{category:"Easy PreSampling",nodes:["easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG","easy preSamplingNoiseIn","easy preSamplingCustom","easy preSamplingLayerDiffusion","easy fullkSampler"],input:{pipe:"pipe",image_to_latent:"image_to_latent",latent:"latent"},output:{pipe:"pipe"},widget:{steps:"steps",cfg:"cfg",cfg_scale_min:"cfg",sampler_name:"sampler_name",scheduler:"scheduler",denoise:"denoise",seed_num:"seed_num",seed:"seed"}},samplers:{category:"Custom Sampler",nodes:["KSamplerSelect","SamplerEulerAncestral","SamplerEulerAncestralCFG++","SamplerLMS","SamplerDPMPP_3M_SDE","SamplerDPMPP_2M_SDE","SamplerDPMPP_SDE","SamplerDPMAdaptative","SamplerLCMUpscale","SamplerTCD","SamplerTCD EulerA"],output:{SAMPLER:"SAMPLER"}},sigmas:{category:"Custom Sigmas",nodes:["BasicScheduler","KarrasScheduler","ExponentialScheduler","PolyexponentialScheduler","VPScheduler","BetaSamplingScheduler","SDTurboScheduler","SplitSigmas","SplitSigmasDenoise","FlipSigmas","AlignYourStepsScheduler","GITSScheduler"],output:{SIGMAS:"SIGMAS"}},kSampler:{category:"Easy kSampler",nodes:["easy kSampler","easy kSamplerTiled","easy kSamplerCustom","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerLayerDiffusion"],input:{pipe:"pipe",model:"model"},output:{pipe:"pipe",image:"image"},widget:{image_output:"image_output",save_prefix:"save_prefix",link_id:"link_id"}},controlNet:{category:"Easy ControlNet",nodes:["easy controlnetLoader","easy controlnetLoaderADV","easy controlnetLoader++","easy instantIDApply","easy instantIDApplyADV"],input:{pipe:"pipe",image:"image",image_kps:"image_kps",control_net:"control_net",positive:"positive",negative:"negative",mask:"mask"},output:{pipe:"pipe",positive:"positive",negative:"negative"},widget:{control_net_name:"control_net_name",strength:["strength","cn_strength"],scale_soft_weights:["scale_soft_weights","cn_soft_weights"],cn_strength:["strength","cn_strength"],cn_soft_weights:["scale_soft_weights","cn_soft_weights"]}},adapter:{category:"Easy Adapter",nodes:["easy ipadapterApply","easy ipadapterApplyADV","easy ipadapterApplyFaceIDKolors","easy ipadapterStyleComposition","easy ipadapterApplyFromParams","easy pulIDApply","easy pulIDApplyADV"],input:{model:"model",image:"image",image_style:"image",attn_mask:"attn_mask",optional_ipadapter:"optional_ipadapter"},output:{model:"model",tiles:"tiles",masks:"masks",ipadapter:"ipadapter"},widget:{preset:"preset",lora_strength:"lora_strength",provider:"provider",weight:"weight",weight_faceidv2:"weight_faceidv2",start_at:"start_at",end_at:"end_at",cache_mode:"cache_mode",use_tiled:"use_tiled",insightface:"insightface",pulid_file:"pulid_file"}},positive:{category:"Easy Positive",nodes:["easy positive","easy wildcards"],input:{},output:{text:"positive",positive:"text"},widget:{text:"positive",positive:"text"}},loadImage:{category:"Easy LoadImage",nodes:["easy loadImageBase64","LoadImage","LoadImageMask"],input:{pipe:"pipe",image:"image",mask:"mask"},output:{IMAGE:"IMAGE",MASK:"MASK"},widget:{image:"image",base64_data:"base64_data",channel:"channel"}},saveImage:{category:"Save/Preview Image",nodes:["SaveImage","PreviewImage"]},inPaint:{category:"Easy Inpaint",nodes:["easy applyBrushNet","easy applyPowerPaint","easy applyInpaint"],input:{},output:{pipe:"pipe"},widget:{dtype:"dtype",fitting:"fitting",function:"function",scale:"scale",start_at:"start_at",end_at:"end_at"}},showAny:{category:"Show Anything",nodes:["easy showAnything","easy showAnythingLazy"],input:{anything:"anything"},output:{output:"output"}},saveText:{category:"Save Text",nodes:["easy saveText","easy saveTextLazy"],input:{image:"image",text:"text",output_file_path:"output_file_path",file_name:"file_name",file_extension:"file_extension",overwrite:"overwrite"},output:{text:"text",image:"image"},widget:{image:"image",text:"text",output_file_path:"output_file_path",file_name:"file_name",file_extension:"file_extension",overwrite:"overwrite"}},persona:{category:"LLM Party Persona",nodes:["load_persona","classify_persona","classify_persona_plus","custom_persona","translate_persona","flux_persona"],input:{file_content:"file_content"},output:{system_prompt:"system_prompt"},widget:{is_enable:"is_enable"}},llmModelLoader:{category:"LLM Model Loader",nodes:["LLM_api_loader","genai_api_loader","LLM_local_loader"],output:{model:"model"}},llmModelChain:{category:"LLM Model Chain",nodes:["LLM","LLM_local"],input:{model:"model",image:"images",images:"image",extra_parameters:"extra_parameters",system_prompt_input:"system_prompt_input",user_prompt_input:"user_prompt_input",tools:"tools",file_content:"file_content"},output:{assistant_response:"assistant_response",history:"history",tool:"tool",image:"image"},widget:{system_prompt:"system_prompt",user_prompt:"user_prompt",temperature:"temperature",is_memory:"is_memory",is_tools_in_sys_prompt:"is_tools_in_sys_prompt",max_length:"max_length",main_brain:"main_brain",conversation_rounds:"conversation_rounds",history_record:"history_record",is_enable:"is_enable"}},maskModify:{category:"Mask Modify",nodes:["CropMask","ThresholdMask","GrowMask","FeatherMask","LayerMask: MaskGrain","LayerMask: MaskEdgeUltraDetail","LayerMask: MaskEdgeUltraDetail V2"],input:{mask:"mask"},output:{MASK:"MASK",mask:"mask",image:"image"}},maskModifyWAS:{category:"Mask Modify (WAS)",nodes:["Mask Dilate Region","Mask Gaussian Region"],input:{masks:"masks"},output:{MASKS:"MASKS"}}};function ct(e,t,s){return function(){!function(e,t,s){var n;const i=LiteGraph.createNode(t);if(i){if(v.graph.add(i),i.pos=e.pos.slice(),i.size=e.size.slice(),(null==(n=e.widgets)?void 0:n.length)>0&&e.widgets.forEach((e=>{var t,n,o;if(null==(n=null==(t=ut[s])?void 0:t.widget)?void 0:n[e.name]){const t=ut[s].widget[e.name];if(t&&i.widgets){const s=(o=t,i.widgets.find((e=>"object"==typeof o?o.includes(e.name):e.name===o)));s&&(s.value=e.value,"seed_num"==e.name&&(s.linkedWidgets[0].value=e.linkedWidgets[0].value),"converted-widget"==e.type&&_t(i,s,e))}}})),e.inputs&&e.inputs.forEach(((t,n)=>{var o,a,l;if(t&&t.link&&(null==(a=null==(o=ut[s])?void 0:o.input)?void 0:a[t.name])){const n=null==(l=ut[s])?void 0:l.input[t.name];if(null===n)return;const o=i.findInputSlot(n);if(-1!==o){const s=e.graph.links[t.link];if(s){const t=e.graph.getNodeById(s.origin_id);t&&t.connect(s.origin_slot,i,o)}}}})),e.outputs&&e.outputs.forEach(((t,n)=>{var o,a;if(t&&t.links&&(null==(a=null==(o=ut[s])?void 0:o.output)?void 0:a[t.name])){const n=ut[s].output[t.name];if(null===n)return;const o=i.findOutputSlot(n);-1!==o&&t.links.forEach((t=>{const s=e.graph.links[t];if(s){const t=e.graph.getNodeById(s.target_id);t&&i.connect(o,t,s.target_slot)}}))}})),v.graph.remove(e),"easy fullkSampler"==i.type){const e=i.outputs[0].links;if(e&&e[0]){const t=v.graph._nodes.find((t=>t.inputs&&t.inputs[0]&&t.inputs[0].link==e[0]));t&&v.graph.remove(t)}}else if(ut.preSampling.nodes.includes(i.type)){const e=i.outputs[0].links;if(!e||!e[0]){const e=LiteGraph.createNode("easy kSampler");v.graph.add(e),e.pos=i.pos.slice(),e.pos[0]=e.pos[0]+i.size[0]+20;const t=i.findInputSlot("pipe");-1!==t&&i&&i.connect(0,e,t)}}i.setSize([i.size[0],i.computeSize()[1]])}}(e,t,s)}}const pt=(e,t)=>{const s=e.prototype.getExtraMenuOptions;e.prototype.getExtraMenuOptions=function(){const e=s.apply(this,arguments);return t.apply(this,arguments),e}},ht=(e,t,s,n,i=!0)=>{pt(n,(function(n,o){o.unshift({content:e,has_submenu:i,callback:(e,n,i,o,a)=>mt(e,n,i,o,a,t,s)}),"loaders"==t&&(o.unshift({content:Y("💎 View Lora Info..."),callback:(e,t,s,n,i)=>{let o=i.widgets.find((e=>"lora_name"==e.name)).value;o&&"None"!=o&&new dt(o).show("loras",o)}}),o.unshift({content:Y("💎 View Checkpoint Info..."),callback:(e,t,s,n,i)=>{let o=i.widgets[0].value;o&&"None"!=o&&new rt(o).show("checkpoints",o)}}))}))},mt=(e,t,s,n,i,o,a)=>{const l=[];return a.map((e=>{i.type!==e&&l.push({content:`${e}`,callback:ct(i,e,o)})})),new LiteGraph.ContextMenu(l,{event:s,callback:null,parentMenu:n,node:i}),!1},gt="converted-widget",ft=Symbol();function yt(e,t,s=""){if(t.origType=t.type,t.origComputeSize=t.computeSize,t.origSerializeValue=t.serializeValue,t.computeSize=()=>[0,-4],t.type=gt+s,t.serializeValue=()=>{if(!e.inputs)return;let s=e.inputs.find((e=>{var s;return(null==(s=e.widget)?void 0:s.name)===t.name}));return s&&s.link?t.origSerializeValue?t.origSerializeValue():t.value:void 0},t.linkedWidgets)for(const n of t.linkedWidgets)yt(e,n,":"+t.name)}function _t(e,t,s){yt(e,t);const{type:n}=function(e){let t=e[0];t instanceof Array&&(t="COMBO");return{type:t}}(s),i=e.size;t.options&&t.options.forceInput||e.addInput(t.name,n,{widget:{name:t.name,[ft]:()=>s}});for(const o of e.widgets)o.last_y+=LiteGraph.NODE_SLOT_HEIGHT;e.setSize([Math.max(i[0],e.size[0]),Math.max(i[1],e.size[1])])}const vt=function(e){var t,s,n,i;const o=e.constructor.type,a=e.properties.origVals||{},l=a.title||e.title,r=a.color||e.color,d=a.bgcolor||e.bgcolor,u=e,c={size:[...e.size],color:r,bgcolor:d,pos:[...e.pos]};let p=[],h=[];if(e.inputs)for(const y of e.inputs)if(y.link){const t=y.name,s=e.findInputSlot(t),n=e.getInputNode(s),i=e.getInputLink(s);p.push([i.origin_slot,n,t])}if(e.outputs)for(const y of e.outputs)if(y.links){const e=y.name;for(const t of y.links){const s=graph.links[t],n=graph._nodes_by_id[s.target_id];h.push([e,n,s.target_slot])}}v.graph.remove(e);let m=v.graph.add(LiteGraph.createNode(o,l,c));function g(){if(u.widgets)for(let e of u.widgets)if("converted-widget"===e.type){const t=m.widgets.find((t=>t.name===e.name));for(let s of u.inputs)s.name===e.name&&_t(m,t,s.widget)}for(let e of p){const[t,s,n]=e;s.connect(t,m.id,n)}for(let e of h){const[t,s,n]=e;m.connect(t,s,n)}}m.inputs.map(((t,s)=>{m.inputs[s].label=e.inputs[s].label})),m.outputs.map(((t,s)=>{m.outputs[s].label=e.outputs[s].label}));let f=u.widgets_values;if(!f&&(null==(t=m.widgets)?void 0:t.length)>0)return m.widgets.forEach(((e,t)=>{const s=u.widgets[t];e.name===s.name&&e.type===s.type&&(e.value=s.value)})),void g();if(f){let e=function(e,t){var s,n,i,o,a,l;if(!0===e||!1===e){if((null==(s=t.options)?void 0:s.on)&&(null==(n=t.options)?void 0:n.off))return{value:e,pass:!0}}else if("number"==typeof e){if((null==(i=t.options)?void 0:i.min)<=e&&e<=(null==(o=t.options)?void 0:o.max))return{value:e,pass:!0}}else{if(null==(l=null==(a=t.options)?void 0:a.values)?void 0:l.includes(e))return{value:e,pass:!0};if(t.inputEl&&"string"==typeof e)return{value:e,pass:!0}}return{value:t.value,pass:!1}},t=!1;const o=(null==f?void 0:f.length)<=(null==(s=m.widgets)?void 0:s.length);let a=o?0:f.length-1;const l=s=>{var n;const i=u.widgets[s];let l=m.widgets[s];if(l.name===i.name&&l.type===i.type){for(;(o?a=0)&&!t;){let{value:t,pass:s}=e(f[a],l);if(s&&null!==t){l.value=t;break}a+=o?1:-1}a++,o||(a=f.length-((null==(n=m.widgets)?void 0:n.length)-1-s))}};if(o&&(null==(n=m.widgets)?void 0:n.length)>0)for(let s=0;s0)for(let s=m.widgets.length-1;s>=0;s--)l(s)}g()};v.registerExtension({name:"Comfy.EasyUse.ExtraMenu",async beforeRegisterNodeDef(e,t,s){pt(e,(function(e,s){s.unshift({content:Y("🔃 Reload Node"),callback:(e,t,s,n,i)=>{let o=LGraphCanvas.active_canvas;if(!o.selected_nodes||Object.keys(o.selected_nodes).length<=1)vt(i);else for(let a in o.selected_nodes)vt(o.selected_nodes[a])}}),"easy ckptNames"==t.name&&s.unshift({content:Y("💎 View Checkpoint Info..."),callback:(e,t,s,n,i)=>{i.widgets[0].value}})}));for(const n in ut)ut[n].nodes.includes(t.name)&&ht(`↪️ Swap ${ut[n].category}`,n,ut[n].nodes,e)}});const bt=LiteGraph.LGraphNode,wt="➡️";v.registerExtension({name:"easy setNode",registerCustomNodes(){class e extends bt{constructor(t){super("Set"),g(this,"defaultVisibility",!0),g(this,"serialize_widgets",!0),this.properties||(this.properties={previousName:""}),this.properties.showOutputText=e.defaultVisibility;const s=this;s.color=LGraphCanvas.node_colors.blue.color,this.addWidget("text","Constant","",((e,t,n,i,o)=>{s.validateName(s.graph),""!==this.widgets[0].value&&(this.title=wt+this.widgets[0].value),this.properties.previousName=this.widgets[0].value,this.update()}),{}),this.addInput("*","*"),this.onConnectionsChange=function(e,t,n,i,o){if(1!=e||n||(this.inputs[t].type="*",this.inputs[t].name="*",this.title="Set"),i&&s.graph&&1==e&&n){const e=s.graph._nodes.find((e=>e.id==i.origin_id)).outputs[i.origin_slot];if(!e)return;const t=e.type,n=s.is_auto_link?this.widgets[0].value:e.name;"Set"===this.title&&(this.title=wt+n,this.widgets[0].value=n),"*"===this.widgets[0].value&&(this.widgets[0].value=n),this.validateName(s.graph),this.inputs[0]&&(this.inputs[0].type=t,this.inputs[0].name=n),setTimeout((e=>{this.title=wt+this.widgets[0].value,this.properties.previousName=this.widgets[0].value}),1)}this.update()},this.validateName=function(e){let t=s.widgets[0].value;if(""!=t){let n=0,i=[];do{i=e._nodes.filter((e=>e!=this&&("easy setNode"==e.type&&e.widgets[0].value===t))),i.length>0&&(t=s.widgets[0].value+n),n++}while(i.length>0);s.widgets[0].value=t,this.update()}},this.clone=function(){const t=e.prototype.clone.apply(this);return t.inputs[0].name="*",t.inputs[0].type="*",t.properties.previousName="",t.size=t.computeSize(),t},this.onAdded=function(e){this.validateName(e)},this.update=function(){if(s.graph){this.findGetters(s.graph).forEach((e=>{e.setType(this.inputs[0].type)})),this.widgets[0].value&&this.findGetters(s.graph,!0).forEach((e=>{e.setName(this.widgets[0].value)}));s.graph._nodes.filter((e=>"easy getNode"==e.type)).forEach((e=>{e.setComboValues&&e.setComboValues()}))}},this.findGetters=function(e,t){const s=t?this.properties.previousName:this.widgets[0].value;return e._nodes.filter((e=>"easy getNode"==e.type&&e.widgets[0].value===s&&""!=s))},this.isVirtualNode=!0}onRemoved(){this.graph._nodes.filter((e=>"easy getNode"==e.type)).forEach((e=>{e.setComboValues&&e.setComboValues([this])}))}}LiteGraph.registerNodeType("easy setNode",Object.assign(e,{title:"Set"})),e.category="EasyUse/Util"}}),v.registerExtension({name:"easy getNode",registerCustomNodes(){class e extends bt{constructor(t){super("Get"),g(this,"defaultVisibility",!0),g(this,"serialize_widgets",!0),this.properties||(this.properties={}),this.properties.showOutputText=e.defaultVisibility;const s=this;s.color=LGraphCanvas.node_colors.blue.color,this.addWidget("combo","Constant","",(e=>{this.onRename()}),{values:()=>s.graph._nodes.filter((e=>"easy setNode"==e.type)).map((e=>e.widgets[0].value)).sort()}),this.addOutput("*","*"),this.onConnectionsChange=function(e,t,s,n,i){this.validateLinks(),2!=e||s?(this.onRename(),setTimeout((e=>{this.title="⬅️"+this.widgets[0].value}),1)):(this.outputs[t].type="*",this.outputs[t].name="*",this.title="Get")},this.setName=function(e){s.widgets[0].value=e,s.onRename(),s.serialize()},this.onRename=function(e=0){const t=this.findSetter(s.graph);if(t){const s=t.inputs[0].type,n=t.inputs[0].name;this.setType(s,n),this.outputs[e].type=s,this.outputs[e].name=n,this.title="⬅️"+t.widgets[0].value}else this.setType("*","*"),this.outputs[e].type="*",this.outputs[e].name="*"},this.clone=function(){const t=e.prototype.clone.apply(this);return t.size=t.computeSize(),t},this.validateLinks=function(){"*"!=this.outputs[0].type&&this.outputs[0].links&&this.outputs[0].links.forEach((e=>{const t=s.graph.links[e];t&&t.type!=this.outputs[0].type&&"*"!=t.type&&s.graph.removeLink(e)}))},this.setType=function(e,t){this.outputs[0].name=t,this.outputs[0].type=e,this.validateLinks()},this.findSetter=function(e){const t=this.widgets[0].value;return e._nodes.find((e=>"easy setNode"==e.type&&e.widgets[0].value===t&&""!=t))},this.isVirtualNode=!0}getInputLink(e){const t=this.findSetter(this.graph);if(t){const s=t.inputs[e];return this.graph.links[s.link]}throw new Error("No setter found for "+this.widgets[0].value+"("+this.type+")")}onAdded(e){}}LiteGraph.registerNodeType("easy getNode",Object.assign(e,{title:"Get"})),e.category="EasyUse/Util"}}),b.addEventListener("easyuse-global-seed",(function(e){let t=app.graph._nodes_by_id;for(let s in t){let n=t[s];if("easy globalSeed"==n.type){if(n.widgets){const t=n.widgets.find((e=>"value"==e.name));n.widgets.find((e=>"last_seed"==e.name)).value=t.value,t.value=e.detail.value}}else if(n.widgets){const t=n.widgets.find((e=>"seed_num"==e.name||"seed"==e.name||"noise_seed"==e.name));t&&null!=e.detail.seed_map[n.id]&&(t.value=e.detail.seed_map[n.id])}}}));const Lt=b.queuePrompt;b.queuePrompt=async function(e,{output:t,workflow:s}){s.seed_widgets={};for(let n in app.graph._nodes_by_id){let e=app.graph._nodes_by_id[n].widgets;if(e)for(let t in e)"seed_num"!=e[t].name&&"seed"!=e[t].name&&"noise_seed"!=e[t].name||"converted-widget"==e[t].type||(s.seed_widgets[n]=parseInt(t))}return await Lt.call(b,e,{output:t,workflow:s})};const St=["easy imageSave","easy fullkSampler","easy kSampler","easy kSamplerTiled","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerSDTurbo","easy detailerFix"];v.registerExtension({name:"Comfy.EasyUse.SaveImageExtraOutput",async beforeRegisterNodeDef(e,t,s){if(St.includes(t.name)){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?t.apply(this,arguments):void 0,n=this.widgets.find((e=>"filename_prefix"===e.name||"save_prefix"===e.name));return n.serializeValue=()=>E(s,n.value),e}}else{const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?t.apply(this,arguments):void 0;return this.properties&&"Node name for S&R"in this.properties||this.addProperty("Node name for S&R",this.constructor.type,"string"),e}}}});const Et=["easy wildcards","easy positive","easy negative","easy stylesSelector","easy promptConcat","easy promptReplace"],Ct=["easy preSampling","easy preSamplingAdvanced","easy preSamplingNoiseIn","easy preSamplingCustom","easy preSamplingDynamicCFG","easy preSamplingSdTurbo","easy preSamplingLayerDiffusion"],kt=["easy kSampler","easy kSamplerTiled","easy kSamplerInpainting","easy kSamplerDownscaleUnet","easy kSamplerSDTurbo"],At=["easy controlnetLoader","easy controlnetLoaderADV"],xt=["easy instantIDApply","easy instantIDApplyADV"],It=["easy ipadapterApply","easy ipadapterApplyADV","easy ipadapterApplyFaceIDKolors","easy ipadapterStyleComposition"],Nt=["easy pipeIn","easy pipeOut","easy pipeEdit","easy pipeEditPrompt"],Tt=["easy XYPlot","easy XYPlotAdvanced"],Ot=["easy setNode"],Dt=["Reroute","RescaleCFG","LoraLoaderModelOnly","LoraLoader","FreeU","FreeU_v2",...It,...Ot],Rt={"easy seed":{from:{INT:["Reroute",...Ct,"easy fullkSampler"]}},"easy positive":{from:{STRING:["Reroute",...Et]}},"easy negative":{from:{STRING:["Reroute",...Et]}},"easy wildcards":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy stylesSelector":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy promptConcat":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy promptReplace":{from:{STRING:["Reroute","easy showAnything",...Et]}},"easy fullLoader":{from:{PIPE_LINE:["Reroute",...Ct,"easy fullkSampler",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy a1111Loader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy comfyLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy hunyuanDiTLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy kolorsLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy pixArtLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy fluxLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy svdLoader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy zero123Loader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy sv3dLoader":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced","easy preSamplingDynamicCFG",...Nt,...Ot],MODEL:Dt},to:{STRING:["Reroute",...Et]}},"easy preSampling":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingAdvanced":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingDynamicCFG":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingCustom":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingLayerDiffusion":{from:{PIPE_LINE:["Reroute","easy kSamplerLayerDiffusion",...kt,...Nt,...At,...Tt,...Ot]}},"easy preSamplingNoiseIn":{from:{PIPE_LINE:["Reroute",...kt,...Nt,...At,...Tt,...Ot]}},"easy fullkSampler":{from:{PIPE_LINE:["Reroute",...Nt.reverse(),"easy preDetailerFix","easy preMaskDetailerFix",...Ct,...Ot]}},"easy kSampler":{from:{PIPE_LINE:["Reroute",...Nt.reverse(),"easy preDetailerFix","easy preMaskDetailerFix","easy hiresFix",...Ct,...Ot]}},"easy controlnetLoader":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}},"easy controlnetLoaderADV":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}},"easy instantIDApply":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{COMBO:["Reroute","easy promptLine"]}},"easy instantIDApplyADV":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot],MODEL:Dt},to:{COMBO:["Reroute","easy promptLine"]}},"easy ipadapterApply":{to:{COMBO:["Reroute","easy promptLine"]}},"easy ipadapterApplyADV":{to:{STRING:["Reroute","easy sliderControl",...Et],COMBO:["Reroute","easy promptLine"]}},"easy ipadapterStyleComposition":{to:{COMBO:["Reroute","easy promptLine"]}},"easy preDetailerFix":{from:{PIPE_LINE:["Reroute","easy detailerFix",...Nt,...Ot]},to:{PIPE_LINE:["Reroute","easy ultralyticsDetectorPipe","easy samLoaderPipe","easy kSampler","easy fullkSampler"]}},"easy preMaskDetailerFix":{from:{PIPE_LINE:["Reroute","easy detailerFix",...Nt,...Ot]}},"easy samLoaderPipe":{from:{PIPE_LINE:["Reroute","easy preDetailerFix",...Nt,...Ot]}},"easy ultralyticsDetectorPipe":{from:{PIPE_LINE:["Reroute","easy preDetailerFix",...Nt,...Ot]}},"easy cascadeLoader":{from:{PIPE_LINE:["Reroute","easy fullCascadeKSampler","easy preSamplingCascade",...At,...Nt,...Ot],MODEL:Dt.filter((e=>!It.includes(e)))}},"easy fullCascadeKSampler":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced",...Nt,...Ot]}},"easy preSamplingCascade":{from:{PIPE_LINE:["Reroute","easy cascadeKSampler",...Nt,...Ot]}},"easy cascadeKSampler":{from:{PIPE_LINE:["Reroute","easy preSampling","easy preSamplingAdvanced",...Nt,...Ot]}},"easy pipeEdit":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Ot]},to:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}},"easy pipeEditPrompt":{from:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Ot]},to:{PIPE_LINE:["Reroute",...Ct,...At,...xt,...Nt,...Ot]}}};v.registerExtension({name:"Comfy.EasyUse.Suggestions",async setup(e){LGraphCanvas.prototype.createDefaultNodeForSlot=function(e){e=e||{};var t,s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},e),n=s.nodeFrom&&null!==s.slotFrom,i=!n&&s.nodeTo&&null!==s.slotTo;if(!n&&!i)return!1;if(!s.nodeType)return!1;var o=n?s.nodeFrom:s.nodeTo,a=n?s.slotFrom:s.slotTo,l=o.type,r=!1;switch(typeof a){case"string":r=n?o.findOutputSlot(a,!1):o.findInputSlot(a,!1),a=n?o.outputs[a]:o.inputs[a];break;case"object":r=n?o.findOutputSlot(a.name):o.findInputSlot(a.name);break;case"number":r=a,a=n?o.outputs[a]:o.inputs[a];break;default:return!1}var d=a.type==LiteGraph.EVENT?"_event_":a.type,u=n?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(u&&u[d]){a.link;let e=!1;const i=n?"from":"to";if(Rt[l]&&Rt[l][i]&&(null==(t=Rt[l][i][d])?void 0:t.length)>0){for(var c in Rt[l][i][d])if(s.nodeType==Rt[l][i][d][c]||"AUTO"==s.nodeType){e=Rt[l][i][d][c];break}}else if("object"==typeof u[d]||"array"==typeof u[d]){for(var c in u[d])if(s.nodeType==u[d][c]||"AUTO"==s.nodeType){e=u[d][c];break}}else s.nodeType!=u[d]&&"AUTO"!=s.nodeType||(e=u[d]);if(e){var p=!1;"object"==typeof e&&e.node&&(p=e,e=e.node);var h=LiteGraph.createNode(e);if(h){if(p){if(p.properties)for(var m in p.properties)h.addProperty(m,p.properties[m]);if(p.inputs)for(var m in h.inputs=[],p.inputs)h.addOutput(p.inputs[m][0],p.inputs[m][1]);if(p.outputs)for(var m in h.outputs=[],p.outputs)h.addOutput(p.outputs[m][0],p.outputs[m][1]);p.title&&(h.title=p.title),p.json&&h.configure(p.json)}return this.graph.add(h),h.pos=[s.position[0]+s.posAdd[0]+(s.posSizeFix[0]?s.posSizeFix[0]*h.size[0]:0),s.position[1]+s.posAdd[1]+(s.posSizeFix[1]?s.posSizeFix[1]*h.size[1]:0)],n?s.nodeFrom.connectByType(r,h,d):s.nodeTo.connectByTypeOutput(r,h,d),!0}}}return!1},LGraphCanvas.prototype.showConnectionMenu=function(e){e=e||{};var t,s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null,allow_searchbox:this.allow_searchbox,showSearchBox:this.showSearchBox},e),n=this,i=s.nodeFrom&&s.slotFrom,o=!i&&s.nodeTo&&s.slotTo;if(!i&&!o)return!1;var a=i?s.nodeFrom:s.nodeTo,l=i?s.slotFrom:s.slotTo,r=!1;switch(typeof l){case"string":r=i?a.findOutputSlot(l,!1):a.findInputSlot(l,!1),l=i?a.outputs[l]:a.inputs[l];break;case"object":r=i?a.findOutputSlot(l.name):a.findInputSlot(l.name);break;case"number":r=l,l=i?a.outputs[l]:a.inputs[l];break;default:return!1}var d=["Add Node",null];s.allow_searchbox&&(d.push("Search"),d.push(null));var u=l.type==LiteGraph.EVENT?"_event_":l.type,c=i?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in,p=a.type;if(c&&c[u]){const e=i?"from":"to";if(Rt[p]&&Rt[p][e]&&(null==(t=Rt[p][e][u])?void 0:t.length)>0)for(var h in Rt[p][e][u])d.push(Rt[p][e][u][h]);else if("object"==typeof c[u]||"array"==typeof c[u])for(var h in c[u])d.push(c[u][h]);else d.push(c[u])}var m=new LiteGraph.ContextMenu(d,{event:s.e,title:(l&&""!=l.name?l.name+(u?" | ":""):"")+(l&&u?u:""),callback:function(e,t,o){switch(e){case"Add Node":LGraphCanvas.onMenuAdd(null,null,o,m,(function(e){i?s.nodeFrom.connectByType(r,e,u):s.nodeTo.connectByTypeOutput(r,e,u)}));break;case"Search":i?s.showSearchBox(o,{node_from:s.nodeFrom,slot_from:l,type_filter_in:u}):s.showSearchBox(o,{node_to:s.nodeTo,slot_from:l,type_filter_out:u});break;default:n.createDefaultNodeForSlot(Object.assign(s,{position:[s.e.canvasX,s.e.canvasY],nodeType:e}))}}});return!1}}}),v.registerExtension({name:"Comfy.EasyUse.TimeTaken",setup(){let e=new Map,t=0;b.addEventListener("execution_start",(e=>{graph&&graph._nodes.forEach((e=>{e.executionDuration&&delete e.executionDuration}))})),b.addEventListener("executing",(s=>{if(!A("EasyUse.Nodes.Runtime",null,!0))return;const n=(null==s?void 0:s.node)||(null==s?void 0:s.detail)||null,i=e.get(t);if(e.delete(t),t&&i){const e=Date.now()-i,s=ve(t);s&&(s.executionDuration||(s.executionDuration=0),s.executionDuration=s.executionDuration+e/1e3)}t=n,e.set(n,Date.now())}))},beforeRegisterNodeDef(e,t){const s=e.prototype.onDrawForeground;e.prototype.onDrawForeground=function(...e){const[t]=e;return function(e,t){if(!t)return;t=parseFloat(t).toFixed(3)+Y("s"),e.save(),e.fillStyle=LiteGraph.NODE_DEFAULT_BGCOLOR,function(e,t,s,n,i,o){e.beginPath(),e.moveTo(t+o,s),e.lineTo(t+n-o,s),e.arcTo(t+n,s,t+n,s+o,o),e.lineTo(t+n,s+i-o),e.arcTo(t+n,s+i,t+n-o,s+i,o),e.lineTo(t+o,s+i),e.arcTo(t,s+i,t,s+i-o,o),e.lineTo(t,s+o),e.arcTo(t,s,t+o,s,o),e.closePath()}(e,0,-LiteGraph.NODE_TITLE_HEIGHT-20,e.measureText(t).width+10,LiteGraph.NODE_TITLE_HEIGHT-10,4),e.fill(),function(e,t,s,n,i="#000",o=12,a="Inter"){e.font=`${o}px ${a}`,e.fillStyle=i,e.fillText(t,s,n)}(e,t,8,-LiteGraph.NODE_TITLE_HEIGHT-6,LiteGraph.NODE_TITLE_COLOR),e.restore()}(t,this.executionDuration),null==s?void 0:s.apply(this,e)}}});let Gt=null;v.registerExtension({name:"Comfy.EasyUse.HotKeys",setup(){if(void 0!==y){y("up,down,left,right",(function(e,t){var s,n,i,o,a,l,r,d,u,c,p,h,m,g,f;e.preventDefault();if(!A("EasyUse.Hotkeys.JumpNearestNodes",null,!0))return;const y=be();if(0===y.length)return;const _=y[0];switch(t.key){case"up":case"left":let e=null;if(ke(_)){const e=null==(s=_.widgets_values)?void 0:s[0],t=null==(n=_.graph)?void 0:n._nodes,i=null==t?void 0:t.find((t=>{var s;if(Ae(t)){if((null==(s=t.widgets_values)?void 0:s[0])===e)return t}return null}));i&&Le(i)}else if((null==(i=_.inputs)?void 0:i.length)>0){for(let t=0;t<_.inputs.length;t++)if(_.inputs[t].link){e=_.inputs[t].link;break}if(e){const t=null==(o=_.graph)?void 0:o.links;if(t[e]){const s=null==(a=t[e])?void 0:a.origin_id,n=null==(r=null==(l=_.graph)?void 0:l._nodes_by_id)?void 0:r[s];n&&Le(n)}}}break;case"down":case"right":let t=null;if(Ae(_)){const e=null==(d=_.widgets_values)?void 0:d[0],t=null==(u=_.graph)?void 0:u._nodes,s=null==t?void 0:t.find((t=>{var s;if(ke(t)){if((null==(s=t.widgets_values)?void 0:s[0])===e)return t}return null}));s&&Le(s)}else if((null==(c=_.outputs)?void 0:c.length)>0){for(let e=0;e<_.outputs.length;e++)if((null==(p=_.outputs[e].links)?void 0:p.length)>0&&_.outputs[e].links[0]){t=_.outputs[e].links[0];break}if(t){const e=null==(h=_.graph)?void 0:h.links;if(e[t]){const s=null==(m=e[t])?void 0:m.target_id,n=null==(f=null==(g=_.graph)?void 0:g._nodes_by_id)?void 0:f[s];n&&Le(n)}}}}})),y("shift+up,shift+down,shift+left,shift+right,shift+alt+⌘+left,shift+alt+⌘+right,shift+alt+ctrl+left,shift+alt+ctrl+right",(function(e,t){e.preventDefault();if(!A("EasyUse.Hotkeys.AlignSelectedNodes",null,!0))return;const s=be();if(s.length<=1)return;const n=s;switch(t.key){case"shift+up":LGraphCanvas.alignNodes(n,"top",n[0]);break;case"shift+down":LGraphCanvas.alignNodes(n,"bottom",n[0]);break;case"shift+left":LGraphCanvas.alignNodes(n,"left",n[0]);break;case"shift+right":LGraphCanvas.alignNodes(n,"right",n[0]);break;case"shift+alt+ctrl+left":case"shift+alt+⌘+left":Fe(n,"horizontal");break;case"shift+alt+ctrl+right":case"shift+alt+⌘+right":Fe(n,"vertical")}Gt||(Gt=$()),Gt&&Gt.update()})),y("shift+⌘+left,shift+⌘+right,shift+ctrl+left,shift+ctrl+right",(function(e,t){e.preventDefault();if(!A("EasyUse.Hotkeys.NormalizeSelectedNodes",null,!0))return;const s=be();if(s.length<=1)return;const n=s;switch(t.key){case"shift+ctrl+left":case"shift+⌘+left":Pe(n,"width");break;case"shift+ctrl+right":case"shift+⌘+right":Pe(n,"height")}Gt||(Gt=$()),Gt&&Gt.update()})),y("shift+g",(function(e,t){e.preventDefault();A("EasyUse.Hotkeys.AddGroup",null,!0)&&(Pt(),Gt||(Gt=$()),Gt&&Gt.update())})),y("shift+r",(function(e,t){e.preventDefault();A("EasyUse.Hotkeys.cleanVRAMused",null,!0)&&Qe()})),y("shift+m",(function(e,t){var s,n,i;if(!A("EasyUse.Hotkeys.toggleNodesMap",null,!0))return;let o=(null==(s=v.extensionManager)?void 0:s.sidebarTab)||v.extensionManager,a=(null==(n=v.extensionManager.sidebarTab)?void 0:n.activeSidebarTabId)||(null==(i=v.extensionManager)?void 0:i.activeSidebarTab);o.activeSidebarTabId=a==P?null:P}));const e=[];Array.from(Array(10).keys()).forEach((t=>e.push(`alt+${t}`))),y(e.join(","),(async function(e,t){e.preventDefault();if(!A("EasyUse.Hotkeys.NodesTemplate",null,!0))return;const s=t.key;let n=parseInt(s.split("+")[1]);const i=await b.getUserData("comfy.templates.json");let o=null;if(200==i.status)try{o=await i.json()}catch(l){Ue.error(Y("Get Node Templates File Failed"))}else localStorage["Comfy.NodeTemplates"]?o=JSON.parse(localStorage["Comfy.NodeTemplates"]):Ue.warn(Y("No Node Templates Found"));if(!o)return void Ue.warn(Y("No Node Templates Found"));n=0===n?9:n-1;const a=o[n];if(a)try{const e=(null==a?void 0:a.name)||"Group",t=(null==a?void 0:a.data)?JSON.parse(a.data):[];Mt((async()=>{await C.registerFromWorkflow(t.groupNodes,{}),localStorage.litegrapheditor_clipboard=a.data,v.canvas.pasteFromClipboard(),t.groupNodes||Pt(e)}))}catch(l){Ue.error(l)}else Ue.warn(Y("Node template with {key} not set").replace("{key}",s))}));const t=async function(e){if(("b"===e.key||"m"==e.key)&&(e.metaKey||e.ctrlKey)){if(0===be().length)return;Gt||(Gt=$()),Gt&&Gt.update()}};window.addEventListener("keydown",t,!0)}}});const Mt=async e=>{const t=localStorage.litegrapheditor_clipboard;await e(),localStorage.litegrapheditor_clipboard=t},Pt=e=>{const t=be();if(0===t.length)return;const s=t;let n=new LiteGraph.LGraphGroup;n.title=e||"Group",((e,t=[],s=20)=>{var n,i,o,a,l,r,d,u,c,p;for(var h of(i=o=a=l=-1,r=d=u=c=-1,[e._nodes,t]))for(var m in h)r=(p=h[m]).pos[0],d=p.pos[1],u=p.pos[0]+p.size[0],c=p.pos[1]+p.size[1],"Reroute"!=p.type&&(d-=LiteGraph.NODE_TITLE_HEIGHT),(null==(n=p.flags)?void 0:n.collapsed)&&(c=d+LiteGraph.NODE_TITLE_HEIGHT,(null==p?void 0:p._collapsed_width)&&(u=r+Math.round(p._collapsed_width))),(-1==i||ra)&&(a=u),(-1==l||c>l)&&(l=c);o-=Math.round(1.4*e.font_size),e.pos=[i-s,o-s],e.size=[a-i+2*s,l-o+2*s]})(n,s),v.canvas.graph.add(n)};function Ft(e,t,s,n){const i=[];return e.workflow.links.forEach((e=>{s&&e[1]===t&&!i.includes(e[3])&&i.push(e[3]),n&&e[3]===t&&!i.includes(e[1])&&i.push(e[1])})),i}async function Ut(e,t=!1){const s=structuredClone(await v.graphToPrompt()),n=[];if(s.workflow.nodes.forEach((e=>{n.push(e.id)})),s.workflow.links=s.workflow.links.filter((e=>n.includes(e[1])&&n.includes(e[3]))),t)for(;!v.graph._nodes_by_id[e].isChooser;)e=Ft(s,e,!0,!1)[0];const i=function(e,t){const s=[],n=[t];for(;n.length>0;){const t=n.pop();s.push(t),n.push(...Ft(e,t,!0,!1).filter((e=>!(s.includes(e)||n.includes(e)))))}n.push(...s.filter((e=>e!=t)));const i=[t];for(;n.length>0;){const t=n.pop();i.push(t),n.push(...Ft(e,t,!1,!0).filter((e=>!(i.includes(e)||n.includes(e)))))}const o=[];return o.push(...s),o.push(...i.filter((e=>!o.includes(e)))),o}(s,e);s.workflow.nodes=s.workflow.nodes.filter((t=>(t.id===e&&t.inputs.forEach((e=>{e.link=null})),i.includes(t.id)))),s.workflow.links=s.workflow.links.filter((e=>i.includes(e[1])&&i.includes(e[3])));const o={};for(const[r,d]of Object.entries(s.output))i.includes(parseInt(r))&&(o[r]=d);const a={};for(const[r,d]of Object.entries(o[e.toString()].inputs))Array.isArray(d)||(a[r]=d);o[e.toString()].inputs=a,s.output=o;const l=v.graphToPrompt;v.graphToPrompt=()=>(v.graphToPrompt=l,s),v.queuePrompt(0)}const Bt=new class{constructor(){this.current_node_id=void 0,this.class_of_current_node=null,this.current_node_is_chooser=!1}update(){var e,t;return v.runningNodeId!=this.current_node_id&&(this.current_node_id=v.runningNodeId,this.current_node_id?(this.class_of_current_node=null==(t=null==(e=v.graph)?void 0:e._nodes_by_id[v.runningNodeId.toString()])?void 0:t.comfyClass,this.current_node_is_chooser="easy imageChooser"===this.class_of_current_node):(this.class_of_current_node=void 0,this.current_node_is_chooser=!1),!0)}},zt=class e{constructor(){}static idle(){return!v.runningNodeId}static paused(){return!0}static paused_here(t){return e.here(t)}static running(){return!e.idle()}static here(e){return v.runningNodeId==e}static state(){return"Paused"}};g(zt,"cancelling",!1);let Wt=zt;function jt(e,t){const s=new FormData;s.append("message",t),s.append("id",e),b.fetchApi("/easyuse/image_chooser_message",{method:"POST",body:s})}function Vt(){jt(-1,"__cancel__"),Wt.cancelling=!0,b.interrupt(),Wt.cancelling=!1}var Yt=0;function Ht(){Yt+=1}const Xt=["easy kSampler","easy kSamplerTiled","easy fullkSampler"];function Zt(e){const t=v.graph._nodes_by_id[e.detail.id];if(t){t.selected_images=new Set,t.anti_selected=new Set;const s=function(e,t){var s;return e.imgs=[],t.forEach((t=>{const s=new Image;e.imgs.push(s),s.onload=()=>{v.graph.setDirtyCanvas(!0)},s.src=`/view?filename=${encodeURIComponent(t.filename)}&type=temp&subfolder=${v.getPreviewFormatParam()}`})),null==(s=e.setSizeForImage)||s.call(e),e.imgs}(t,e.detail.urls);return{node:t,image:s,isKSampler:Xt.includes(t.type)}}}function Kt(e,t,s){var n;if(e.imageRects)n=e.imageRects[t];else{const t=e.imagey;n=[1,t+1,e.size[0]-2,e.size[1]-t-2]}s.strokeRect(n[0]+1,n[1]+1,n[2]-2,n[3]-2)}class Jt extends L{constructor(){super(),this.node=null,this.select_index=[],this.dialog_div=null}show(e,t){this.select_index=[],this.node=t;const s=e.map(((e,s)=>{const n=w("img",{src:e.src,onclick:e=>{this.select_index.includes(s)?(this.select_index=this.select_index.filter((e=>e!==s)),n.classList.remove("selected")):(this.select_index.push(s),n.classList.add("selected")),t.selected_images.has(s)?t.selected_images.delete(s):t.selected_images.add(s)}});return n}));super.show(w("div.comfyui-easyuse-chooser-dialog",[w("h5.comfyui-easyuse-chooser-dialog-title",Y("Choose images to continue")),w("div.comfyui-easyuse-chooser-dialog-images",s)]))}createButtons(){const e=super.createButtons();return e[0].onclick=e=>{Wt.running()&&Vt(),super.close()},e.unshift(w("button",{type:"button",textContent:Y("Choose Selected Images"),onclick:e=>{jt(this.node.id,[...this.node.selected_images,-1,...this.node.anti_selected]),Wt.idle()&&(Ht(),Ut(this.node.id).then((()=>{jt(this.node.id,[...this.node.selected_images,-1,...this.node.anti_selected])}))),super.close()}})),e}}function $t(){const e=v.graph._nodes_by_id[this.node_id];if(e){const t=[...e.selected_images];(null==t?void 0:t.length)>0&&e.setProperty("values",t),jt(e.id,[...e.selected_images,-1,...e.anti_selected]),Wt.idle()&&(Ht(),Ut(e.id).then((()=>{jt(e.id,[...e.selected_images,-1,...e.anti_selected])})))}}function qt(){Wt.running()&&Vt()}function Qt(e){Object.defineProperty(e,"clicked",{get:function(){return this._clicked},set:function(e){this._clicked=e&&""!=this.name}})}function es(e){e.options||(e.options={}),e.options.serialize=!1}v.registerExtension({name:"Comfy.EasyUse.imageChooser",init(){window.addEventListener("beforeunload",Vt,!0)},setup(e){const t=LGraphCanvas.prototype.draw;LGraphCanvas.prototype.draw=function(){Bt.update()&&e.graph._nodes.forEach((e=>{e.update&&e.update()})),t.apply(this,arguments)},b.addEventListener("easyuse-image-choose",(function(e){const{node:t,image:s,isKSampler:n}=Zt(e);if(n){(new Jt).show(s,t)}}));const s=b.interrupt;b.interrupt=function(){Wt.cancelling||Vt(),s.apply(this,arguments)},b.addEventListener("execution_start",(function(){(Yt>0?(Yt-=1,0):(jt(-1,"__start__"),1))&&e.graph._nodes.forEach((e=>{(e.selected_images||e.anti_selected)&&(e.selected_images.clear(),e.anti_selected.clear(),e.update())}))}))},async nodeCreated(e,t){if("easy imageChooser"==e.comfyClass){e.setProperty("values",[]),void 0===(null==e?void 0:e.imageIndex)&&Object.defineProperty(e,"imageIndex",{get:function(){return null},set:function(t){e.overIndex=t}}),void 0===(null==e?void 0:e.imagey)&&Object.defineProperty(e,"imagey",{get:function(){return null},set:function(t){return e.widgets[e.widgets.length-1].last_y+LiteGraph.NODE_WIDGET_HEIGHT}});const t=e.onMouseDown;e.onMouseDown=function(s,n,i){if(s.isPrimary){const t=function(e,t){var s,n;if((null==(s=e.imgs)?void 0:s.length)>1)for(var i=0;i0&&s0&&ne.imagey)return 0;return-1}(e,n);t>=0&&this.imageClicked(t)}return t&&t.apply(this,arguments)},e.send_button_widget=e.addWidget("button","","",$t),e.cancel_button_widget=e.addWidget("button","","",qt),Qt(e.cancel_button_widget),Qt(e.send_button_widget),es(e.cancel_button_widget),es(e.send_button_widget)}},beforeRegisterNodeDef(e,t,s){if("easy imageChooser"==(null==t?void 0:t.name)){const t=e.prototype.onDrawBackground;e.prototype.onDrawBackground=function(e){t.apply(this,arguments),function(e,t){var s;if(e.imgs){if(e.imageRects)for(let s=0;s{Kt(e,s,t)})),t.strokeStyle="#F88",null==(s=null==e?void 0:e.anti_selected)||s.forEach((s=>{Kt(e,s,t)}))}}(this,e)},e.prototype.imageClicked=function(t){"easy imageChooser"===(null==e?void 0:e.comfyClass)&&(this.selected_images.has(t)?this.selected_images.delete(t):this.selected_images.add(t),this.update())};const s=e.prototype.update;e.prototype.update=function(){var e;if(s&&s.apply(this,arguments),this.send_button_widget){this.send_button_widget.node_id=this.id;const t=(this.selected_images?this.selected_images.size:0)+(this.anti_selected?this.anti_selected.size:0),s=(null==(e=this.imgs)?void 0:e.length)||0;Wt.paused_here(this.id)&&t>0?this.send_button_widget.name=t>1?"Progress selected ("+t+"/"+s+")":"Progress selected image":this.send_button_widget.name=t>0?t>1?"Progress selected ("+t+"/"+s+")":"Progress selected image as restart":""}if(this.cancel_button_widget){const e=Wt.running();this.cancel_button_widget.name=e?"Cancel current run":""}this.setDirtyCanvas(!0,!0)}}}}),Number.prototype.div=function(e){return function(e,t){let s,n,i=0,o=0,a="string"==typeof e?e:e.toString(),l="string"==typeof t?t:t.toString();try{i=a.toString().split(".")[1].length}catch(r){}try{o=l.toString().split(".")[1].length}catch(r){}return s=Number(a.toString().replace(".","")),n=Number(l.toString().replace(".","")),s/n*Math.pow(10,o-i)}(this,e)};let ts=[],ss=0;const ns={sd3:6.5,"sd3-turbo":4};class is extends L{constructor(){super(),this.lists=[],this.dialog_div=null,this.user_div=null}addItem(e,t){return w("div.easyuse-account-dialog-item",[w("input",{type:"text",placeholder:"Enter name",oninput:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts[t].name=e.target.value},value:ts[e].name}),w("input.key",{type:"text",oninput:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts[t].key=e.target.value},placeholder:"Enter APIKEY",value:ts[e].key}),w("button.choose",{textContent:Y("Choose"),onclick:async e=>{var s,n,i;const o=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);let a=ts[o].name,l=ts[o].key;if(!a)return void Ue.error(Y("Please enter the account name"));if(!l)return void Ue.error(Y("Please enter the APIKEY"));let r=!0;for(let t=0;t{(new is).show(t)}},[w("div.user",[w("div.avatar",o?[w("img",{src:o})]:"😀"),w("div.info",[w("h5.name",a),w("h6.remark","Credits: "+l)])]),w("div.edit",{textContent:Y("Edit")})])),Ue.success(Y("Save Succeed"))}else Ue.success(Y("Save Succeed"));this.close()}else Ue.error(Y("Save Failed"))}}),w("button.delete",{textContent:Y("Delete"),onclick:e=>{const t=Array.prototype.indexOf.call(this.dialog_div.querySelectorAll(".easyuse-account-dialog-item"),e.target.parentNode);ts.length<=1?Ue.error(Y("At least one account is required")):(ts.splice(t,1),this.dialog_div.removeChild(e.target.parentNode))}})])}show(e){ts.forEach(((t,s)=>{this.lists.push(this.addItem(s,e))})),this.dialog_div=w("div.easyuse-account-dialog",this.lists),super.show(w("div.easyuse-account-dialog-main",[w("div",[w("a",{href:"https://platform.stability.ai/account/keys",target:"_blank",textContent:Y("Getting Your APIKEY")})]),this.dialog_div]))}createButtons(){const e=super.createButtons();return e.unshift(w("button",{type:"button",textContent:Y("Save Account Info"),onclick:e=>{let t=!0;for(let s=0;s{200==e.status?Ue.success(Y("Save Succeed")):Ue.error(Y("Save Failed"))}))}else Ue.error(Y("APIKEY is not Empty"))}})),e.unshift(w("button",{type:"button",textContent:Y("Add Account"),onclick:e=>{const t="Account "+ts.length.toString();ts.push({name:t,key:""});const s=this.addItem(ts.length-1);this.lists.push(s),this.dialog_div.appendChild(s)}})),e}}v.registerExtension({name:"Comfy.EasyUse.API.SD3",async beforeRegisterNodeDef(e,t,s){if("easy stableDiffusion3API"==t.name){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=async function(){var e,s,n;t&&(null==t||t.apply(this,arguments));const i=this.widgets.find((e=>["seed_num","seed"].includes(e.name))),o=this.widgets.find((e=>["control_before_generate","control_after_generate"].includes(e.name)));let a=this.widgets.find((e=>"model"==e.name));a.callback=e=>{l.value="-"+ns[e]};const l=this.addWidget("text","cost_credit","0",(e=>{}),{serialize:!1});l.disabled=!0,setTimeout((e=>{"control_before_generate"==o.name&&0===i.value&&(i.value=Math.floor(4294967294*Math.random())),l.value="-"+ns[a.value]}),100);let r=w("div.easyuse-account-user",[Y("Loading UserInfo...")]);this.addDOMWidget("account","btn",w("div.easyuse-account",r)),b.addEventListener("stable-diffusion-api-generate-succeed",(async({detail:e})=>{var t;let s=r.querySelectorAll(".remark");if(s&&s[0]){const t=(null==e?void 0:e.model)?ns[e.model]:0;if(t){let e=function(e,t){let s,n,i,o,a,l;a="string"==typeof e?e:e.toString(),l="string"==typeof t?t:t.toString();try{s=a.split(".")[1].length}catch(r){s=0}try{n=l.split(".")[1].length}catch(r){n=0}return i=Math.pow(10,Math.max(s,n)),o=s>=n?s:n,((e*i-t*i)/i).toFixed(o)}(parseFloat(s[0].innerText.replace(/Credits: /g,"")),t);e>0&&(s[0].innerText="Credits: "+e.toString())}}await X(1e4);const n=await b.fetchApi("/easyuse/stability/balance");if(200==n.status){const e=await n.json();if(null==e?void 0:e.balance){const n=(null==(t=e.balance)?void 0:t.credits)||0;s&&s[0]&&(s[0].innerText="Credits: "+n)}}}));const d=await b.fetchApi("/easyuse/stability/api_keys");if(200==d.status){let t=await d.json();if(ts=t.keys,ss=t.current,ts.length>0&&void 0!==ss){const t=ts[ss].key,i=ts[ss].name;if(t){const t=await b.fetchApi("/easyuse/stability/user_info");if(200==t.status){const i=await t.json();if((null==i?void 0:i.account)&&(null==i?void 0:i.balance)){const t=(null==(e=i.account)?void 0:e.profile_picture)||null,o=(null==(s=i.account)?void 0:s.email)||null,a=(null==(n=i.balance)?void 0:n.credits)||0;r.replaceChildren(w("div.easyuse-account-user-info",{onclick:e=>{(new is).show(r)}},[w("div.user",[w("div.avatar",t?[w("img",{src:t})]:"😀"),w("div.info",[w("h5.name",o),w("h6.remark","Credits: "+a)])]),w("div.edit",{textContent:Y("Edit")})]))}}}else r.replaceChildren(w("div.easyuse-account-user-info",{onclick:e=>{(new is).show(r)}},[w("div.user",[w("div.avatar","😀"),w("div.info",[w("h5.name",i),w("h6.remark",Y("Click to set the APIKEY first"))])]),w("div.edit",{textContent:Y("Edit")})]))}}}}}});let os=null;function as(){os&&(os.removeEventListeners(),os.dropdown.remove(),os=null)}function ls(e,t,s,n=!1){as(),new rs(e,t,s,n)}class rs{constructor(e,t,s,n=!1){this.dropdown=document.createElement("ul"),this.dropdown.setAttribute("role","listbox"),this.dropdown.classList.add("easy-dropdown"),this.selectedIndex=-1,this.inputEl=e,this.suggestions=t,this.onSelect=s,this.isDict=n,this.focusedDropdown=this.dropdown,this.buildDropdown(),this.onKeyDownBound=this.onKeyDown.bind(this),this.onWheelBound=this.onWheel.bind(this),this.onClickBound=this.onClick.bind(this),this.addEventListeners()}buildDropdown(){this.isDict?this.buildNestedDropdown(this.suggestions,this.dropdown):this.suggestions.forEach(((e,t)=>{this.addListItem(e,t,this.dropdown)}));const e=this.inputEl.getBoundingClientRect();this.dropdown.style.top=e.top+e.height-10+"px",this.dropdown.style.left=e.left+"px",document.body.appendChild(this.dropdown),os=this}buildNestedDropdown(e,t){let s=0;Object.keys(e).forEach((n=>{const i=e[n];if("object"==typeof i&&null!==i){const e=document.createElement("ul");e.setAttribute("role","listbox"),e.classList.add("easy-nested-dropdown");const o=document.createElement("li");o.classList.add("folder"),o.textContent=n,o.appendChild(e),o.addEventListener("mouseover",this.onMouseOver.bind(this,s,t)),t.appendChild(o),this.buildNestedDropdown(i,e),s+=1}else{const e=document.createElement("li");e.classList.add("item"),e.setAttribute("role","option"),e.textContent=n,e.addEventListener("mouseover",this.onMouseOver.bind(this,s,t)),e.addEventListener("mousedown",this.onMouseDown.bind(this,n)),t.appendChild(e),s+=1}}))}addListItem(e,t,s){const n=document.createElement("li");n.setAttribute("role","option"),n.textContent=e,n.addEventListener("mouseover",this.onMouseOver.bind(this,t)),n.addEventListener("mousedown",this.onMouseDown.bind(this,e)),s.appendChild(n)}addEventListeners(){document.addEventListener("keydown",this.onKeyDownBound),this.dropdown.addEventListener("wheel",this.onWheelBound),document.addEventListener("click",this.onClickBound)}removeEventListeners(){document.removeEventListener("keydown",this.onKeyDownBound),this.dropdown.removeEventListener("wheel",this.onWheelBound),document.removeEventListener("click",this.onClickBound)}onMouseOver(e,t){t&&(this.focusedDropdown=t),this.selectedIndex=e,this.updateSelection()}onMouseOut(){this.selectedIndex=-1,this.updateSelection()}onMouseDown(e,t){t.preventDefault(),this.onSelect(e),this.dropdown.remove(),this.removeEventListeners()}onKeyDown(e){const t=Array.from(this.focusedDropdown.children),s=t[this.selectedIndex];if(os)if(38===e.keyCode)e.preventDefault(),this.selectedIndex=Math.max(0,this.selectedIndex-1),this.updateSelection();else if(40===e.keyCode)e.preventDefault(),this.selectedIndex=Math.min(t.length-1,this.selectedIndex+1),this.updateSelection();else if(39===e.keyCode){if(e.preventDefault(),s&&s.classList.contains("folder")){const e=s.querySelector(".easy-nested-dropdown");e&&(this.focusedDropdown=e,this.selectedIndex=0,this.updateSelection())}}else if(37===e.keyCode&&this.focusedDropdown!==this.dropdown){const e=this.focusedDropdown.closest(".easy-dropdown, .easy-nested-dropdown").parentNode.closest(".easy-dropdown, .easy-nested-dropdown");e&&(this.focusedDropdown=e,this.selectedIndex=Array.from(e.children).indexOf(this.focusedDropdown.parentNode),this.updateSelection())}else if((13===e.keyCode||9===e.keyCode)&&this.selectedIndex>=0){e.preventDefault(),s.classList.contains("item")&&(this.onSelect(t[this.selectedIndex].textContent),this.dropdown.remove(),this.removeEventListeners());const n=s.querySelector(".easy-nested-dropdown");n&&(this.focusedDropdown=n,this.selectedIndex=0,this.updateSelection())}else 27===e.keyCode&&(this.dropdown.remove(),this.removeEventListeners())}onWheel(e){const t=parseInt(this.dropdown.style.top);localStorage.getItem("Comfy.Settings.Comfy.InvertMenuScrolling")?this.dropdown.style.top=t+(e.deltaY<0?10:-10)+"px":this.dropdown.style.top=t+(e.deltaY<0?-10:10)+"px"}onClick(e){this.dropdown.contains(e.target)||e.target===this.inputEl||(this.dropdown.remove(),this.removeEventListeners())}updateSelection(){Array.from(this.focusedDropdown.children).forEach(((e,t)=>{t===this.selectedIndex?e.classList.add("selected"):e.classList.remove("selected")}))}}function ds(e){const t=e.min||0,s=e.max||0,n=e.step||1;if(0===n)return[];const i=[];let o=t;for(;o<=s;){if(Number.isInteger(n))i.push(Math.round(o)+"; ");else{let e=o.toFixed(3);-0==e&&(e="0.000"),/\.\d{3}$/.test(e)||(e+="0"),i.push(e+"; ")}o+=n}return s>=0&&t>=0?i:i.reverse()}let us={},cs={};function ps(e,t){String(e.id);const s=t.name,n=t.value.replace(/^(loader|preSampling):\s/,"");cs[s]?cs[s]!=us[n]&&(cs[s]=us[n]):cs={...cs,[s]:us[n]}}v.registerExtension({name:"Comfy.EasyUse.XYPlot",async beforeRegisterNodeDef(e,t,s){if("easy XYPlot"===t.name){us=t.input.hidden.plot_dict[0];for(const e in us){const t=us[e];if(Array.isArray(t)){let s=[];for(const e of t)s.push(e+"; ");us[e]=s}else us[e]="object"==typeof t?"seed"==e?t+"; ":ds(t):t+"; "}us.None=[],us["---------------------"]=[]}},nodeCreated(e){"easy XYPlot"===e.comfyClass&&(function(e){if(e.widgets)for(const t of e.widgets)if("x_axis"===t.name||"y_axis"===t.name){let s=t.value;Object.defineProperty(t,"value",{get:()=>s,set(n){n!==s&&(s=n,ps(e,t))}})}}(e),function(e){if(e.widgets){const t=e.widgets.filter((e=>"customtext"===e.type&&!1!==e.dynamicPrompts||e.dynamicPrompts));for(const e of t){let t=function(e,t,n,i){return e&&(t[n]=e),t.map((e=>s(e,i))).filter((e=>""!==e)).join("")},s=function(e,t){if(e=n(e),i(e,t))return e+"; ";let s=o(e,t);return 1===s.length||2===s.length?s[0]:i(a(e),t)?a(e)+"; ":""},n=function(e){return e.replace(/(\n|;| )/g,"")},i=function(e,t){return t.includes(e+"; ")},o=function(e,t){return t.filter((t=>t.toLowerCase().includes(e.toLowerCase())))},a=function(e){return Number(e)?Number(e).toFixed(3):["0","0.","0.0","0.00","00"].includes(e)?"0.000":e};const l=function(){const s=e.name[0]+"_axis";let n=(null==cs?void 0:cs[s])||[];if(0===n.length)return;const i=e.inputEl.value,o=e.inputEl.selectionStart;let a=i.split("; ");const l=i.substring(0,o).split("; ").length-1,r=a[l].replace(/\n/g,"").toLowerCase(),d=n.filter((e=>e.toLowerCase().includes(r))).map((e=>e.replace(/; /g,"")));if(d.length>0)ls(e.inputEl,d,(s=>{const i=t(s,a,l,n);e.inputEl.value=i}));else{as();const s=t(null,a,l,n);e.inputEl.value=s}};e.inputEl.removeEventListener("input",l),e.inputEl.addEventListener("input",l),e.inputEl.removeEventListener("mouseup",l),e.inputEl.addEventListener("mouseup",l)}}}(e))}});export{Y as $,M as N,b as a,v as b,fe as c,A as d,Qe as e,P as f,ge as g,x as h,Se as j,V as l,X as s,Ue as t,$ as u}; diff --git a/web_version/v2/easyuse.js b/web_version/v2/easyuse.js index 30d1a68..35beace 100644 --- a/web_version/v2/easyuse.js +++ b/web_version/v2/easyuse.js @@ -1,2 +1,2 @@ -!function(){"use strict";try{if("undefined"!=typeof document){var e=document.createElement("style");e.appendChild(document.createTextNode('@charset "UTF-8";.easyuse-model-info{color:#fff;max-width:90vw;font-family:var(--font-family)}.easyuse-model-content{display:flex;flex-direction:column;overflow:hidden}.easyuse-model-header{margin:0 0 15px}.easyuse-model-header-remark{display:flex;align-items:center;margin-top:5px}.easyuse-model-info h2{text-align:left;margin:0}.easyuse-model-info h5{text-align:left;margin:0 15px 0 0;font-weight:400;color:var(--descrip-text)}.easyuse-model-info p{margin:5px 0}.easyuse-model-info a{color:var(--theme-color-light)}.easyuse-model-info a:hover{text-decoration:underline}.easyuse-model-tags-list{display:flex;flex-wrap:wrap;list-style:none;gap:10px;max-height:200px;overflow:auto;margin:10px 0;padding:0}.easyuse-model-tag{background-color:var(--comfy-input-bg);border:2px solid var(--border-color);color:var(--input-text);display:flex;align-items:center;gap:5px;border-radius:5px;padding:2px 5px;cursor:pointer}.easyuse-model-tag--selected span:before{content:"✅";position:absolute;background-color:var(--theme-color-light);left:0;top:0;right:0;bottom:0;text-align:center}.easyuse-model-tag:hover{border:2px solid var(--theme-color-light)}.easyuse-model-tag p{margin:0}.easyuse-model-tag span{text-align:center;border-radius:5px;background-color:var(--theme-color-light);padding:2px;position:relative;min-width:20px;overflow:hidden;color:#fff}.easyuse-model-metadata .comfy-modal-content{max-width:100%}.easyuse-model-metadata label{margin-right:1ch;color:#ccc}.easyuse-model-metadata span{color:var(--theme-color-light)}.easyuse-preview{max-width:660px;margin-right:15px;position:relative}.easyuse-preview-group{position:relative;overflow:hidden;border-radius:.5rem;width:660px}.easyuse-preview-list{display:flex;flex-wrap:nowrap;width:100%;transition:all .5s ease-in-out}.easyuse-preview-list.no-transition{transition:none}.easyuse-preview-slide{display:flex;flex-basis:calc(50% - 5px);flex-grow:0;flex-shrink:0;position:relative;justify-content:center;align-items:center;padding-right:5px;padding-left:0}.easyuse-preview-slide:nth-child(2n){padding-left:5px;padding-right:0}.easyuse-preview-slide-content{position:relative;min-height:150px;width:100%}.easyuse-preview-slide-content .save{position:absolute;right:6px;z-index:12;bottom:6px;display:flex;align-items:center;height:26px;padding:0 9px;color:var(--input-text);font-size:12px;line-height:26px;background:#00000080;border-radius:13px;cursor:pointer;min-width:80px;text-align:center}.easyuse-preview-slide-content .save:hover{filter:brightness(120%);will-change:auto}.easyuse-preview-slide-content img{border-radius:14px;object-position:center center;max-width:100%;max-height:700px;border-style:none;vertical-align:middle}.easyuse-preview button{position:absolute;z-index:10;top:50%;display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:15px;border:1px solid rgba(66,63,78,.15);background-color:#423f4e80;color:#fffc;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transform:translateY(-50%)}.easyuse-preview button.left{left:10px}.easyuse-preview button.right{right:10px}.easyuse-model-detail{margin-top:16px;overflow:hidden;border:1px solid var(--border-color);border-radius:8px;width:300px}.easyuse-model-detail-head{height:40px;padding:0 10px;font-weight:500;font-size:14px;font-style:normal;line-height:40px}.easyuse-model-detail-body{box-sizing:border-box;font-size:12px}.easyuse-model-detail-item{display:flex;justify-content:flex-start;border-top:1px solid var(--border-color)}.easyuse-model-detail-item-label{flex-shrink:0;width:88px;padding-top:5px;padding-bottom:5px;padding-left:10px;border-right:1px solid var(--border-color);color:var(--input-text);font-weight:400}.easyuse-model-detail-item-value{display:flex;flex-wrap:wrap;padding:5px 10px;color:var(--input-text)}.easyuse-model-detail-textarea{border-top:1px solid var(--border-color);padding:10px;height:100px;overflow-y:auto;font-size:12px}.easyuse-model-detail-textarea textarea{width:100%;height:100%;border:0;background-color:transparent;color:var(--input-text)}.easyuse-model-detail-textarea textarea::placeholder{color:var(--descrip-text)}.easyuse-model-detail-textarea.empty{display:flex;justify-content:center;align-items:center;color:var(--descrip-text)}.easyuse-model-notes{background-color:#00000040;padding:5px;margin-top:5px}.easyuse-model-notes:empty{display:none}.easyuse-account-user{font-size:10px;color:var(--descrip-text);text-align:center}.easyuse-account-user-info{display:flex;justify-content:space-between;align-items:center;padding-bottom:10px;cursor:pointer}.easyuse-account-user-info .user{display:flex;align-items:center}.easyuse-account-user-info .edit{padding:5px 10px;background:var(--comfy-menu-bg);border-radius:4px}.easyuse-account-user-info:hover{filter:brightness(110%)}.easyuse-account-user-info h5{margin:0;font-size:10px;text-align:left}.easyuse-account-user-info h6{margin:0;font-size:8px;text-align:left;font-weight:300}.easyuse-account-user-info .remark{margin-top:4px}.easyuse-account-user-info .avatar{width:36px;height:36px;background:var(--comfy-input-bg);border-radius:50%;margin-right:5px;display:flex;justify-content:center;align-items:center;font-size:16px;overflow:hidden}.easyuse-account-user-info .avatar img{width:100%;height:100%}.easyuse-account-dialog{width:600px}.easyuse-account-dialog-main a,.easyuse-account-dialog-main a:visited{font-weight:400;color:var(--theme-color-light)}.easyuse-account-dialog-item{display:flex;justify-content:flex-start;align-items:center;padding:10px 0;border-bottom:1px solid var(--border-color)}.easyuse-account-dialog-item input{padding:5px;margin-right:5px}.easyuse-account-dialog-item input.key{flex:1}.easyuse-account-dialog-item button{cursor:pointer;margin-left:5px!important;padding:5px!important;font-size:16px!important}.easyuse-account-dialog-item button:hover{filter:brightness(120%)}.easyuse-account-dialog-item button.choose{background:var(--theme-color)}.easyuse-account-dialog-item button.delete{background:var(--error-color)}.easy-dropdown,.easy-nested-dropdown{position:relative;box-sizing:border-box;background-color:#171717;box-shadow:0 4px 4px #ffffff40;padding:0;margin:0;list-style:none;z-index:1000;overflow:visible;max-height:fit-content;max-width:fit-content}.easy-dropdown{position:absolute;border-radius:0}.easy-dropdown li.item,.easy-nested-dropdown li.item{font-weight:400;min-width:max-content}.easy-dropdown li.folder,.easy-nested-dropdown li.folder{cursor:default;position:relative;border-right:3px solid cyan}.easy-dropdown li.folder:after,.easy-nested-dropdown li.folder:after{content:">";position:absolute;right:2px;font-weight:400}.easy-dropdown li,.easy-nested-dropdown li{padding:4px 10px;cursor:pointer;font-family:system-ui;font-size:.7rem;position:relative}.easy-nested-dropdown{position:absolute;top:0;left:100%;margin:0;border:none;display:none}.easy-dropdown li.selected>.easy-nested-dropdown,.easy-nested-dropdown li.selected>.easy-nested-dropdown{display:block;border:none}.easy-dropdown li.selected,.easy-nested-dropdown li.selected{background-color:#e5e5e5;border:none}#crystools-root-easyuse{flex-direction:row;justify-content:center;flex-shrink:1;width:min-content;min-width:max-content;height:100%;margin:0 auto}#crystools-root-easyuse .crystools-monitor-container{width:100%;cursor:crosshair;display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-end;gap:5px;height:100%}#crystools-root-easyuse .crystools-monitor-container .crystools-monitor{background-color:var(--comfy-input-bg);position:relative;align-items:center;flex-direction:row;width:40px;height:100%}#crystools-root-easyuse .crystools-monitor-container .crystools-monitor .crystools-text{font-size:10px;text-align:right;margin-left:3px;position:absolute;font-weight:100;bottom:2px;z-index:10}#crystools-root-easyuse .crystools-monitor-container .crystools-content{position:relative;height:100%}#crystools-root-easyuse .crystools-monitor-container .crystools-slider{position:absolute;height:100%;width:0;box-shadow:inset 2px 2px 10px #0003}#crystools-root-easyuse .crystools-monitor-container .crystools-label{position:relative;width:100%;color:var(--input-text);font-weight:500;font-size:11px;left:2px;top:4px;text-align:right}:root{--theme-color:var(--primary-bg);--theme-color-light: var(--primary-hover-bg);--success-color: #52c41a;--error-color: #ff4d4f;--warning-color: #faad14;--font-family: Inter, -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;--p-inputtext-background: var(--p-form-field-background);--p-inputtext-disabled-background: var(--p-form-field-disabled-background);--p-inputtext-filled-background: var(--p-form-field-filled-background);--p-inputtext-filled-focus-background: var(--p-form-field-filled-focus-background);--p-inputtext-border-color: var(--p-form-field-border-color);--p-inputtext-hover-border-color: var(--p-form-field-hover-border-color);--p-inputtext-focus-border-color: var(--p-form-field-focus-border-color);--p-inputtext-invalid-border-color: var(--p-form-field-invalid-border-color);--p-inputtext-color: var(--p-form-field-color);--p-inputtext-disabled-color: var(--p-form-field-disabled-color);--p-inputtext-placeholder-color: var(--p-form-field-placeholder-color);--p-inputtext-shadow: var(--p-form-field-shadow);--p-inputtext-padding-x: var(--p-form-field-padding-x);--p-inputtext-padding-y: var(--p-form-field-padding-y);--p-inputtext-border-radius: var(--p-form-field-border-radius);--p-inputtext-focus-ring-width: var(--p-form-field-focus-ring-width);--p-inputtext-focus-ring-style: var(--p-form-field-focus-ring-style);--p-inputtext-focus-ring-color: var(--p-form-field-focus-ring-color);--p-inputtext-focus-ring-offset: var(--p-form-field-focus-ring-offset);--p-inputtext-focus-ring-shadow: var(--p-form-field-focus-ring-shadow);--p-inputtext-transition-duration: var(--p-form-field-transition-duration);--p-inputtext-sm-font-size: .875rem;--p-inputtext-sm-padding-x: .625rem;--p-inputtext-sm-padding-y: .375rem;--p-inputtext-lg-font-size: 1.125rem;--p-inputtext-lg-padding-x: .875rem;--p-inputtext-lg-padding-y: .625rem;--p-tooltip-max-width: 12.5rem;--p-tooltip-gutter: .25rem;--p-tooltip-shadow: var(--p-overlay-popover-shadow);--p-tooltip-padding: .5rem .75rem;--p-tooltip-border-radius: var(--p-overlay-popover-border-radius);--p-tooltip-background: var(--p-surface-700);--p-tooltip-color: var(--p-surface-0)}.comfyui-easyuse-theme,.comfyui-easyuse-primary{color:var(--theme-color-light)}.comfyui-easyuse-theme.point:hover,.comfyui-easyuse-primary.point:hover{opacity:.8}.comfyui-easyuse-success{color:var(--success-color)}.comfyui-easyuse-success.point:hover{opacity:.8}.comfyui-easyuse-error{color:var(--error-color)}.comfyui-easyuse-error.point:hover{opacity:.8}.comfyui-easyuse-warning,.comfyui-easyuse--warn{color:var(--warning-color)}.comfyui-easyuse-warning.point:hover,.comfyui-easyuse--warn.point:hover{opacity:.8}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.grid{display:grid}.gap-0\\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-10{gap:2.5rem}.gap-12{gap:3rem}.gap-2{gap:.5rem}.gap-2\\.5{gap:.625rem}.gap-20{gap:5rem}.gap-3{gap:.75rem}.gap-3\\.5{gap:.875rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-7{gap:1.75rem}.gap-8{gap:2rem}.gap-\\[0\\.28rem\\]{gap:.28rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.gap-y-6{row-gap:1.5rem}@media (min-width: 576px){.sm\\:col-span-4{grid-column:span 4/span 4}.sm\\:col-span-6{grid-column:span 6/span 6}.sm\\:ml-8{margin-left:2rem}.sm\\:mt-0{margin-top:0}.sm\\:flex{display:flex}.sm\\:h-60{height:15rem}.sm\\:\\!w-64{width:16rem!important}.sm\\:w-40{width:10rem}.sm\\:w-44{width:11rem}.sm\\:w-56{width:14rem}.sm\\:w-60{width:15rem}.sm\\:w-64{width:16rem}.sm\\:w-80{width:20rem}.sm\\:w-96{width:24rem}.sm\\:w-\\[30rem\\]{width:30rem}.sm\\:w-auto{width:auto}.sm\\:min-w-\\[30rem\\]{min-width:30rem}.sm\\:flex-row{flex-direction:row}.sm\\:flex-col{flex-direction:column}.sm\\:flex-nowrap{flex-wrap:nowrap}.sm\\:items-start{align-items:flex-start}.sm\\:items-end{align-items:flex-end}.sm\\:items-center{align-items:center}.sm\\:justify-center{justify-content:center}.sm\\:justify-between{justify-content:space-between}.sm\\:gap-2{gap:.5rem}.sm\\:p-20{padding:5rem}.sm\\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\\:py-10{padding-bottom:2.5rem;padding-top:2.5rem}.sm\\:py-5{padding-bottom:1.25rem;padding-top:1.25rem}.sm\\:pt-32{padding-top:8rem}.sm\\:text-left{text-align:left}.sm\\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\\:-bottom-12{bottom:-3rem}.md\\:-bottom-28{bottom:-7rem}.md\\:-bottom-8{bottom:-2rem}.md\\:-bottom-\\[26rem\\]{bottom:-26rem}.md\\:-left-12{left:-3rem}.md\\:-left-28{left:-7rem}.md\\:-left-32{left:-8rem}.md\\:-left-4{left:-1rem}.md\\:-left-48{left:-12rem}.md\\:-left-\\[22rem\\]{left:-22rem}.md\\:bottom-0{bottom:0}.md\\:left-10{left:2.5rem}.md\\:left-\\[32rem\\]{left:32rem}.md\\:left-\\[42rem\\]{left:42rem}.md\\:top-1\\/2{top:50%}.md\\:top-32{top:8rem}.md\\:top-8{top:2rem}.md\\:col-span-2{grid-column:span 2/span 2}.md\\:col-span-4{grid-column:span 4/span 4}.md\\:col-span-6{grid-column:span 6/span 6}.md\\:ml-auto{margin-left:auto}.md\\:block{display:block}.md\\:flex{display:flex}.md\\:hidden{display:none}.md\\:h-\\[20rem\\]{height:20rem}.md\\:h-\\[32rem\\]{height:32rem}.md\\:\\!w-80{width:20rem!important}.md\\:w-2\\/12{width:16.666667%}.md\\:w-40{width:10rem}.md\\:w-5\\/12{width:41.666667%}.md\\:w-56{width:14rem}.md\\:w-6\\/12{width:50%}.md\\:w-60{width:15rem}.md\\:w-8\\/12{width:66.666667%}.md\\:w-80{width:20rem}.md\\:w-\\[100rem\\]{width:100rem}.md\\:w-\\[26rem\\]{width:26rem}.md\\:w-\\[30rem\\]{width:30rem}.md\\:w-\\[50rem\\]{width:50rem}.md\\:w-\\[52rem\\]{width:52rem}.md\\:w-\\[60rem\\]{width:60rem}.md\\:w-\\[95rem\\]{width:95rem}.md\\:w-screen{width:100vw}.md\\:flex-initial{flex:0 1 auto}.md\\:-translate-y-1\\/2{--tw-translate-y: -50% }.md\\:-translate-y-1\\/2,.md\\:translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\\:translate-x-0{--tw-translate-x: 0px }.md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\\:flex-row{flex-direction:row}.md\\:flex-col{flex-direction:column}.md\\:items-end{align-items:flex-end}.md\\:items-center{align-items:center}.md\\:justify-start{justify-content:flex-start}.md\\:justify-center{justify-content:center}.md\\:gap-20{gap:5rem}.md\\:gap-4{gap:1rem}.md\\:p-5{padding:1.25rem}.md\\:p-8{padding:2rem}}@media (min-width: 992px){.lg\\:left-20{left:5rem}.lg\\:left-\\[36rem\\]{left:36rem}.lg\\:left-\\[50rem\\]{left:50rem}.lg\\:col-span-1{grid-column:span 1/span 1}.lg\\:col-span-2{grid-column:span 2/span 2}.lg\\:col-span-4{grid-column:span 4/span 4}.lg\\:col-span-6{grid-column:span 6/span 6}.lg\\:mb-0{margin-bottom:0}.lg\\:mt-0{margin-top:0}.lg\\:mt-20{margin-top:5rem}.lg\\:flex{display:flex}.lg\\:hidden{display:none}.lg\\:h-10{height:2.5rem}.lg\\:h-32{height:8rem}.lg\\:h-\\[28rem\\]{height:28rem}.lg\\:\\!w-\\[30rem\\]{width:30rem!important}.lg\\:w-3\\/12{width:25%}.lg\\:w-32{width:8rem}.lg\\:w-\\[28rem\\]{width:28rem}.lg\\:w-\\[64rem\\]{width:64rem}.lg\\:w-fit{width:-moz-fit-content;width:fit-content}.lg\\:max-w-6xl{max-width:72rem}.lg\\:flex-row{flex-direction:row}.lg\\:gap-0{gap:0}.lg\\:rounded-2xl{border-radius:1rem}.lg\\:rounded-3xl{border-radius:1.5rem}.lg\\:rounded-xl{border-radius:.75rem}.lg\\:p-7{padding:1.75rem}.lg\\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\\:px-20{padding-left:5rem;padding-right:5rem}.lg\\:px-56{padding-left:14rem;padding-right:14rem}.lg\\:px-8{padding-left:2rem;padding-right:2rem}.lg\\:px-9{padding-left:2.25rem;padding-right:2.25rem}.lg\\:py-20{padding-bottom:5rem;padding-top:5rem}.lg\\:py-7{padding-bottom:1.75rem;padding-top:1.75rem}.lg\\:pt-0{padding-top:0}.lg\\:text-2xl{font-size:1.5rem;line-height:2rem}.lg\\:text-5xl{font-size:3rem;line-height:1}.lg\\:text-base{font-size:1rem;line-height:1.5rem}.lg\\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 1200px){.xl\\:-left-12{left:-3rem}.xl\\:-left-28{left:-7rem}.xl\\:bottom-0{bottom:0}.xl\\:left-36{left:9rem}.xl\\:left-\\[42rem\\]{left:42rem}.xl\\:left-\\[60rem\\]{left:60rem}.xl\\:col-span-3{grid-column:span 3/span 3}.xl\\:col-span-4{grid-column:span 4/span 4}.xl\\:col-span-6{grid-column:span 6/span 6}.xl\\:block{display:block}.xl\\:flex{display:flex}.xl\\:hidden{display:none}.xl\\:h-\\[36\\.25rem\\]{height:36.25rem}.xl\\:\\!w-40{width:10rem!important}.xl\\:w-3\\/12{width:25%}.xl\\:w-6\\/12{width:50%}.xl\\:w-96{width:24rem}.xl\\:w-\\[29rem\\]{width:29rem}.xl\\:max-w-36{max-width:9rem}.xl\\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\\:flex-row{flex-direction:row}.xl\\:items-start{align-items:flex-start}.xl\\:items-center{align-items:center}.xl\\:gap-1{gap:.25rem}.xl\\:gap-6{gap:1.5rem}.xl\\:text-left{text-align:left}}.comfyui-easyuse-toast{position:fixed;z-index:99999;top:0;left:0;height:0;width:100%;display:flex;flex-direction:column;align-items:center;justify-content:start}.comfyui-easyuse-toast-container{position:relative;height:fit-content;padding:4px;margin-top:-100px;opacity:0;z-index:3;-webkit-transition:all .3s ease-in-out;-khtml-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.comfyui-easyuse-toast-container:last-child{z-index:1}.comfyui-easyuse-toast-container.show{opacity:1;margin-top:0!important;transform:translateY(0)}.comfyui-easyuse-toast-container:not(.show){z-index:1}.comfyui-easyuse-toast-container>div{position:relative;background:var(--comfy-menu-bg);color:var(--input-text);height:fit-content;box-shadow:0 0 10px #000000e0;padding:9px 12px;border-radius:var(--border-radius);font-size:14px;pointer-events:all;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-toast-container>div>span{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-toast-container>div>span i{font-size:16px;margin-right:8px}.comfyui-easyuse-toast-container>div>span i.loading{animation:loading-rotate 1s linear infinite}.comfyui-easyuse-toast-container a{cursor:pointer;text-decoration:underline;color:var(--theme-color-light);margin-left:4px;display:inline-block;line-height:1}.comfyui-easyuse-toast-container a:hover{color:var(--theme-color-light);text-decoration:none}@keyframes loading-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.comfyui-easyuse-selector{position:relative}.comfyui-easyuse-selector.hide{display:none}.comfyui-easyuse-selector__header{--height:26px;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;height:var(--height);padding-bottom:10px;border-bottom:1px solid var(--border-color-solid)}.comfyui-easyuse-selector__header_button{height:var(--height);width:var(--height);border-radius:var(--border-radius);border:1px solid var(--border-color);font-size:11px;background:var(--bg-color);box-shadow:none;cursor:pointer;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-selector__header_button:hover{filter:brightness(1.2)}.comfyui-easyuse-selector__header_button:hover i{color:var(--error-color)}.comfyui-easyuse-selector__header_button i{font-size:16px;color:var(--input-text);-webkit-transition:all .3s ease-in-out;-khtml-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.comfyui-easyuse-selector__header_search{flex:1;margin-left:10px;border-radius:var(--border-radius);border:1px solid var(--border-color);font-size:11px;background:var(--bg-color);padding:0 8px;height:var(--height);display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-selector__header_search i{font-size:16px}.comfyui-easyuse-selector__header_search .search{vertical-align:middle;margin-left:5px;outline:none;resize:none;border:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-shadow:none;overflow-y:scroll;background:transparent;width:100%;line-height:var(--height);font-size:12px;color:var(--input-text);-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1}.comfyui-easyuse-selector__content{list-style:none;padding:0;margin:0;min-height:150px;height:calc(100% - 28px);overflow-y:auto;overflow-x:hidden}.comfyui-easyuse-selector-item{display:inline-block;position:relative}.comfyui-easyuse-selector-item__tag{display:inline-block;vertical-align:middle;margin-top:8px;margin-right:8px;padding:4px;color:var(--input-text);background-color:var(--bg-color);border-radius:var(--border-radius);border:1px solid var(--border-color);font-size:11px;cursor:pointer;overflow:hidden;position:relative}.comfyui-easyuse-selector-item__tag:hover{filter:brightness(1.2)}.comfyui-easyuse-selector-item__tag.hide{display:none}.comfyui-easyuse-selector-item__tag input{--ring-color: transparent;position:relative;box-shadow:none;border:1px solid var(--border-color);border-radius:2px;background:linear-gradient(135deg,var(--comfy-menu-bg) 0%,var(--comfy-input-bg) 60%)}.comfyui-easyuse-selector-item__tag input[type=checkbox]{display:inline-block;flex-shrink:0;vertical-align:middle;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:1px solid var(--border-color);background-origin:border-box;padding:0;width:1rem;height:1rem;border-radius:4px;color:var(--theme-color-light);-webkit-user-select:none;user-select:none}.comfyui-easyuse-selector-item__tag input[type=checkbox]:checked{border:1px solid var(--theme-color-light);background-color:var(--theme-color-light);background-image:url("data:image/svg+xml,%3csvg viewBox=\'0 0 16 16\' fill=\'white\' xmlns=\'http://www.w3.org/2000/svg\'%3e%3cpath d=\'M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z\'/%3e%3c/svg%3e")}.comfyui-easyuse-selector-item__tag input span{margin:0 4px;vertical-align:middle}.comfyui-easyuse-selector-preview{position:absolute;left:-180px;top:-110px;z-index:2;border:1px solid var(--border-color);border-radius:var(--border-radius);background:var(--comfy-menu-bg);-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px);overflow:hidden}.comfyui-easyuse-selector-preview img{width:150px;height:150px}.comfyui-easyuse-selector-preview__text{font-size:12px;padding:5px 10px;max-width:130px;color:var(--input-text)}.comfyui-easyuse-selector-preview__text h6{line-height:1.15;font-size:10px;margin:10px 0}.comfyui-easyuse-dialog{max-width:600px}.comfyui-easyuse-dialog-title{font-size:18px;font-weight:700;text-align:center;color:var(--input-text);margin:0}.comfyui-easyuse-dialog-images{margin-top:10px;display:flex;flex-wrap:wrap;width:100%;box-sizing:border-box}.comfyui-easyuse-dialog-images img{width:50%;height:auto;cursor:pointer;box-sizing:border-box;filter:brightness(80%)}.comfyui-easyuse-dialog-images img:hover{filter:brightness(100%)}.comfyui-easyuse-dialog-images.selected{border:4px solid var(--success-color)}.comfyui-easyuse-dialog-hidden{display:none;height:0}.comfyui-easyuse-contextmenu{--height: 26px;--padding: 8px;font-family:var(--font-family);position:fixed;top:0;left:0;width:100%;max-width:200px;min-width:100px;min-height:100px;padding:var(--padding) 0;box-shadow:0 0 10px #00000040;background-color:var(--tr-odd-bg-color);border-radius:var(--border-radius);z-index:10;will-change:transform}.comfyui-easyuse-contextmenu-item-divider{height:1px;width:100%;background-color:var(--border-color);margin:var(--padding) 0}.comfyui-easyuse-contextmenu-item-content{height:var(--height);padding:0 12px;cursor:pointer;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-contextmenu-item-content span{font-size:11px;color:var(--input-text);display:-webkit-box;-webkit-line-clamp:1;overflow:hidden;word-break:break-all;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1}.comfyui-easyuse-contextmenu-item-content i{color:var(--input-text);margin-left:4px;display:block;width:0;height:0;border-width:4px 4px 0;border-style:solid;border-color:var(--descrip-text) transparent transparent;-webkit-transform:scaleY(.8) rotate(-90deg);-khtml-transform:scaleY(.8) rotate(-90deg);-moz-transform:scaleY(.8) rotate(-90deg);-ms-transform:scaleY(.8) rotate(-90deg);-o-transform:scaleY(.8) rotate(-90deg);transform:scaleY(.8) rotate(-90deg)}.comfyui-easyuse-contextmenu-item-content:hover{background:var(--theme-color)}.comfyui-easyuse-contextmenu-item.disabled .comfyui-easyuse-contextmenu-item-content span{color:var(--border-color);cursor:default}.comfyui-easyuse-contextmenu-item.disabled .comfyui-easyuse-contextmenu-item-content:hover{background:transparent}.comfyui-easyuse-contextmenu-submenu{font-family:var(--font-family);position:absolute;top:0;left:200px;max-width:200px;width:200px;min-width:100px;min-height:--height;padding:var(--padding) 0;box-shadow:0 0 10px #00000040;background-color:var(--tr-odd-bg-color);border-radius:var(--border-radius);z-index:10;will-change:transform}.comfyui-easyuse-contextmenu-model{position:relative}.comfyui-easyuse-contextmenu-model:hover img{display:block;opacity:1}.comfyui-easyuse-contextmenu-model img{position:absolute;z-index:1;right:-175px;top:-75px;width:150px;height:auto;display:none;filter:brightness(70%);-webkit-filter:brightness(70%);opacity:0;-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);-khtml-transition:all .5s cubic-bezier(.55,0,.1,1);-moz-transition:all .5s cubic-bezier(.55,0,.1,1);-ms-transition:all .5s cubic-bezier(.55,0,.1,1);-o-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1)}.comfyui-easyuse-slider{width:100%;height:100%;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-slider-item{height:inherit;min-width:25px;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;-webkit-flex-direction:column;-khtml-flex-direction:column;-moz-flex-direction:column;-ms-flex-direction:column;-o-flex-direction:column;flex-direction:column}.comfyui-easyuse-slider-item-input{height:15px;font-size:10px;color:var(--input-text)}.comfyui-easyuse-slider-item-label{height:15px;border:none;color:var(--descrip-text);font-size:8px}.comfyui-easyuse-slider-item-scroll{width:5px;height:calc(100% - 30px);background:var(--comfy-input-bg);border-radius:10px;position:relative}.comfyui-easyuse-slider-item-bar{width:10px;height:10px;background:linear-gradient(to bottom,var(--input-text),var(--descrip-text));border-radius:100%;box-shadow:0 2px 10px var(--bg-color);position:absolute;top:0;left:-2.5px;cursor:pointer;z-index:1}.comfyui-easyuse-slider-item-area{width:100%;border-radius:20px;position:absolute;bottom:0;background:var(--input-text);z-index:0}.comfyui-easyuse-slider-item.positive .comfyui-easyuse-slider-item-label{color:var(--success-color)}.comfyui-easyuse-slider-item.positive .comfyui-easyuse-slider-item-area{background:var(--success-color)}.comfyui-easyuse-slider-item.negative .comfyui-easyuse-slider-item-label{color:var(--error-color)}.comfyui-easyuse-slider-item.negative .comfyui-easyuse-slider-item-area{background:var(--error-color)}.comfyui-easyuse-map{height:100%;background:var(--comfy-menu-bg)}.comfyui-easyuse-map .p-splitter-gutter-handle{height:1px!important}.comfyui-easyuse-map-nodes{height:100%;position:relative}.comfyui-easyuse-map-nodes__header{position:absolute;z-index:2;top:0;left:0;width:100%;padding:.25rem 0 .25rem 1rem;height:2.7rem;background:var(--comfy-menu-bg);border-bottom:1px solid var(--border-color);display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px)}.comfyui-easyuse-map-nodes__header .title{font-size:13px;color:var(--input-text);font-weight:400;line-height:1.5;-webkit-user-select:none;user-select:none}.comfyui-easyuse-map-nodes__header .toolbar{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-map-nodes__header .toolbar .icon{font-size:.85rem;margin-left:.25rem;cursor:pointer}.comfyui-easyuse-map-nodes__content{position:relative;padding:2.7rem 0 0;height:100%;overflow:auto}.comfyui-easyuse-map-nodes__content dl .label{padding-left:1rem}.comfyui-easyuse-map-nodes__content ol,.comfyui-easyuse-map-nodes__content dl{list-style-type:none;padding:0;margin:0}.comfyui-easyuse-map-nodes__content ol .toolbar span,.comfyui-easyuse-map-nodes__content dl .toolbar span{font-size:13px}.comfyui-easyuse-map-nodes__content ol .toolbar span.pi-eye,.comfyui-easyuse-map-nodes__content dl .toolbar span.pi-eye{color:var(--input-text)}.comfyui-easyuse-map-nodes__content ol .toolbar span.pi-eye-slash,.comfyui-easyuse-map-nodes__content dl .toolbar span.pi-eye-slash{color:var(--descrip-text)}.comfyui-easyuse-map-nodes__content ol .toolbar span.pi-eye-slash.never,.comfyui-easyuse-map-nodes__content dl .toolbar span.pi-eye-slash.never{opacity:.5}.comfyui-easyuse-map-nodes__content .no_result{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;text-align:center}.comfyui-easyuse-map-nodes-group{position:relative;overflow:hidden;width:100%;height:2rem;cursor:default;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;pointer-events:auto}.comfyui-easyuse-map-nodes-group .left,.comfyui-easyuse-map-nodes-group .right{height:100%;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-map-nodes-group .left{padding-left:.5rem;margin-right:.25rem;-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1}.comfyui-easyuse-map-nodes-group .icon{font-size:.85rem;margin-right:.25rem}.comfyui-easyuse-map-nodes-group .label{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;height:100%;width:100%;min-width:80px}.comfyui-easyuse-map-nodes-group .label span{font-size:14px;color:var(--input-text);font-weight:400;line-height:1.5;display:-webkit-box;-webkit-line-clamp:1;overflow:hidden;word-break:break-all;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.comfyui-easyuse-map-nodes-group:hover{background:var(--content-hover-bg)!important}.comfyui-easyuse-map-nodes-group.active{background:var(--theme-color)!important}.comfyui-easyuse-map-nodes-group.active .label{color:#fff;cursor:default}.comfyui-easyuse-map-nodes-group.never .label{color:var(--descrip-text);opacity:.4}.comfyui-easyuse-map-nodes-group.bypass .label{color:var(--descrip-text)}.comfyui-easyuse-map-nodes-node{height:2rem;cursor:default;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;position:relative;overflow:hidden;pointer-events:auto}.comfyui-easyuse-map-nodes-node .label{text-indent:.5rem;font-size:13px;color:var(--input-text);font-weight:400;line-height:1.5;-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;margin-right:.25rem;height:2rem;line-height:2rem;width:100%;display:-webkit-box;-webkit-line-clamp:1;overflow:hidden;word-break:break-all;text-overflow:ellipsis;-webkit-box-orient:vertical}.comfyui-easyuse-map-nodes-node .label.error{color:var(--error-color)}.comfyui-easyuse-map-nodes-node:hover{background:var(--content-hover-bg)!important}.comfyui-easyuse-map-nodes-node.never .label{color:var(--descrip-text);opacity:.5}.comfyui-easyuse-map-nodes-node.bypass .label{color:#f0f;opacity:.5}.comfyui-easyuse-map-nodes .nodes .label{text-indent:1rem}.comfyui-easyuse-toolbar{border-radius:0 12px 12px 0;min-width:50px;height:24px;position:fixed;bottom:85px;left:0;display:flex;align-items:center;z-index:1000;background-color:var(--comfy-menu-bg);border:1px solid var(--bg-color);-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px)}.comfyui-easyuse-toolbar-icon{height:100%;padding:0 4px;display:flex;justify-content:center;align-items:center;font-size:12px;color:var(--input-text);transition:all .3s ease-in-out;cursor:pointer}.comfyui-easyuse-toolbar-icon svg{width:14px;height:14px}.comfyui-easyuse-toolbar-icon:hover.group{color:var(--warning-color)}.comfyui-easyuse-toolbar-icon:hover.rocket{color:var(--theme-color)}.comfyui-easyuse-toolbar-nodes-map{position:absolute;top:50px;left:10px;width:200px;border-radius:12px;min-height:100px;max-height:600px;color:var(--descrip-text);background-color:var(--comfy-menu-bg);border:1px solid var(--bg-color);-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px);z-index:399;padding-top:0;overflow:hidden}.comfyui-easyuse-toolbar-nodes-map .no-result-placeholder-content{-webkit-transform:scale(.8);-khtml-transform:scale(.8);-moz-transform:scale(.8);-ms-transform:scale(.8);-o-transform:scale(.8);transform:scale(.8)}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes{min-height:100px;max-height:600px}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes__header:before{content:"…";position:absolute;left:.25rem;top:2.75rem;transform:translateY(-2rem) rotate(90deg);width:.5rem;height:.5rem;display:inline-block;overflow:hidden;line-height:5px;padding:3px 4px;cursor:move;vertical-align:middle;font-size:12px;font-family:sans-serif;letter-spacing:2px;color:var(--drag-text);z-index:3;text-shadow:1px 0 1px black}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes__header .title{cursor:move;padding-left:.25rem}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes__content{max-height:calc(600px - 2.7rem)}.no-result-placeholder{display:flex;justify-content:center;align-items:center;height:100%}.no-result-placeholder-content{text-align:center;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-flex-direction:column;-khtml-flex-direction:column;-moz-flex-direction:column;-ms-flex-direction:column;-o-flex-direction:column;flex-direction:column;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between}.no-result-placeholder .p-card{background-color:transparent!important;box-shadow:none;text-align:center}.no-result-placeholder h3{color:var(--input-text);margin-bottom:.5rem}.no-result-placeholder p{color:var(--descrip-text);margin-bottom:1rem;margin-top:0}#comfyui-easyuse-components{position:absolute;top:0;left:0;z-index:3}.comfyui-easyuse{--p-datatable-header-cell-padding: .15rem 1rem;--p-datatable-body-cell-padding: .15rem 1rem;--p-primary-color: var(--theme-color-light)!important;--border-color-solid: var(--border-color);--border-radius: 8px}.comfyui-easyuse.dark-theme{--fg-color: #fff;--bg-color: #242427;--content-bg:#18181b;--content-fg:#fff;--content-hover-bg: #27272a;--comfy-menu-bg: rgba(24,24,27,.9);--comfy-input-bg: #242427;--input-text: #ffffff;--descrip-text: #71717a;--drag-text: #ccc;--error-text: #ff4444;--border-color: #3f3f46;--border-color-solid: #2a2a2e;--tr-even-bg-color: rgba(28,28,28,.9);--tr-odd-bg-color: rgba(19,19,19,.9)}.comfyui-easyuse.dark-theme #graph-canvas{background:#000}.comfyui-easyuse ::-webkit-scrollbar{width:0em}.comfyui-easyuse ::-webkit-scrollbar-track{background-color:transparent}.comfyui-easyuse ::-webkit-scrollbar-thumb{background-color:transparent;border-radius:2px}.comfyui-easyuse ::-webkit-scrollbar-thumb:hover{background-color:transparent}.comfyui-easyuse body{font-family:var(--font-family)!important;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.comfyui-easyuse textarea{font-family:var(--font-family)!important}.comfyui-easyuse hr{border:1px solid var(--border-color)}.comfyui-easyuse .comfy-multiline-input{background-color:transparent;border:1px solid var(--border-color-solid);border-radius:var(--border-radius);padding:8px;font-size:12px}.comfyui-easyuse #comfy-settings-dialog{border:1px solid var(--border-color);background:transparent;-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%);box-shadow:none}.comfyui-easyuse .comfy-modal{border:1px solid var(--border-color);box-shadow:none;-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .cm-title{background-color:transparent!important}.comfyui-easyuse .cm-notice-board{border-radius:10px!important;border:1px solid var(--border-color)!important}.comfyui-easyuse .cm-menu-container{margin-bottom:50px!important}.comfyui-easyuse .cn-manager-custom_milk_white .tg-column-name,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-column-name{color:var(--input-text)}.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-message,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-message{text-align:center;color:var(--descrip-text)!important}.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-frame .tg-cell,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-frame .tg-cell{color:var(--input-text)}.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-frame .cn-node-name a,.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-frame .cmm-node-name a,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-frame .cn-node-name a,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-frame .cmm-node-name a{color:var(--theme-color)!important}.comfyui-easyuse .comfy-menu{border-radius:16px;box-shadow:0 0 1px var(--descrip-text);-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .comfy-menu button.comfy-settings-btn{font-size:12px}.comfyui-easyuse .comfy-menu-btns{margin-bottom:4px}.comfyui-easyuse .comfy-menu button,.comfyui-easyuse .comfy-modal button{font-size:14px;padding:4px 0;margin-bottom:4px}.comfyui-easyuse .comfy-menu-btns button,.comfyui-easyuse .comfy-list-actions button{font-size:10px}.comfyui-easyuse .comfy-menu>button,.comfyui-easyuse .comfy-menu-btns button,.comfyui-easyuse .comfy-menu .comfy-list button,.comfyui-easyuse .comfy-modal button{border-width:1px}.comfyui-easyuse #comfy-dev-save-api-button{justify-content:center}.comfyui-easyuse #queue-button{position:relative;overflow:hidden;min-height:30px;z-index:1}.comfyui-easyuse #queue-button:after{clear:both;content:attr(data-attr);background:green;color:#fff;width:var(--process-bar-width);height:100%;position:absolute;top:0;left:0;z-index:0;text-align:center;display:flex;justify-content:center;align-items:center}.comfyui-easyuse #shareButton{background:linear-gradient(to left,var(--theme-color),var(--theme-color-light))!important;color:#fff!important}.comfyui-easyuse .litegraph.litecontextmenu{--height: 24px;--padding: 6px;font-family:var(--font-family);padding:var(--padding) 0;border-radius:var(--border-radius);-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .litegraph.litecontextmenu .litemenu-title{padding:var(--padding)}.comfyui-easyuse .litegraph.litecontextmenu>div:first-child.litemenu-title{margin-top:-6px}.comfyui-easyuse .litegraph.litecontextmenu .submenu{height:var(--height);line-height:var(--height);padding:0 18px 0 12px;margin:0;background:transparent!important}.comfyui-easyuse .litegraph.litecontextmenu .submenu.has_submenu{border-right:none;position:relative}.comfyui-easyuse .litegraph.litecontextmenu .submenu.has_submenu:after{content:"";display:block;width:0;height:0;border-width:4px 4px 0;border-style:solid;border-color:var(--input-text) transparent transparent;transform:translateY(-50%) rotate(-90deg);top:50%;position:absolute}.comfyui-easyuse .litegraph.litecontextmenu .submenu.separator{height:1px;width:100%;background-color:var(--border-color)!important;margin:var(--padding) 0;border:none}.comfyui-easyuse .litegraph.litecontextmenu .submenu:last-child.separator{display:none}.comfyui-easyuse .litegraph.litecontextmenu .submenu:hover:not(.separator){background:var(--theme-color)!important}.comfyui-easyuse .litegraph.lite-search-item{background-color:var(--comfy-input-bg)!important;filter:brightness(100%)}.comfyui-easyuse .litegraph.lite-search-item:hover{filter:brightness(120%);color:var(--input-text)}.comfyui-easyuse .graphdialog{-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .graphdialog button{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse .comfyui-menu{border-bottom:1px solid var(--bg-color)}.comfyui-easyuse .side-tool-bar-container{border-right:1px solid var(--bg-color)}.comfyui-easyuse .comfy-modal-content{width:100%}.comfyui-easyuse-poseEditor{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;text-align:center;font-size:18px;line-height:1.5}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}}(); -var e;import{$ as s,l as t,a as l,t as a,b as o,s as n,g as i,c as r,u,N as d,j as c,d as p,e as v,f as m,h as g}from"./assets/extensions-CrUNTPqr.js";import{r as y,w as h,e as f,b as A,c as w,I as S,d as x,F as E,C as M,J as b,K as C,L as _,M as k,z as B,G as I,o as N,N as H,D as Q,O as z,E as j,P as R,x as D,Q as L,R as Z,S as Y}from"./assets/vue-DjzFgvDF.js";import{d as O,s as V,a as P,b as G,c as W,T as U,e as F}from"./assets/vendor-DT1J-jWa.js";import{c as T}from"./assets/lodash-CZi7izHi.js";import{P as X}from"./assets/primevue-BSs2m5Wu.js";import"./assets/primeuix-Be3xdh47.js";const J=O("graphStore",{state:e=>({selectors:[],selectors_styles:{},seg_selectors:[],slider_controls:[]}),actions:{setSelectors(e){this.selectors=T(e)},setStyles(e,s){this.selectors_styles[e]||(this.selectors_styles[e]=s)},setSegSelectors(e){this.seg_selectors=T(e)},setSliderControls(e){this.slider_controls=T(e)}}}),q=["data-id"],K=[x("i",{class:"mdi mdi-trash-can"},null,-1)],$=x("i",{class:"mdi mdi-magnify"},null,-1),ee=["placeholder"],se=["onMouseenter","onMouseleave"],te=["onClick"],le=["name","checked"],ae=["src"],oe={key:0},ne=x("span",{class:"comfyui-easyuse-success"},"positive:",-1),ie={key:1},re=x("span",{class:"comfyui-easyuse-error"},"negative:",-1),ue="comfyui-easyuse-selector",de={__name:"stylesSelector",props:{id:{type:String|Number,default:""},type:{type:String,default:""},selectedStyles:{type:Array,default:[]},show:{type:Boolean,default:!1}},emits:["chooseStyle"],setup(e,{emit:o}){const n=e,i=J(),{selectors_styles:r}=V(i),u=y([]);h((e=>n.type),(async e=>{u.value=[],e&&await(async e=>{if(r.value[n.type])return!0;const t=await l.fetchApi(`/easyuse/prompt/styles?name=${e}`);if(200===t.status){let e=(await t.json()).map(((e,s)=>(e.index=s,e)));return await i.setStyles(n.type,e),!0}return a.error(s("Get styles list Failed")),!1})(e)&&c()}),{immediate:!0});const d=o,c=e=>{const s=n.selectedStyles,t=T(r.value[n.type]);u.value=t.sort(((e,s)=>e.index-s.index)).sort(((e,t)=>s.includes(t.name)-s.includes(e.name)))},p=y(""),v=e=>e.toLowerCase(),m=f({}),g=e=>{m.src="",m.name="",m.positive="",m.negative=""},N=async e=>{const s=await l.fetchApi(`/easyuse/prompt/styles/image?name=${e}&styles_name=${n.type}`);if(200===s.status){const t=await s.text();if(t.startsWith("http"))return t;return`/easyuse/prompt/styles/image?name=${e}&styles_name=${n.type}`}},H=e=>{e.target.src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QNLaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA5LjEtYzAwMSA3OS4xNDYyODk5Nzc3LCAyMDIzLzA2LzI1LTIzOjU3OjE0ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoMjAyMzA5MDUubS4yMzE2IDk3OWM4NmQpICAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjA3NEU1QzNCNUJBMTFFRUExMUVDNkZDRjI0NzlBN0QiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjA3NEU1QzRCNUJBMTFFRUExMUVDNkZDRjI0NzlBN0QiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGMDc0RTVDMUI1QkExMUVFQTExRUM2RkNGMjQ3OUE3RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGMDc0RTVDMkI1QkExMUVFQTExRUM2RkNGMjQ3OUE3RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEAAYEBAQFBAYFBQYJBgUGCQsIBgYICwwKCgsKCgwQDAwMDAwMEAwODxAPDgwTExQUExMcGxsbHB8fHx8fHx8fHx8BBwcHDQwNGBAQGBoVERUaHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH//AABEIAIAAgAMBEQACEQEDEQH/xACLAAEAAgMBAQEAAAAAAAAAAAAABAUCAwYBBwgBAQADAQEBAAAAAAAAAAAAAAABAgMEBQYQAAEEAgECAwUHAwUAAAAAAAEAAgMEEQUhEgYxEwdBYSIyFFFxgVJyIxWRoTOxwdFiJBEBAAICAQQBBAIDAAAAAAAAAAECEQMxIUESBBOB0SIyUXGCIwX/2gAMAwEAAhEDEQA/AP1SgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICDXJYgj+d4afsVopM8KWvEcy8it1pXdMcjXO/Lnn+im2u0cwV2VniW1UXEBAQEBAQEBAQEBAQRNlc+mgyDh7zhv+5WunX5Sw37fCHM2dh48r06ank7N6rn2Ja7qa4hw5BBwQV010uK+/DsO29v/J68SOI86Jxjl95HIP4gryPc0fHfHaXu+j7Py68zzHSVquV2iAgICAgICAgICDyTr6HdHz4PTnwypjnqic46OauNbY6mGX99p+L8w9xaeV6OufHt0eXtr59M9VFb194E9LmuH3kf6rv17avO2ets7YVcuuuk/uOa3PgBlxP4BdMbq9nLPqbJ5xDbSM9azFXpyujuSO+Bo5kcf0NPyj25We2YtEzaPxdfr6519Kz+UvqEIlELBKQZQ0eYRwC7HOPxXzVsZ6cPpK5x15ZKEiAgICAgICAgICCNc1tG40CzA2XHg4j4h9zhyFpr22p+s4Z7NNL/ALRlTX+1dVFBJOJrcTI2lxZHYcBx+sldWv3bzOMVn6fZy39OkRnNo+v3aoOx9JOxks8tqwHDPS+1IW8+IzGWZVrf9DZHSMR/j9yvo656zMz9V1rdLqdYwsoVIqwd87mNAc79Tvmd+JXJt332ftMy6temlP1jCasmggICAgICAgICAgwlmiib1SPDB7zhWrWZ4VtaI5QXb2l5ojYHvLjjIGB/dbR61sZlhPtVziFb3PYdd0luCvAZbXludVZ1huZQPgyTx4/atvWj4rxaZ6d/6Ye1/t1zSI6zx/bzti5YqaOpBeg8u41n/oa14cA4ccH7lPs1jZebVn8eyPUtOrXFbR+XdYx9xa90pjeXROaSCXDj+oysZ9S+Mx1bR7uvOJ6LGOWKVgfG8PafAtOQueazHLqraJjMMlCRAQEBAQEBAQRLNp4HTFx/2/4WtKR3Y32T2Udl8j3knk/aeSu6kREPPvaZlpY3DmyY8DyrzPZWv8tkvmFv7bg12RyR1DGeeMj2KnjE9JaeUx1hi1sgaet/U7JIOMcE8Dj7FMREcK2zPKMasr5XO6fmOVt5xEOadVplYU45IAOhxa72kLm2TFuXXqrNeF1WtlwDZeHfmHguO+vHDupszylLJsICAgICAg8cMjCQiYR5IVpFmc1Q5qLXHPgfbhbV2MLaYlqNQAYA4V/kV+PDA1fcp81fjYurtYMu4CmLZRNYhtZWBAI8CqzdaKN8df3LObtIokxwe5ZzZrFUloIGFnLWHqhIgICAgICAgxMbSpyjDAwAq3kr4MTWCnzR4MX02PGHDISNmETqieWba7QABwB4KJumKNgjaFXK0VZYChYQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEHzvuv1G7k1W9s6/Xamtaq15oaonmnsCR008HntaI4K8/s4HOeEGXZXqTud7uqtG7r6kNa5HdMU9aaw9zZde+FkrHsnr1+M2MZBPIKDRe9cO2K2mjs/V0m7X61lWzq32W+ZFEbfkSSO4B+GL9zw4QWm99TqFVmjsaSu7fUtxeNM2aTmSMBbHI9zWHqHVJlnDTxjPKCJL6sea502t1D7Ouhr0rNqxNM2CSNuwnkgjAi6ZOotdEc/Egibf1j/j+7JNL9DWdWg84TWn2ywtdFKyMZb5Tg0nLyG55x48IJ3bXqe/ea/a26dFtyTXtldDUqyOdNL5VqaDHS5gwXRxMe3xz1Y9iDKP1Sa7uefUnR7TyYqUVoEU5jY6pJZIz1RY4ZiMYd7TkexBA749Wr2gtCKlrIpGs17NjK29LLWmPmMsyiFkbIZsPEdKQu6y0eAQWdD1E2L93W1tzRyCDY3paev2NaxVlhIjidMfMb5vmse1kbi9pZ7MeKDt0BAQEBAQfEPU+lFY2++q2K1uSSezTnrReVsTTmiZVYHOd9LVuQyubIwANkbxz4FA7FsQ0NrrLNXX7N0eo1+3darGDYPjb5j6prxVRajjDetsRAjj4yM4CDre2uxO7q2hqtm7nua6w9rp5tfXgoSxwyTOMr42PlrPe4Nc8jJJQRDb3Oz1fYFrcV7As0mu3u7nbWkBZ9LSfG5nlxs/yySWRiNozwcBBx9EXadGTXz62+LG41+jZS6adhzS6vfnlkEjgzEZax7T8ePFBu3nbPdUXqJZsw6S5cqbCW1YdIY2lxhhfEGMjfHtoG9HxucwPEZy4/A7kMC87aq2Kmv7mdvxuqGmklFjUU4G2Yp21rdyW00t+kJkFl88pY9vDgwNDvEoK9np73FBcHdkrt2+rZd5FjQx7O0b8WvbzDKZhN1SSse573QdeAHkN+Ichj3p2rBvZq9vUnY2tcNQPqpZYZpJ44GxXqzHdVlzZZpib73mLHViI85c1BZ6OpsIe/6/XSuntevdsz6+8+pI0/yM1dtWVr2Z644P8rmyuj6S53jxkh9aQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBB/9k="};return(l,a)=>(A(),w("div",{class:E(ue+` ${ue}-styles`),"data-id":e.id,onMouseleave:g},[u.value.length>0&&e.show?(A(),w(S,{key:0},[x("div",{class:E(ue+"__header")},[x("div",{class:E(ue+"__header_button"),onClick:a[0]||(a[0]=e=>(d("chooseStyle",[]),void(p.value="")))},K,2),x("div",{class:E(ue+"__header_search")},[$,M(x("textarea",{class:"search","onUpdate:modelValue":a[1]||(a[1]=e=>p.value=e),dir:"ltr",rows:1,placeholder:C(s)("Type here to search styles ...")},null,8,ee),[[b,p.value]])],2)],2),x("div",{class:E(ue+"__content"),onMouseleave:c},[(A(!0),w(S,null,_(u.value,((s,l)=>(A(),w("div",{class:E(ue+"-item"),key:l,onMouseenter:e=>(async e=>{if(!e.imageSrc){if(e.imageLoading)return;e.imageLoading=!0;const s=await N(e.imgName).finally((()=>e.imageLoading=!1));e.imageSrc=s}m.name="zh-CN"==t&&e.name_cn?e.name_cn:e.name,m.positive=e.prompt,m.negative=e.negative_prompt,m.src=e.imageSrc})(s),onMouseleave:k((e=>g()),["stop"])},[x("span",{class:E([ue+"-item__tag",{hide:!(e.selectedStyles.includes(s.name)||-1!=v(s.name).indexOf(v(p.value))||s.name_cn&&-1!=v(s.name_cn).indexOf(v(p.value)))}]),onClick:e=>(e=>{let s=n.selectedStyles;s.includes(e.name)?s=s.filter((s=>s!==e.name)):s.push(e.name),d("chooseStyle",s)})(s)},[x("input",{type:"checkbox",name:s.name,checked:e.selectedStyles.includes(s.name)},null,8,le),x("span",null,B("zh-CN"==C(t)&&s.name_cn?s.name_cn:s.name),1)],10,te)],42,se)))),128))],34),(null==m?void 0:m.src)?(A(),w("div",{key:0,class:E(ue+"-preview")},[x("img",{src:m.src,ref:"image",alt:"preview",onError:H},null,40,ae),x("div",{class:E(ue+"-preview__text")},[x("b",null,B(m.name),1),x("div",{class:E(ue+"-preview__prompt")},[m.positive?(A(),w("h6",oe,[ne,x("span",null,B(m.positive),1)])):I("",!0),m.negative?(A(),w("h6",ie,[re,x("span",null,B(m.negative),1)])):I("",!0)],2)],2)],2)):I("",!0)],64)):I("",!0)],42,q))}},ce=["data-id"],pe=["onClick"],ve=["name","checked"],me="comfyui-easyuse-selector",ge={__name:"segSelector",props:{id:{type:String|Number,default:""},type:{type:String,default:""},selected:{type:Array,default:[]},show:{type:Boolean,default:!1}},emits:["select"],setup(e,{emit:t}){const l=e,a=y([]);h((e=>l.type),(async e=>{switch(e){case"selfie_multiclass_256x256":a.value=["Background","Hair","Body","Face","Clothes","Others"];break;case"human_parsing_lip":a.value=["Background","Hat","Hair","Glove","Sunglasses","Upper-clothes","Dress","Coat","Socks","Pants","Jumpsuits","Scarf","Skirt","Face","Left-arm","Right-arm","Left-leg","Right-leg","Left-shoe","Right-shoe"];break;case"human_parts (deeplabv3p)":a.value=["Background","Face","Hair","Glasses","Top-clothes","Bottom-clothes","Torso-skin","Left-arm","Right-arm","Left-leg","Right-leg","Left-foot","Right-foot"]}}),{immediate:!0});const o=t;return(t,n)=>{var i;return A(),w("div",{class:E(me+` ${me}-seg`),"data-id":e.id},[(null==(i=a.value)?void 0:i.length)>0&&e.show?(A(!0),w(S,{key:0},_(a.value,((t,a)=>(A(),w("div",{class:E(me+"-item"),key:a},[x("span",{class:E(me+"-item__tag"),onClick:e=>(e=>{let s=T(l.selected);s.includes(e)?s=s.filter((s=>s!==e)):s.push(e),o("select",s)})(a)},[x("input",{type:"checkbox",name:t,checked:e.selected.includes(a)},null,8,ve),x("span",null,B(C(s)(t)),1)],10,pe)],2)))),128)):I("",!0)],10,ce)}}},ye=["data-id"],he=["onMousedown","onDblclick"],fe="comfyui-easyuse-slider",Ae="ipadapter layer weights",we={__name:"sliderControl",props:{id:{type:String|Number,default:""},mode:{type:String,default:""},type:{type:String,default:""},values:{type:Array,default:[]},show:{type:Boolean,default:!1}},emits:["changeValues","showSlider"],setup(e,{emit:s}){const t=e,l=s,a=(e,s,t)=>(e-s)/(t-s)*100,o=(e,s,l=void 0)=>{if(t.mode===Ae){let t={3:2.5,6:1}[s]||0;return{default:12==e?t:0,min:-1,max:3,step:.05,value:void 0!==l?l:12==e?t:0,top:void 0!==l?100-a(l,-1,3)+"%":null,height:void 0!==l?a(l,-1,3)+"%":null}}};h((e=>t.mode),(async(e,s)=>{var a;if(e!==s&&e===Ae)if(!s&&(null==(a=t.values)?void 0:a.length)>0){const e=t.values.map((e=>{const s=e.split(":");return o(t.values.length,s[0],parseFloat(s[1]))}));await l("changeValues",e)}else{let e="sd1"==t.type?16:12,s=Array.from({length:e},((s,t)=>o(e,t)));await l("changeValues",s)}l("showSlider")}),{immediate:!0}),h((e=>t.type),((e,s)=>{if(e!=s&&t.mode==Ae){let e="sd1"==t.type?16:12,s=Array.from({length:e},((s,t)=>o(e,t)));l("changeValues",s)}}));const n=y(null),i=y(null);return N((()=>{document.onmouseup=e=>document.onmousemove=null})),(s,o)=>{var r;return A(),w("div",{class:E(fe),"data-id":e.id},[(null==(r=e.values)?void 0:r.length)>0&&e.show?(A(!0),w(S,{key:0},_(e.values,((s,o)=>(A(),w("div",{class:E([fe+"-item",{positive:3==o&&"sdxl"==e.type&&e.mode==Ae},{negative:6==o&&"sdxl"==e.type&&e.mode==Ae}]),key:o},[x("div",{class:E(fe+"-item-input")},B(s.value),3),x("div",{class:E(fe+"-item-scroll"),ref_for:!0,ref_key:"scroll",ref:n},[x("div",{class:E(fe+"-item-bar"),ref_for:!0,ref_key:"bar",ref:i,style:H({top:s.top||100-a(s.default,s.min,s.max)+"%"}),onMousedown:e=>((e,s,a)=>{let o=e||window.event,r=n.value[a],u=i.value[a],d=T(t.values),c=o.clientY-u.offsetTop;document.onmousemove=e=>{let t=(e||window.event).clientY-c;t<0?t=0:t>r.offsetHeight-u.offsetHeight&&(t=r.offsetHeight-u.offsetHeight);let o=(s.max-s.min)/s.step,n=(r.offsetHeight-u.offsetHeight)/o;t=Math.round(t/n)*n;const i=Math.floor(t/(r.offsetHeight-u.offsetHeight)*100)+"%",p=Math.floor((r.offsetHeight-u.offsetHeight-t)/(r.offsetHeight-u.offsetHeight)*100)+"%",v=parseFloat(parseFloat(s.max-(s.max-s.min)*(t/(r.offsetHeight-u.offsetHeight))).toFixed(2));d[a]={...d[a],top:i,height:p,value:v},l("changeValues",d),window.getSelection?window.getSelection().removeAllRanges():document.selection.empty()}})(e,s,o),onDblclick:e=>((e,s,a)=>{let o=T(t.values);o[a]={...o[a],top:null,height:null,value:s.default},l("changeValues",o)})(0,s,o)},null,46,he),x("div",{class:E(fe+"-item-area"),style:H({height:s.height||a(s.default,s.min,s.max)+"%"})},null,6)],2),x("div",{class:E(fe+"-item-label")},[x("span",null,B(s.label),1)],2)],2)))),128)):I("",!0)],8,ye)}}},Se={__name:"index",setup(e){const s=J(),{selectors:t,seg_selectors:l,slider_controls:a}=V(s),u=y({}),d=async e=>{var l,a,o,r,d,c,p;await n(1);const v=i(e,"styles"),m=(null==(l=e.properties.values)?void 0:l.length)>0?e.properties.values:[];let g=T(t.value);g.push({id:e.id,type:v.value,value:m,show:!1});const y=g.length-1;await s.setSelectors(g),(null==(a=e.flags)?void 0:a.collapsed)&&e.collapse();let h=null==(d=null==(r=null==(o=u.value[e.id])?void 0:o._)?void 0:r.vnode)?void 0:d.el;if(!h)return;let f=e.addDOMWidget("select_styles","btn",h);e.properties.values||e.setProperty("values",[]),g[y].show=!0,await s.setSelectors(g);let A=v.value;Object.defineProperty(v,"value",{set:t=>{A=t,g[y].type=t,e.properties.values=[],g[y].value=[],s.setSelectors(g)},get:e=>A}),Object.defineProperty(f,"value",{set:e=>{setTimeout((t=>{g[y].value=e.split(","),s.setSelectors(g)}),150)},get:s=>{var l,a;return e.properties.values=(null==(a=null==(l=t.value)?void 0:l[y])?void 0:a.value)||[],e.properties.values.join(",")}}),((null==(c=e.size)?void 0:c[0])<150||(null==(p=e.size)?void 0:p[1])<150)&&e.setSize([425,500]);const w=e.onRemoved;e.onRemoved=function(){if(w&&(null==w||w.apply(this,arguments)),void 0!==t.value.findIndex((s=>s.id==e.id))){let e=T(t.value);e.splice(y,1),s.setSelectors(e)}return w}},c=y({}),p=async e=>{var t,a,o,u,d;await n(1);const p=i(e,"method"),v=(null==(t=e.properties.values)?void 0:t.length)>0?e.properties.values:[];let m=T(l.value);m.push({id:e.id,type:p.value,value:v,show:!1});const g=m.length-1;await s.setSegSelectors(m),(null==(a=e.flags)?void 0:a.collapsed)&&e.collapse();let y=null==(d=null==(u=null==(o=c.value[e.id])?void 0:o._)?void 0:u.vnode)?void 0:d.el;if(!y)return;let h=e.addDOMWidget("mask_components","btn",y);e.properties.values||e.setProperty("values",[]),m[g].show=!0,await s.setSegSelectors(m);let f=p.value;Object.defineProperty(p,"value",{set:t=>{f=t,m[g].type=t,e.properties.values=[],m[g].value=[],r(e,i(e,"confidence"),"selfie_multiclass_256x256"===f),e.setSize([300,"selfie_multiclass_256x256"===f?260:400]),s.setSegSelectors(m)},get:e=>f}),Object.defineProperty(h,"value",{set:e=>{setTimeout((t=>{m[g].value=e.split(","),s.setSegSelectors(m)}),150)},get:s=>{var t;return e.properties.values=(null==(t=l.value)?void 0:t[g].value)||[],e.properties.values.join(",")}}),r(e,i(e,"confidence"),"selfie_multiclass_256x256"===f),e.setSize([300,"selfie_multiclass_256x256"===f?260:500]);const A=e.onRemoved;e.onRemoved=function(){if(A&&(null==A||A.apply(this,arguments)),void 0!==l.value.findIndex((s=>s.id==e.id))){let e=T(l.value);e.splice(g,1),s.setSegSelectors(e)}return A}},v=y({}),m=async e=>{var t,l,o,r,u;await n(1);const d=i(e,"mode"),c=i(e,"model_type"),p=(null==(t=e.properties.values)?void 0:t.length)>0?e.properties.values:[];(null==(l=e.flags)?void 0:l.collapsed)&&e.collapse();let m=T(a.value);m.push({id:e.id,type:c.value,mode:d.value,value:p,show:!1});const g=m.length-1;await s.setSliderControls(m);let y=null==(u=null==(r=null==(o=v.value[e.id])?void 0:o._)?void 0:r.vnode)?void 0:u.el;if(!y)return;let h=e.addDOMWidget("values","btn",y);e.properties.values||e.setProperty("values",[]),Object.defineProperty(h,"value",{set:function(){},get:s=>{var t;const l=(null==(t=a.value)?void 0:t[g].value)||[];return e.properties.values=l.map(((e,s)=>`${s}:${e.value}`)),e.properties.values.join(",")}}),e.setSize("sdxl"==c.value?[375,320]:[455,320]),c.callback=t=>{m=T(a.value),m[g].type!=t&&(e.setSize("sdxl"==t?[375,320]:[455,320]),m[g].value=[],m[g].type=t,s.setSliderControls(m))};const f=e.onRemoved;e.onRemoved=function(){if(f&&(null==f||f.apply(this,arguments)),void 0!==a.value.findIndex((s=>s.id==e.id))){let e=T(a.value);e.splice(g,1),s.setSliderControls(e)}return f}};return N((e=>{o.registerExtension({name:"Comfy.EasyUse.Components",async beforeRegisterNodeDef(e,s){const t=e.prototype.onNodeCreated;"easy stylesSelector"==s.name&&(e.prototype.onNodeCreated=async function(){return t&&(null==t||t.apply(this,arguments)),await d(this),t}),"easy humanSegmentation"==s.name&&(e.prototype.onNodeCreated=async function(){return t&&(null==t||t.apply(this,arguments)),await p(this),t}),"easy sliderControl"==s.name&&(e.prototype.onNodeCreated=async function(){return t&&(null==t||t.apply(this,arguments)),await m(this),t}),"easy poseEditor"==s.name&&(e.prototype.onNodeCreated=async function(){t&&(null==t||t.apply(this,arguments));const e=document.createElement("div");return e.className="comfyui-easyuse-poseEditor",e.innerHTML='
This node is about to be removed, you can use ComfyUI_Custom_Nodes_AlekPet to replace it.
',this.addDOMWidget("editor","btn",e),t})}})})),(e,o)=>(A(),w(S,null,[(A(!0),w(S,null,_(C(t),((e,l)=>(A(),Q(de,{ref_for:!0,ref:s=>{s&&(u.value[e.id]=s)},type:e.type,key:l,id:e.id,show:e.show,selectedStyles:e.value,onChooseStyle:e=>((e,l)=>{let a=T(t.value);a[l].value=e,s.setSelectors(a)})(e,l)},null,8,["type","id","show","selectedStyles","onChooseStyle"])))),128)),(A(!0),w(S,null,_(C(l),((e,t)=>(A(),Q(ge,{ref_for:!0,ref:s=>{s&&(c.value[e.id]=s)},type:e.type,key:t,id:e.id,show:e.show,selected:e.value,onSelect:e=>((e,t)=>{let a=T(l.value);a[t].value=e,s.setSegSelectors(a)})(e,t)},null,8,["type","id","show","selected","onSelect"])))),128)),(A(!0),w(S,null,_(C(a),((e,t)=>(A(),Q(we,{ref_for:!0,ref:s=>{s&&(v.value[e.id]=s)},type:e.type,key:t,id:e.id,show:e.show,mode:e.mode,values:e.value,onChangeValues:e=>((e,t)=>{let l=T(a.value);l[t].value=e,s.setSliderControls(l)})(e,t),onShowSlider:e=>(e=>{let t=T(a.value);t[e].show=!0,s.setSliderControls(t)})(t)},null,8,["type","id","show","mode","values","onChangeValues","onShowSlider"])))),128))],64))}},xe={class:"no-result-placeholder"},Ee={class:"no-result-placeholder-content"},Me={key:0},be={__name:"noResultsPlaceholder",props:{icon:{type:String,default:"",required:!1},iconSize:{type:String,default:"3rem",required:!1},title:{type:String,required:!0},message:{type:String,required:!1},buttonLabel:{type:String,default:"",required:!1}},emits:["action"],setup:e=>(s,t)=>(A(),w("div",xe,[z(C(G),null,{content:j((()=>[x("div",Ee,[x("i",{class:E(e.icon),style:H({"font-size":e.iconSize,"margin-bottom":".5rem"})},null,6),x("h3",null,B(e.title),1),e.message?(A(),w("p",Me,B(e.message),1)):I("",!0),e.buttonLabel?(A(),Q(C(P),{key:1,label:e.buttonLabel,onClick:t[0]||(t[0]=e=>s.$emit("action")),class:"p-button-text"},null,8,["label"])):I("",!0)])])),_:1})]))},Ce={class:"left flex-1"},_e={key:1,class:"edit"},ke={key:2,class:"label"},Be={class:"right toolbar"},Ie={key:0,class:"nodes"},Ne={__name:"group",props:{item:{type:Object,default:{}}},emits:["mousedown","mouseup","changeMode"],setup(e){const s=e,t=u(),l=y(!1),a=y(null),n=y(""),i=e=>{var l,a;let n=s.item;if(!(null==(l=n.info)?void 0:l.is_edit)&&(null==(a=n.children)?void 0:a.length)>0){let e=o.canvas.graph._groups.find((e=>e.pos[0]==n.info.pos[0]&&e.pos[1]==n.info.pos[1]));e&&(e.show_nodes=!e.show_nodes,t.setGroups(o.canvas.graph._groups))}},r=async()=>{let e=s.item,a=o.canvas.graph._groups.find((s=>s.pos[0]==e.info.pos[0]&&s.pos[1]==e.info.pos[1]));a?(a.is_edit=!1,a.title=n.value,await t.setGroups(o.canvas.graph._groups),l.value=!1):l.value=!1};return(u,c)=>{var p,v,m;return A(),w(S,null,[x("div",{class:E("comfyui-easyuse-map-nodes-group"),onClick:i},[x("div",Ce,[e.item.children?(A(),w("i",{key:0,class:E(["icon",e.item.info.show_nodes?"pi pi-folder-open":"pi pi-folder"]),style:H({color:e.item.info.color})},null,6)):I("",!0),(null==(p=e.item.info)?void 0:p.is_edit)?(A(),w("div",_e,[z(C(W),{ref_key:"modifyRef",ref:a,modelValue:n.value,"onUpdate:modelValue":c[0]||(c[0]=e=>n.value=e),variant:"outline",size:"small",type:"text",onBlur:r,onKeydown:[R(r,["enter"]),R(r,["esc"])],style:{width:"100%"}},null,8,["modelValue"])])):(A(),w("div",ke,[x("span",{onDblclick:c[1]||(c[1]=k((i=>(async()=>{var e,i;if(l.value)return;let r=s.item,u=o.canvas.graph._groups.find((e=>e.pos[0]==r.info.pos[0]&&e.pos[1]==r.info.pos[1]));u&&(u.is_edit=!u.is_edit,n.value=u.is_edit?r.info.title:"",await t.setGroups(o.canvas.graph._groups),l.value=!0,null==(i=null==(e=a.value)?void 0:e[0])||i.$el.focus())})(e.item)),["stop"]))},B(e.item.info.title),33)]))]),x("div",Be,[(null==(v=e.item.children)?void 0:v.length)>0?(A(),Q(C(P),{key:0,size:"small",icon:e.item.children.find((e=>e.mode==C(d).ALWAYS))?"pi pi-eye":"pi pi-eye-slash",text:"",rounded:"",severity:"secondary",onClick:c[2]||(c[2]=k((e=>u.$emit("changeMode")),["stop"])),onMousedown:c[3]||(c[3]=k((e=>u.$emit("mousedown")),["stop"])),onMouseup:c[4]||(c[4]=k((e=>u.$emit("mouseup")),["stop"]))},null,8,["icon"])):I("",!0)])]),(null==(m=e.item.children)?void 0:m.length)>0&&e.item.info.show_nodes?(A(),w("div",Ie,[D(u.$slots,"default")])):I("",!0)],64)}}},He={key:1,class:"label error"},Qe={class:"right toolbar"},ze={__name:"node",props:{node:{type:Object,default:{}}},emits:["mousedown","mouseup","changeMode"],setup:e=>(s,t)=>(A(),w("div",{draggable:!1,class:E(["comfyui-easyuse-map-nodes-node",{never:void 0!==e.node.mode&&e.node.mode==C(d).NEVER},{bypass:void 0!==e.node.mode&&e.node.mode==C(d).BYPASS}])},[void 0!==e.node.title?(A(),w("span",{key:0,class:"label",onDblclick:t[0]||(t[0]=k((s=>C(c)(e.node.id)),["stop"]))},B(e.node.title),33)):(A(),w("span",He,B(e.node.type),1)),x("div",Qe,[z(C(P),{size:"small",icon:e.node.mode==C(d).ALWAYS?"pi pi-eye":"pi pi-eye-slash",text:"",rounded:"",severity:"secondary",onClick:t[1]||(t[1]=k((e=>s.$emit("changeMode")),["stop"])),onMousedown:t[2]||(t[2]=k((e=>s.$emit("mousedown")),["stop"])),onMouseup:t[3]||(t[3]=k((e=>s.$emit("mouseup")),["stop"]))},null,8,["icon"])])],2))},je={class:"title"},Re={class:"toolbar"},De={key:0},Le=["onDragstart","onDragend","onDragover"],Ze={key:1,class:"no_result",style:{height:"100%"}},Ye="comfyui-easyuse-map-nodes",Oe={__name:"nodesMap",emits:["handleHeader"],setup(e){const t=u(),{groups_nodes:l,groups:n}=V(t),i=y(!1),r=e=>{i.value=!i.value,o.canvas.graph._groups.forEach((e=>{e.show_nodes=i.value})),t.setGroups(o.canvas.graph._groups)};let c,v=0,m=0,g=!1;const h=(e,s=!1)=>{if(g)return void(g=!1);const l=e.children.find((e=>e.mode==d.ALWAYS)),a=e.children.map((e=>e.id));o.canvas.graph._nodes.forEach((e=>{a.includes(e.id)&&(e.mode=l?s?d.NEVER:d.BYPASS:d.ALWAYS,e.graph.change())})),t.setNodes(o.canvas.graph._nodes)},f=(e,s=!1)=>{if(g)return void(g=!1);const l=e.mode==d.ALWAYS,a=o.canvas.graph._nodes.find((s=>s.id==e.id));a&&(a.mode=l?s?d.NEVER:d.BYPASS:d.ALWAYS,a.graph.change(),t.setNodes(o.canvas.graph._nodes))},b=(e,s="group")=>{v=(new Date).getTime(),clearTimeout(c),c=setTimeout((t=>{"group"==s?h(e,!0):f(e,!0)}),500)},N=e=>{m=(new Date).getTime(),m-v>500&&(g=!0),clearTimeout(c)};let H=y(null),R=y(null);y(!1);return(e,u)=>{var d,c;return A(),w("div",{class:E(Ye)},[x("div",{class:E(Ye+"__header"),onMousedown:u[0]||(u[0]=s=>e.$emit("handleHeader",s))},[x("div",je,B(C(s)("Nodes Map",!0)),1),x("div",Re,[(null==(d=C(n))?void 0:d.length)>0?M((A(),Q(C(P),{key:0,icon:i.value?"pi pi-angle-double-down":"pi pi-angle-double-up",text:"",rounded:"",severity:"secondary",onClick:k(r,["stop"]),size:"small"},null,8,["icon"])),[[C(U),i.value?C(s)("Collapse All"):C(s)("Expand All"),void 0,{top:!0}]]):I("",!0),D(e.$slots,"icon")])],34),x("div",{class:E(Ye+"__content")},[(null==(c=C(l))?void 0:c.length)>0?(A(),w("ol",De,[(A(!0),w(S,null,_(C(l),((e,l)=>(A(),w("li",{key:l,onDragstart:e=>((e,s)=>{H.value=s,e.currentTarget.style.opacity="0.6",e.currentTarget.style.border="1px dashed yellow",e.dataTransfer.effectAllowed="move"})(e,l),onDragend:e=>(e=>{if(e.target.style.opacity="1",e.currentTarget.style.border="1px dashed transparent","Manual drag&drop sorting"!==p("EasyUse.NodesMap.Sorting"))return void a.warn(s("For drag and drop sorting, please find Nodes map sorting mode in Settings->EasyUse and change it to manual"));let l=o.canvas.graph._groups,n=l[H.value],i=l[R.value];o.canvas.graph._groups[H.value]=i,o.canvas.graph._groups[R.value]=n,t.setGroups(o.canvas.graph._groups)})(e),onDragover:e=>((e,s)=>{e.preventDefault(),e.currentIndex!=H.value&&(R.value=s)})(e,l),draggable:!0},[void 0!==e.children?(A(),Q(Ne,{key:0,item:e,onChangeMode:s=>h(e),onMousedown:s=>b(e,"group"),onMouseup:N},{default:j((()=>[(A(!0),w(S,null,_(e.children,((e,s)=>(A(),Q(ze,{key:s,node:e,onChangeMode:s=>f(e),onMousedown:s=>b(e,"node"),onMouseup:N},null,8,["node","onChangeMode","onMousedown"])))),128))])),_:2},1032,["item","onChangeMode","onMousedown"])):(A(),Q(ze,{key:1,node:e.info,onChangeMode:s=>f(e.info),onMousedown:s=>b(e.info,"node"),onMouseup:N},null,8,["node","onChangeMode","onMousedown"]))],40,Le)))),128))])):(A(),w("div",Ze,[z(be,{icon:"pi pi-sitemap",title:C(s)("No Nodes",!0),message:C(s)("No nodes found in the map",!0)},null,8,["title","message"])]))],2)])}}},Ve=[x("svg",{class:"icon",t:"1714565543756",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"22538",width:"200",height:"200"},[x("path",{d:"M871.616 64H152.384c-31.488 0-60.416 25.28-60.416 58.24v779.52c0 32.896 26.24 58.24 60.352 58.24h719.232c34.112 0 60.352-25.344 60.352-58.24V122.24c0.128-32.96-28.8-58.24-60.288-58.24zM286.272 512c-23.616 0-44.672-20.224-44.672-43.008 0-22.784 20.992-43.008 44.608-43.008 23.616 0 44.608 20.224 44.608 43.008A43.328 43.328 0 0 1 286.272 512z m0-202.496c-23.616 0-44.608-20.224-44.608-43.008 0-22.784 20.992-43.008 44.608-43.008 23.616 0 44.608 20.224 44.608 43.008a43.456 43.456 0 0 1-44.608 43.008zM737.728 512H435.904c-23.68 0-44.672-20.224-44.672-43.008 0-22.784 20.992-43.008 44.608-43.008h299.264c23.616 0 44.608 20.224 44.608 43.008a42.752 42.752 0 0 1-41.984 43.008z m0-202.496H435.904c-23.616 0-44.608-20.224-44.608-43.008 0-22.784 20.992-43.008 44.608-43.008h299.264c23.616 0 44.608 20.224 44.608 43.008a42.88 42.88 0 0 1-42.048 43.008z","p-id":"22539",fill:"currentColor"})],-1)],Pe=[x("svg",{class:"icon",t:"1714565020764",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"7999",width:"200",height:"200"},[x("path",{d:"M810.438503 379.664884l-71.187166-12.777183C737.426025 180.705882 542.117647 14.602496 532.991087 7.301248c-12.777184-10.951872-32.855615-10.951872-47.45811 0-9.12656 7.301248-204.434938 175.229947-206.26025 359.586453l-67.536542 10.951871c-18.253119 3.650624-31.030303 18.253119-31.030303 36.506239v189.832442c0 10.951872 5.475936 21.903743 12.777184 27.379679 7.301248 5.475936 14.602496 9.12656 23.729055 9.12656h5.475936l133.247772-23.729055c40.156863 47.458111 91.265597 73.012478 151.500891 73.012477 60.235294 0 111.344029-27.379679 151.500891-74.837789l136.898396 23.729055h5.475936c9.12656 0 16.427807-3.650624 23.729055-9.12656 9.12656-7.301248 12.777184-16.427807 12.777184-27.379679V412.520499c1.825312-14.602496-10.951872-29.204991-27.379679-32.855615zM620.606061 766.631016H401.568627c-20.078431 0-36.506239 16.427807-36.506238 36.506239v109.518716c0 14.602496 9.12656 29.204991 23.729055 34.680927 14.602496 5.475936 31.030303 1.825312 40.156863-9.126559l16.427807-18.25312 32.855615 80.313726c5.475936 14.602496 18.253119 23.729055 34.680927 23.729055 16.427807 0 27.379679-9.12656 34.680927-23.729055l32.855615-80.313726 16.427807 18.25312c10.951872 10.951872 25.554367 14.602496 40.156863 9.126559 14.602496-5.475936 23.729055-18.253119 23.729055-34.680927v-109.518716c-3.650624-20.078431-20.078431-36.506239-40.156862-36.506239z",fill:"currentColor","p-id":"8000"})],-1)],Ge="comfyui-easyuse-toolbar",We={__name:"index",setup(e){const t=u(),l=y(!1);h((e=>l.value),(e=>{e?t.watchGraph(!0):t.unwatchGraph()}));const a=y(null),o=e=>{const s=a.value;var t=e.clientX||0,l=e.clientY||0,o=s.offsetLeft,n=s.offsetTop;function i(e){var a=e.clientX,i=e.clientY,r=a-t,u=i-l;s.style.left=o+r+"px",s.style.top=n+u+"px"}document.addEventListener("mousemove",i),document.addEventListener("mouseup",(function e(){document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",e)}))};return(e,t)=>(A(),w(S,null,[x("div",{class:E(["flex-c",Ge])},[x("div",{class:E(["group flex-c",Ge+"-icon"]),onClick:t[0]||(t[0]=e=>l.value=!l.value)},Ve,2),x("div",{class:E(["rocket flex-c",Ge+"-icon"]),onClick:t[1]||(t[1]=(...e)=>C(v)&&C(v)(...e))},Pe,2)]),l.value?(A(),w("div",{key:0,ref_key:"nodesMapRef",ref:a,class:E(Ge+"-nodes-map")},[z(Oe,{onHandleHeader:o},{icon:j((()=>[M(z(C(P),{icon:"pi pi-times",text:"",rounded:"",severity:"secondary",onClick:t[2]||(t[2]=e=>l.value=!1),size:"small"},null,512),[[C(U),C(s)("Close"),void 0,{top:!0}]])])),_:1})],2)):I("",!0)],64))}},Ue={__name:"index",setup(e){const s=u();return N((e=>{s.watchGraph()})),(e,s)=>(A(),w("div",{class:E("comfyui-easyuse-map")},[z(Oe)]))}},Fe="Comfy.UseNewMenu",Te={__name:"App",setup(e){const t=y(null);return N((e=>{try{p("EasyUse.NodesMap.Enable",null,!0)&&o.extensionManager.registerSidebarTab({id:m,icon:"pi pi-sitemap",title:s("Nodes Map",!0),tooltip:s("Nodes Map",!0),type:"custom",render:e=>{e.style.height="100%",L(Z(Ue,{}),e)}}),t.value=p(Fe),g(Fe,(e=>{t.value=e}))}catch(l){}})),(e,s)=>(A(),w(S,null,[z(Se),"Disabled"==t.value?(A(),Q(We,{key:0})):I("",!0)],64))}},Xe=null==(e=document.getElementsByClassName("graph-canvas-container"))?void 0:e[0],Je=document.createElement("div");Je.id="comfyui-easyuse-components",Xe?Xe.append(Je):document.body.append(Je);const qe=Y(Te);qe.use(X),qe.use(F()),qe.mount("#"+Je.id); +!function(){"use strict";try{if("undefined"!=typeof document){var e=document.createElement("style");e.appendChild(document.createTextNode('@charset "UTF-8";.easyuse-model-info{color:#fff;max-width:90vw;font-family:var(--font-family)}.easyuse-model-content{display:flex;flex-direction:column;overflow:hidden}.easyuse-model-header{margin:0 0 15px}.easyuse-model-header-remark{display:flex;align-items:center;margin-top:5px}.easyuse-model-info h2{text-align:left;margin:0}.easyuse-model-info h5{text-align:left;margin:0 15px 0 0;font-weight:400;color:var(--descrip-text)}.easyuse-model-info p{margin:5px 0}.easyuse-model-info a{color:var(--theme-color-light)}.easyuse-model-info a:hover{text-decoration:underline}.easyuse-model-tags-list{display:flex;flex-wrap:wrap;list-style:none;gap:10px;max-height:200px;overflow:auto;margin:10px 0;padding:0}.easyuse-model-tag{background-color:var(--comfy-input-bg);border:2px solid var(--border-color);color:var(--input-text);display:flex;align-items:center;gap:5px;border-radius:5px;padding:2px 5px;cursor:pointer}.easyuse-model-tag--selected span:before{content:"✅";position:absolute;background-color:var(--theme-color-light);left:0;top:0;right:0;bottom:0;text-align:center}.easyuse-model-tag:hover{border:2px solid var(--theme-color-light)}.easyuse-model-tag p{margin:0}.easyuse-model-tag span{text-align:center;border-radius:5px;background-color:var(--theme-color-light);padding:2px;position:relative;min-width:20px;overflow:hidden;color:#fff}.easyuse-model-metadata .comfy-modal-content{max-width:100%}.easyuse-model-metadata label{margin-right:1ch;color:#ccc}.easyuse-model-metadata span{color:var(--theme-color-light)}.easyuse-preview{max-width:660px;margin-right:15px;position:relative}.easyuse-preview-group{position:relative;overflow:hidden;border-radius:.5rem;width:660px}.easyuse-preview-list{display:flex;flex-wrap:nowrap;width:100%;transition:all .5s ease-in-out}.easyuse-preview-list.no-transition{transition:none}.easyuse-preview-slide{display:flex;flex-basis:calc(50% - 5px);flex-grow:0;flex-shrink:0;position:relative;justify-content:center;align-items:center;padding-right:5px;padding-left:0}.easyuse-preview-slide:nth-child(2n){padding-left:5px;padding-right:0}.easyuse-preview-slide-content{position:relative;min-height:150px;width:100%}.easyuse-preview-slide-content .save{position:absolute;right:6px;z-index:12;bottom:6px;display:flex;align-items:center;height:26px;padding:0 9px;color:var(--input-text);font-size:12px;line-height:26px;background:#00000080;border-radius:13px;cursor:pointer;min-width:80px;text-align:center}.easyuse-preview-slide-content .save:hover{filter:brightness(120%);will-change:auto}.easyuse-preview-slide-content img{border-radius:14px;object-position:center center;max-width:100%;max-height:700px;border-style:none;vertical-align:middle}.easyuse-preview button{position:absolute;z-index:10;top:50%;display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:15px;border:1px solid rgba(66,63,78,.15);background-color:#423f4e80;color:#fffc;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s;transform:translateY(-50%)}.easyuse-preview button.left{left:10px}.easyuse-preview button.right{right:10px}.easyuse-model-detail{margin-top:16px;overflow:hidden;border:1px solid var(--border-color);border-radius:8px;width:300px}.easyuse-model-detail-head{height:40px;padding:0 10px;font-weight:500;font-size:14px;font-style:normal;line-height:40px}.easyuse-model-detail-body{box-sizing:border-box;font-size:12px}.easyuse-model-detail-item{display:flex;justify-content:flex-start;border-top:1px solid var(--border-color)}.easyuse-model-detail-item-label{flex-shrink:0;width:88px;padding-top:5px;padding-bottom:5px;padding-left:10px;border-right:1px solid var(--border-color);color:var(--input-text);font-weight:400}.easyuse-model-detail-item-value{display:flex;flex-wrap:wrap;padding:5px 10px;color:var(--input-text)}.easyuse-model-detail-textarea{border-top:1px solid var(--border-color);padding:10px;height:100px;overflow-y:auto;font-size:12px}.easyuse-model-detail-textarea textarea{width:100%;height:100%;border:0;background-color:transparent;color:var(--input-text)}.easyuse-model-detail-textarea textarea::placeholder{color:var(--descrip-text)}.easyuse-model-detail-textarea.empty{display:flex;justify-content:center;align-items:center;color:var(--descrip-text)}.easyuse-model-notes{background-color:#00000040;padding:5px;margin-top:5px}.easyuse-model-notes:empty{display:none}.easyuse-account-user{font-size:10px;color:var(--descrip-text);text-align:center}.easyuse-account-user-info{display:flex;justify-content:space-between;align-items:center;padding-bottom:10px;cursor:pointer}.easyuse-account-user-info .user{display:flex;align-items:center}.easyuse-account-user-info .edit{padding:5px 10px;background:var(--comfy-menu-bg);border-radius:4px}.easyuse-account-user-info:hover{filter:brightness(110%)}.easyuse-account-user-info h5{margin:0;font-size:10px;text-align:left}.easyuse-account-user-info h6{margin:0;font-size:8px;text-align:left;font-weight:300}.easyuse-account-user-info .remark{margin-top:4px}.easyuse-account-user-info .avatar{width:36px;height:36px;background:var(--comfy-input-bg);border-radius:50%;margin-right:5px;display:flex;justify-content:center;align-items:center;font-size:16px;overflow:hidden}.easyuse-account-user-info .avatar img{width:100%;height:100%}.easyuse-account-dialog{width:600px}.easyuse-account-dialog-main a,.easyuse-account-dialog-main a:visited{font-weight:400;color:var(--theme-color-light)}.easyuse-account-dialog-item{display:flex;justify-content:flex-start;align-items:center;padding:10px 0;border-bottom:1px solid var(--border-color)}.easyuse-account-dialog-item input{padding:5px;margin-right:5px}.easyuse-account-dialog-item input.key{flex:1}.easyuse-account-dialog-item button{cursor:pointer;margin-left:5px!important;padding:5px!important;font-size:16px!important}.easyuse-account-dialog-item button:hover{filter:brightness(120%)}.easyuse-account-dialog-item button.choose{background:var(--theme-color)}.easyuse-account-dialog-item button.delete{background:var(--error-color)}.easy-dropdown,.easy-nested-dropdown{position:relative;box-sizing:border-box;background-color:#171717;box-shadow:0 4px 4px #ffffff40;padding:0;margin:0;list-style:none;z-index:1000;overflow:visible;max-height:fit-content;max-width:fit-content}.easy-dropdown{position:absolute;border-radius:0}.easy-dropdown li.item,.easy-nested-dropdown li.item{font-weight:400;min-width:max-content}.easy-dropdown li.folder,.easy-nested-dropdown li.folder{cursor:default;position:relative;border-right:3px solid cyan}.easy-dropdown li.folder:after,.easy-nested-dropdown li.folder:after{content:">";position:absolute;right:2px;font-weight:400}.easy-dropdown li,.easy-nested-dropdown li{padding:4px 10px;cursor:pointer;font-family:system-ui;font-size:.7rem;position:relative}.easy-nested-dropdown{position:absolute;top:0;left:100%;margin:0;border:none;display:none}.easy-dropdown li.selected>.easy-nested-dropdown,.easy-nested-dropdown li.selected>.easy-nested-dropdown{display:block;border:none}.easy-dropdown li.selected,.easy-nested-dropdown li.selected{background-color:#e5e5e5;border:none}#crystools-root-easyuse{flex-direction:row;justify-content:center;flex-shrink:1;width:min-content;min-width:max-content;height:100%;margin:0 auto}#crystools-root-easyuse .crystools-monitor-container{width:100%;cursor:crosshair;display:flex;flex-wrap:wrap;flex-direction:row;justify-content:flex-end;gap:5px;height:100%}#crystools-root-easyuse .crystools-monitor-container .crystools-monitor{background-color:var(--comfy-input-bg);position:relative;align-items:center;flex-direction:row;width:40px;height:100%}#crystools-root-easyuse .crystools-monitor-container .crystools-monitor .crystools-text{font-size:10px;text-align:right;margin-left:3px;position:absolute;font-weight:100;bottom:2px;z-index:10}#crystools-root-easyuse .crystools-monitor-container .crystools-content{position:relative;height:100%}#crystools-root-easyuse .crystools-monitor-container .crystools-slider{position:absolute;height:100%;width:0;box-shadow:inset 2px 2px 10px #0003}#crystools-root-easyuse .crystools-monitor-container .crystools-label{position:relative;width:100%;color:var(--input-text);font-weight:500;font-size:11px;left:2px;top:4px;text-align:right}:root{--theme-color:var(--primary-bg);--theme-color-light: var(--primary-hover-bg);--success-color: #52c41a;--error-color: #ff4d4f;--warning-color: #faad14;--font-family: Inter, -apple-system, BlinkMacSystemFont, Helvetica Neue, sans-serif;--p-inputtext-background: var(--p-form-field-background);--p-inputtext-disabled-background: var(--p-form-field-disabled-background);--p-inputtext-filled-background: var(--p-form-field-filled-background);--p-inputtext-filled-focus-background: var(--p-form-field-filled-focus-background);--p-inputtext-border-color: var(--p-form-field-border-color);--p-inputtext-hover-border-color: var(--p-form-field-hover-border-color);--p-inputtext-focus-border-color: var(--p-form-field-focus-border-color);--p-inputtext-invalid-border-color: var(--p-form-field-invalid-border-color);--p-inputtext-color: var(--p-form-field-color);--p-inputtext-disabled-color: var(--p-form-field-disabled-color);--p-inputtext-placeholder-color: var(--p-form-field-placeholder-color);--p-inputtext-shadow: var(--p-form-field-shadow);--p-inputtext-padding-x: var(--p-form-field-padding-x);--p-inputtext-padding-y: var(--p-form-field-padding-y);--p-inputtext-border-radius: var(--p-form-field-border-radius);--p-inputtext-focus-ring-width: var(--p-form-field-focus-ring-width);--p-inputtext-focus-ring-style: var(--p-form-field-focus-ring-style);--p-inputtext-focus-ring-color: var(--p-form-field-focus-ring-color);--p-inputtext-focus-ring-offset: var(--p-form-field-focus-ring-offset);--p-inputtext-focus-ring-shadow: var(--p-form-field-focus-ring-shadow);--p-inputtext-transition-duration: var(--p-form-field-transition-duration);--p-inputtext-sm-font-size: .875rem;--p-inputtext-sm-padding-x: .625rem;--p-inputtext-sm-padding-y: .375rem;--p-inputtext-lg-font-size: 1.125rem;--p-inputtext-lg-padding-x: .875rem;--p-inputtext-lg-padding-y: .625rem;--p-tooltip-max-width: 12.5rem;--p-tooltip-gutter: .25rem;--p-tooltip-shadow: var(--p-overlay-popover-shadow);--p-tooltip-padding: .5rem .75rem;--p-tooltip-border-radius: var(--p-overlay-popover-border-radius);--p-tooltip-background: var(--p-surface-700);--p-tooltip-color: var(--p-surface-0)}.comfyui-easyuse-theme,.comfyui-easyuse-primary{color:var(--theme-color-light)}.comfyui-easyuse-theme.point:hover,.comfyui-easyuse-primary.point:hover{opacity:.8}.comfyui-easyuse-success{color:var(--success-color)}.comfyui-easyuse-success.point:hover{opacity:.8}.comfyui-easyuse-error{color:var(--error-color)}.comfyui-easyuse-error.point:hover{opacity:.8}.comfyui-easyuse-warning,.comfyui-easyuse--warn{color:var(--warning-color)}.comfyui-easyuse-warning.point:hover,.comfyui-easyuse--warn.point:hover{opacity:.8}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.flex-nowrap{flex-wrap:nowrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.grid{display:grid}.gap-0\\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-10{gap:2.5rem}.gap-12{gap:3rem}.gap-2{gap:.5rem}.gap-2\\.5{gap:.625rem}.gap-20{gap:5rem}.gap-3{gap:.75rem}.gap-3\\.5{gap:.875rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-7{gap:1.75rem}.gap-8{gap:2rem}.gap-\\[0\\.28rem\\]{gap:.28rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.gap-y-6{row-gap:1.5rem}@media (min-width: 576px){.sm\\:col-span-4{grid-column:span 4/span 4}.sm\\:col-span-6{grid-column:span 6/span 6}.sm\\:ml-8{margin-left:2rem}.sm\\:mt-0{margin-top:0}.sm\\:flex{display:flex}.sm\\:h-60{height:15rem}.sm\\:\\!w-64{width:16rem!important}.sm\\:w-40{width:10rem}.sm\\:w-44{width:11rem}.sm\\:w-56{width:14rem}.sm\\:w-60{width:15rem}.sm\\:w-64{width:16rem}.sm\\:w-80{width:20rem}.sm\\:w-96{width:24rem}.sm\\:w-\\[30rem\\]{width:30rem}.sm\\:w-auto{width:auto}.sm\\:min-w-\\[30rem\\]{min-width:30rem}.sm\\:flex-row{flex-direction:row}.sm\\:flex-col{flex-direction:column}.sm\\:flex-nowrap{flex-wrap:nowrap}.sm\\:items-start{align-items:flex-start}.sm\\:items-end{align-items:flex-end}.sm\\:items-center{align-items:center}.sm\\:justify-center{justify-content:center}.sm\\:justify-between{justify-content:space-between}.sm\\:gap-2{gap:.5rem}.sm\\:p-20{padding:5rem}.sm\\:px-10{padding-left:2.5rem;padding-right:2.5rem}.sm\\:py-10{padding-bottom:2.5rem;padding-top:2.5rem}.sm\\:py-5{padding-bottom:1.25rem;padding-top:1.25rem}.sm\\:pt-32{padding-top:8rem}.sm\\:text-left{text-align:left}.sm\\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\\:-bottom-12{bottom:-3rem}.md\\:-bottom-28{bottom:-7rem}.md\\:-bottom-8{bottom:-2rem}.md\\:-bottom-\\[26rem\\]{bottom:-26rem}.md\\:-left-12{left:-3rem}.md\\:-left-28{left:-7rem}.md\\:-left-32{left:-8rem}.md\\:-left-4{left:-1rem}.md\\:-left-48{left:-12rem}.md\\:-left-\\[22rem\\]{left:-22rem}.md\\:bottom-0{bottom:0}.md\\:left-10{left:2.5rem}.md\\:left-\\[32rem\\]{left:32rem}.md\\:left-\\[42rem\\]{left:42rem}.md\\:top-1\\/2{top:50%}.md\\:top-32{top:8rem}.md\\:top-8{top:2rem}.md\\:col-span-2{grid-column:span 2/span 2}.md\\:col-span-4{grid-column:span 4/span 4}.md\\:col-span-6{grid-column:span 6/span 6}.md\\:ml-auto{margin-left:auto}.md\\:block{display:block}.md\\:flex{display:flex}.md\\:hidden{display:none}.md\\:h-\\[20rem\\]{height:20rem}.md\\:h-\\[32rem\\]{height:32rem}.md\\:\\!w-80{width:20rem!important}.md\\:w-2\\/12{width:16.666667%}.md\\:w-40{width:10rem}.md\\:w-5\\/12{width:41.666667%}.md\\:w-56{width:14rem}.md\\:w-6\\/12{width:50%}.md\\:w-60{width:15rem}.md\\:w-8\\/12{width:66.666667%}.md\\:w-80{width:20rem}.md\\:w-\\[100rem\\]{width:100rem}.md\\:w-\\[26rem\\]{width:26rem}.md\\:w-\\[30rem\\]{width:30rem}.md\\:w-\\[50rem\\]{width:50rem}.md\\:w-\\[52rem\\]{width:52rem}.md\\:w-\\[60rem\\]{width:60rem}.md\\:w-\\[95rem\\]{width:95rem}.md\\:w-screen{width:100vw}.md\\:flex-initial{flex:0 1 auto}.md\\:-translate-y-1\\/2{--tw-translate-y: -50% }.md\\:-translate-y-1\\/2,.md\\:translate-x-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.md\\:translate-x-0{--tw-translate-x: 0px }.md\\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\\:flex-row{flex-direction:row}.md\\:flex-col{flex-direction:column}.md\\:items-end{align-items:flex-end}.md\\:items-center{align-items:center}.md\\:justify-start{justify-content:flex-start}.md\\:justify-center{justify-content:center}.md\\:gap-20{gap:5rem}.md\\:gap-4{gap:1rem}.md\\:p-5{padding:1.25rem}.md\\:p-8{padding:2rem}}@media (min-width: 992px){.lg\\:left-20{left:5rem}.lg\\:left-\\[36rem\\]{left:36rem}.lg\\:left-\\[50rem\\]{left:50rem}.lg\\:col-span-1{grid-column:span 1/span 1}.lg\\:col-span-2{grid-column:span 2/span 2}.lg\\:col-span-4{grid-column:span 4/span 4}.lg\\:col-span-6{grid-column:span 6/span 6}.lg\\:mb-0{margin-bottom:0}.lg\\:mt-0{margin-top:0}.lg\\:mt-20{margin-top:5rem}.lg\\:flex{display:flex}.lg\\:hidden{display:none}.lg\\:h-10{height:2.5rem}.lg\\:h-32{height:8rem}.lg\\:h-\\[28rem\\]{height:28rem}.lg\\:\\!w-\\[30rem\\]{width:30rem!important}.lg\\:w-3\\/12{width:25%}.lg\\:w-32{width:8rem}.lg\\:w-\\[28rem\\]{width:28rem}.lg\\:w-\\[64rem\\]{width:64rem}.lg\\:w-fit{width:-moz-fit-content;width:fit-content}.lg\\:max-w-6xl{max-width:72rem}.lg\\:flex-row{flex-direction:row}.lg\\:gap-0{gap:0}.lg\\:rounded-2xl{border-radius:1rem}.lg\\:rounded-3xl{border-radius:1.5rem}.lg\\:rounded-xl{border-radius:.75rem}.lg\\:p-7{padding:1.75rem}.lg\\:px-2{padding-left:.5rem;padding-right:.5rem}.lg\\:px-20{padding-left:5rem;padding-right:5rem}.lg\\:px-56{padding-left:14rem;padding-right:14rem}.lg\\:px-8{padding-left:2rem;padding-right:2rem}.lg\\:px-9{padding-left:2.25rem;padding-right:2.25rem}.lg\\:py-20{padding-bottom:5rem;padding-top:5rem}.lg\\:py-7{padding-bottom:1.75rem;padding-top:1.75rem}.lg\\:pt-0{padding-top:0}.lg\\:text-2xl{font-size:1.5rem;line-height:2rem}.lg\\:text-5xl{font-size:3rem;line-height:1}.lg\\:text-base{font-size:1rem;line-height:1.5rem}.lg\\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width: 1200px){.xl\\:-left-12{left:-3rem}.xl\\:-left-28{left:-7rem}.xl\\:bottom-0{bottom:0}.xl\\:left-36{left:9rem}.xl\\:left-\\[42rem\\]{left:42rem}.xl\\:left-\\[60rem\\]{left:60rem}.xl\\:col-span-3{grid-column:span 3/span 3}.xl\\:col-span-4{grid-column:span 4/span 4}.xl\\:col-span-6{grid-column:span 6/span 6}.xl\\:block{display:block}.xl\\:flex{display:flex}.xl\\:hidden{display:none}.xl\\:h-\\[36\\.25rem\\]{height:36.25rem}.xl\\:\\!w-40{width:10rem!important}.xl\\:w-3\\/12{width:25%}.xl\\:w-6\\/12{width:50%}.xl\\:w-96{width:24rem}.xl\\:w-\\[29rem\\]{width:29rem}.xl\\:max-w-36{max-width:9rem}.xl\\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.xl\\:flex-row{flex-direction:row}.xl\\:items-start{align-items:flex-start}.xl\\:items-center{align-items:center}.xl\\:gap-1{gap:.25rem}.xl\\:gap-6{gap:1.5rem}.xl\\:text-left{text-align:left}}.comfyui-easyuse-toast{position:fixed;z-index:99999;top:0;left:0;height:0;width:100%;display:flex;flex-direction:column;align-items:center;justify-content:start}.comfyui-easyuse-toast-container{position:relative;height:fit-content;padding:4px;margin-top:-100px;opacity:0;z-index:3;-webkit-transition:all .3s ease-in-out;-khtml-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.comfyui-easyuse-toast-container:last-child{z-index:1}.comfyui-easyuse-toast-container.show{opacity:1;margin-top:0!important;transform:translateY(0)}.comfyui-easyuse-toast-container:not(.show){z-index:1}.comfyui-easyuse-toast-container>div{position:relative;background:var(--comfy-menu-bg);color:var(--input-text);height:fit-content;box-shadow:0 0 10px #000000e0;padding:9px 12px;border-radius:var(--border-radius);font-size:14px;pointer-events:all;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-toast-container>div>span{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-toast-container>div>span i{font-size:16px;margin-right:8px}.comfyui-easyuse-toast-container>div>span i.loading{animation:loading-rotate 1s linear infinite}.comfyui-easyuse-toast-container a{cursor:pointer;text-decoration:underline;color:var(--theme-color-light);margin-left:4px;display:inline-block;line-height:1}.comfyui-easyuse-toast-container a:hover{color:var(--theme-color-light);text-decoration:none}@keyframes loading-rotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.comfyui-easyuse-selector{position:relative}.comfyui-easyuse-selector.hide{display:none}.comfyui-easyuse-selector__header{--height:26px;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;height:var(--height);padding-bottom:10px;border-bottom:1px solid var(--border-color-solid)}.comfyui-easyuse-selector__header_button{height:var(--height);width:var(--height);border-radius:var(--border-radius);border:1px solid var(--border-color);font-size:11px;background:var(--bg-color);box-shadow:none;cursor:pointer;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-selector__header_button:hover{filter:brightness(1.2)}.comfyui-easyuse-selector__header_button:hover i{color:var(--error-color)}.comfyui-easyuse-selector__header_button i{font-size:16px;color:var(--input-text);-webkit-transition:all .3s ease-in-out;-khtml-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;transition:all .3s ease-in-out}.comfyui-easyuse-selector__header_search{flex:1;margin-left:10px;border-radius:var(--border-radius);border:1px solid var(--border-color);font-size:11px;background:var(--bg-color);padding:0 8px;height:var(--height);display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-selector__header_search i{font-size:16px}.comfyui-easyuse-selector__header_search .search{vertical-align:middle;margin-left:5px;outline:none;resize:none;border:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-shadow:none;overflow-y:scroll;background:transparent;width:100%;line-height:var(--height);font-size:12px;color:var(--input-text);-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1}.comfyui-easyuse-selector__content{list-style:none;padding:0;margin:0;min-height:150px;height:calc(100% - 28px);overflow-y:auto;overflow-x:hidden}.comfyui-easyuse-selector-item{display:inline-block;position:relative}.comfyui-easyuse-selector-item__tag{display:inline-block;vertical-align:middle;margin-top:8px;margin-right:8px;padding:4px;color:var(--input-text);background-color:var(--bg-color);border-radius:var(--border-radius);border:1px solid var(--border-color);font-size:11px;cursor:pointer;overflow:hidden;position:relative}.comfyui-easyuse-selector-item__tag:hover{filter:brightness(1.2)}.comfyui-easyuse-selector-item__tag.hide{display:none}.comfyui-easyuse-selector-item__tag input{--ring-color: transparent;position:relative;box-shadow:none;border:1px solid var(--border-color);border-radius:2px;background:linear-gradient(135deg,var(--comfy-menu-bg) 0%,var(--comfy-input-bg) 60%)}.comfyui-easyuse-selector-item__tag input[type=checkbox]{display:inline-block;flex-shrink:0;vertical-align:middle;-webkit-appearance:none;-moz-appearance:none;appearance:none;border:1px solid var(--border-color);background-origin:border-box;padding:0;width:1rem;height:1rem;border-radius:4px;color:var(--theme-color-light);-webkit-user-select:none;user-select:none}.comfyui-easyuse-selector-item__tag input[type=checkbox]:checked{border:1px solid var(--theme-color-light);background-color:var(--theme-color-light);background-image:url("data:image/svg+xml,%3csvg viewBox=\'0 0 16 16\' fill=\'white\' xmlns=\'http://www.w3.org/2000/svg\'%3e%3cpath d=\'M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z\'/%3e%3c/svg%3e")}.comfyui-easyuse-selector-item__tag input span{margin:0 4px;vertical-align:middle}.comfyui-easyuse-selector-preview{position:absolute;left:-180px;top:-110px;z-index:2;border:1px solid var(--border-color);border-radius:var(--border-radius);background:var(--comfy-menu-bg);-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px);overflow:hidden}.comfyui-easyuse-selector-preview img{width:150px;height:150px}.comfyui-easyuse-selector-preview__text{font-size:12px;padding:5px 10px;max-width:130px;color:var(--input-text)}.comfyui-easyuse-selector-preview__text h6{line-height:1.15;font-size:10px;margin:10px 0}.comfyui-easyuse-dialog{max-width:600px}.comfyui-easyuse-dialog-title{font-size:18px;font-weight:700;text-align:center;color:var(--input-text);margin:0}.comfyui-easyuse-dialog-images{margin-top:10px;display:flex;flex-wrap:wrap;width:100%;box-sizing:border-box}.comfyui-easyuse-dialog-images img{width:50%;height:auto;cursor:pointer;box-sizing:border-box;filter:brightness(80%)}.comfyui-easyuse-dialog-images img:hover{filter:brightness(100%)}.comfyui-easyuse-dialog-images.selected{border:4px solid var(--success-color)}.comfyui-easyuse-dialog-hidden{display:none;height:0}.comfyui-easyuse-contextmenu{--height: 26px;--padding: 8px;font-family:var(--font-family);position:fixed;top:0;left:0;width:100%;max-width:200px;min-width:100px;min-height:100px;padding:var(--padding) 0;box-shadow:0 0 10px #00000040;background-color:var(--tr-odd-bg-color);border-radius:var(--border-radius);z-index:10;will-change:transform}.comfyui-easyuse-contextmenu-item-divider{height:1px;width:100%;background-color:var(--border-color);margin:var(--padding) 0}.comfyui-easyuse-contextmenu-item-content{height:var(--height);padding:0 12px;cursor:pointer;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-contextmenu-item-content span{font-size:11px;color:var(--input-text);display:-webkit-box;-webkit-line-clamp:1;overflow:hidden;word-break:break-all;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1}.comfyui-easyuse-contextmenu-item-content i{color:var(--input-text);margin-left:4px;display:block;width:0;height:0;border-width:4px 4px 0;border-style:solid;border-color:var(--descrip-text) transparent transparent;-webkit-transform:scaleY(.8) rotate(-90deg);-khtml-transform:scaleY(.8) rotate(-90deg);-moz-transform:scaleY(.8) rotate(-90deg);-ms-transform:scaleY(.8) rotate(-90deg);-o-transform:scaleY(.8) rotate(-90deg);transform:scaleY(.8) rotate(-90deg)}.comfyui-easyuse-contextmenu-item-content:hover{background:var(--theme-color)}.comfyui-easyuse-contextmenu-item.disabled .comfyui-easyuse-contextmenu-item-content span{color:var(--border-color);cursor:default}.comfyui-easyuse-contextmenu-item.disabled .comfyui-easyuse-contextmenu-item-content:hover{background:transparent}.comfyui-easyuse-contextmenu-submenu{font-family:var(--font-family);position:absolute;top:0;left:200px;max-width:200px;width:200px;min-width:100px;min-height:--height;padding:var(--padding) 0;box-shadow:0 0 10px #00000040;background-color:var(--tr-odd-bg-color);border-radius:var(--border-radius);z-index:10;will-change:transform}.comfyui-easyuse-contextmenu-model{position:relative}.comfyui-easyuse-contextmenu-model:hover img{display:block;opacity:1}.comfyui-easyuse-contextmenu-model img{position:absolute;z-index:1;right:-175px;top:-75px;width:150px;height:auto;display:none;filter:brightness(70%);-webkit-filter:brightness(70%);opacity:0;-webkit-transition:all .5s cubic-bezier(.55,0,.1,1);-khtml-transition:all .5s cubic-bezier(.55,0,.1,1);-moz-transition:all .5s cubic-bezier(.55,0,.1,1);-ms-transition:all .5s cubic-bezier(.55,0,.1,1);-o-transition:all .5s cubic-bezier(.55,0,.1,1);transition:all .5s cubic-bezier(.55,0,.1,1)}.comfyui-easyuse-slider{width:100%;height:100%;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-slider-item{height:inherit;min-width:25px;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;-webkit-flex-direction:column;-khtml-flex-direction:column;-moz-flex-direction:column;-ms-flex-direction:column;-o-flex-direction:column;flex-direction:column}.comfyui-easyuse-slider-item-input{height:15px;font-size:10px;color:var(--input-text)}.comfyui-easyuse-slider-item-label{height:15px;border:none;color:var(--descrip-text);font-size:8px}.comfyui-easyuse-slider-item-scroll{width:5px;height:calc(100% - 30px);background:var(--comfy-input-bg);border-radius:10px;position:relative}.comfyui-easyuse-slider-item-bar{width:10px;height:10px;background:linear-gradient(to bottom,var(--input-text),var(--descrip-text));border-radius:100%;box-shadow:0 2px 10px var(--bg-color);position:absolute;top:0;left:-2.5px;cursor:pointer;z-index:1}.comfyui-easyuse-slider-item-area{width:100%;border-radius:20px;position:absolute;bottom:0;background:var(--input-text);z-index:0}.comfyui-easyuse-slider-item.positive .comfyui-easyuse-slider-item-label{color:var(--success-color)}.comfyui-easyuse-slider-item.positive .comfyui-easyuse-slider-item-area{background:var(--success-color)}.comfyui-easyuse-slider-item.negative .comfyui-easyuse-slider-item-label{color:var(--error-color)}.comfyui-easyuse-slider-item.negative .comfyui-easyuse-slider-item-area{background:var(--error-color)}.comfyui-easyuse-map{height:100%;background:var(--comfy-menu-bg)}.comfyui-easyuse-map .p-splitter-gutter-handle{height:1px!important}.comfyui-easyuse-map-nodes{height:100%;position:relative}.comfyui-easyuse-map-nodes__header{position:absolute;z-index:2;top:0;left:0;width:100%;padding:.25rem 0 .25rem 1rem;height:2.7rem;background:var(--comfy-menu-bg);border-bottom:1px solid var(--border-color);display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px)}.comfyui-easyuse-map-nodes__header .title{font-size:13px;color:var(--input-text);font-weight:400;line-height:1.5;-webkit-user-select:none;user-select:none}.comfyui-easyuse-map-nodes__header .toolbar{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-map-nodes__header .toolbar .icon{font-size:.85rem;margin-left:.25rem;cursor:pointer}.comfyui-easyuse-map-nodes__content{position:relative;padding:2.7rem 0 0;height:100%;overflow:auto}.comfyui-easyuse-map-nodes__content dl .label{padding-left:1rem}.comfyui-easyuse-map-nodes__content ol,.comfyui-easyuse-map-nodes__content dl{list-style-type:none;padding:0;margin:0}.comfyui-easyuse-map-nodes__content ol .toolbar span,.comfyui-easyuse-map-nodes__content dl .toolbar span{font-size:13px}.comfyui-easyuse-map-nodes__content ol .toolbar span.pi-eye,.comfyui-easyuse-map-nodes__content dl .toolbar span.pi-eye{color:var(--input-text)}.comfyui-easyuse-map-nodes__content ol .toolbar span.pi-eye-slash,.comfyui-easyuse-map-nodes__content dl .toolbar span.pi-eye-slash{color:var(--descrip-text)}.comfyui-easyuse-map-nodes__content ol .toolbar span.pi-eye-slash.never,.comfyui-easyuse-map-nodes__content dl .toolbar span.pi-eye-slash.never{opacity:.5}.comfyui-easyuse-map-nodes__content .no_result{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;text-align:center}.comfyui-easyuse-map-nodes-group{position:relative;overflow:hidden;width:100%;height:2rem;cursor:default;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;pointer-events:auto}.comfyui-easyuse-map-nodes-group .left,.comfyui-easyuse-map-nodes-group .right{height:100%;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse-map-nodes-group .left{padding-left:.5rem;margin-right:.25rem;-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1}.comfyui-easyuse-map-nodes-group .icon{font-size:.85rem;margin-right:.25rem}.comfyui-easyuse-map-nodes-group .label{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;height:100%;width:100%;min-width:80px}.comfyui-easyuse-map-nodes-group .label span{font-size:14px;color:var(--input-text);font-weight:400;line-height:1.5;display:-webkit-box;-webkit-line-clamp:1;overflow:hidden;word-break:break-all;text-overflow:ellipsis;-webkit-box-orient:vertical;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.comfyui-easyuse-map-nodes-group:hover{background:var(--content-hover-bg)!important}.comfyui-easyuse-map-nodes-group.active{background:var(--theme-color)!important}.comfyui-easyuse-map-nodes-group.active .label{color:#fff;cursor:default}.comfyui-easyuse-map-nodes-group.never .label{color:var(--descrip-text);opacity:.4}.comfyui-easyuse-map-nodes-group.bypass .label{color:var(--descrip-text)}.comfyui-easyuse-map-nodes-node{height:2rem;cursor:default;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;position:relative;overflow:hidden;pointer-events:auto}.comfyui-easyuse-map-nodes-node .label{text-indent:.5rem;font-size:13px;color:var(--input-text);font-weight:400;line-height:1.5;-webkit-box-flex:1;-ms-flex:1;-webkit-flex:1;flex:1;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;margin-right:.25rem;height:2rem;line-height:2rem;width:100%;display:-webkit-box;-webkit-line-clamp:1;overflow:hidden;word-break:break-all;text-overflow:ellipsis;-webkit-box-orient:vertical}.comfyui-easyuse-map-nodes-node .label.error{color:var(--error-color)}.comfyui-easyuse-map-nodes-node:hover{background:var(--content-hover-bg)!important}.comfyui-easyuse-map-nodes-node.never .label{color:var(--descrip-text);opacity:.5}.comfyui-easyuse-map-nodes-node.bypass .label{color:#f0f;opacity:.5}.comfyui-easyuse-map-nodes .nodes .label{text-indent:1rem}.comfyui-easyuse-toolbar{border-radius:0 12px 12px 0;min-width:50px;height:24px;position:fixed;bottom:85px;left:0;display:flex;align-items:center;z-index:1000;background-color:var(--comfy-menu-bg);border:1px solid var(--bg-color);-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px)}.comfyui-easyuse-toolbar-icon{height:100%;padding:0 4px;display:flex;justify-content:center;align-items:center;font-size:12px;color:var(--input-text);transition:all .3s ease-in-out;cursor:pointer}.comfyui-easyuse-toolbar-icon svg{width:14px;height:14px}.comfyui-easyuse-toolbar-icon:hover.group{color:var(--warning-color)}.comfyui-easyuse-toolbar-icon:hover.rocket{color:var(--theme-color)}.comfyui-easyuse-toolbar-nodes-map{position:absolute;top:50px;left:10px;width:200px;border-radius:12px;min-height:100px;max-height:600px;color:var(--descrip-text);background-color:var(--comfy-menu-bg);border:1px solid var(--bg-color);-webkit-backdrop-filter:saturate(180%) blur(40px);-khtml-backdrop-filter:saturate(180%) blur(40px);-moz-backdrop-filter:saturate(180%) blur(40px);-ms-backdrop-filter:saturate(180%) blur(40px);-o-backdrop-filter:saturate(180%) blur(40px);backdrop-filter:saturate(180%) blur(40px);z-index:399;padding-top:0;overflow:hidden}.comfyui-easyuse-toolbar-nodes-map .no-result-placeholder-content{-webkit-transform:scale(.8);-khtml-transform:scale(.8);-moz-transform:scale(.8);-ms-transform:scale(.8);-o-transform:scale(.8);transform:scale(.8)}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes{min-height:100px;max-height:600px}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes__header:before{content:"…";position:absolute;left:.25rem;top:2.75rem;transform:translateY(-2rem) rotate(90deg);width:.5rem;height:.5rem;display:inline-block;overflow:hidden;line-height:5px;padding:3px 4px;cursor:move;vertical-align:middle;font-size:12px;font-family:sans-serif;letter-spacing:2px;color:var(--drag-text);z-index:3;text-shadow:1px 0 1px black}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes__header .title{cursor:move;padding-left:.25rem}.comfyui-easyuse-toolbar-nodes-map .comfyui-easyuse-map-nodes__content{max-height:calc(600px - 2.7rem)}.no-result-placeholder{display:flex;justify-content:center;align-items:center;height:100%}.no-result-placeholder-content{text-align:center;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-flex-direction:column;-khtml-flex-direction:column;-moz-flex-direction:column;-ms-flex-direction:column;-o-flex-direction:column;flex-direction:column;-webkit-justify-content:space-between;-khtml-justify-content:space-between;-moz-justify-content:space-between;-ms-justify-content:space-between;-o-justify-content:space-between;justify-content:space-between}.no-result-placeholder .p-card{background-color:transparent!important;box-shadow:none;text-align:center}.no-result-placeholder h3{color:var(--input-text);margin-bottom:.5rem}.no-result-placeholder p{color:var(--descrip-text);margin-bottom:1rem;margin-top:0}#comfyui-easyuse-components{position:absolute;top:0;left:0;z-index:3}.comfyui-easyuse{--p-datatable-header-cell-padding: .15rem 1rem;--p-datatable-body-cell-padding: .15rem 1rem;--p-primary-color: var(--theme-color-light)!important;--border-color-solid: var(--border-color);--border-radius: 8px}.comfyui-easyuse.dark-theme{--fg-color: #fff;--bg-color: #242427;--content-bg:#18181b;--content-fg:#fff;--content-hover-bg: #27272a;--comfy-menu-bg: rgba(24,24,27,.9);--comfy-input-bg: #242427;--input-text: #ffffff;--descrip-text: #71717a;--drag-text: #ccc;--error-text: #ff4444;--border-color: #3f3f46;--border-color-solid: #2a2a2e;--tr-even-bg-color: rgba(28,28,28,.9);--tr-odd-bg-color: rgba(19,19,19,.9)}.comfyui-easyuse ::-webkit-scrollbar{width:0em}.comfyui-easyuse ::-webkit-scrollbar-track{background-color:transparent}.comfyui-easyuse ::-webkit-scrollbar-thumb{background-color:transparent;border-radius:2px}.comfyui-easyuse ::-webkit-scrollbar-thumb:hover{background-color:transparent}.comfyui-easyuse body{font-family:var(--font-family)!important;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.comfyui-easyuse textarea{font-family:var(--font-family)!important}.comfyui-easyuse hr{border:1px solid var(--border-color)}.comfyui-easyuse .comfy-multiline-input{background-color:transparent;border:1px solid var(--border-color-solid);border-radius:var(--border-radius);padding:8px;font-size:12px}.comfyui-easyuse #comfy-settings-dialog{border:1px solid var(--border-color);background:transparent;-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%);box-shadow:none}.comfyui-easyuse .comfy-modal{border:1px solid var(--border-color);box-shadow:none;-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .cm-title{background-color:transparent!important}.comfyui-easyuse .cm-notice-board{border-radius:10px!important;border:1px solid var(--border-color)!important}.comfyui-easyuse .cm-menu-container{margin-bottom:50px!important}.comfyui-easyuse .cn-manager-custom_milk_white .tg-column-name,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-column-name{color:var(--input-text)}.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-message,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-message{text-align:center;color:var(--descrip-text)!important}.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-frame .tg-cell,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-frame .tg-cell{color:var(--input-text)}.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-frame .cn-node-name a,.comfyui-easyuse .cn-manager-custom_milk_white .tg-body-frame .cmm-node-name a,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-frame .cn-node-name a,.comfyui-easyuse .cmm-manager-custom_milk_white .tg-body-frame .cmm-node-name a{color:var(--theme-color)!important}.comfyui-easyuse .comfy-menu{border-radius:16px;box-shadow:0 0 1px var(--descrip-text);-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .comfy-menu button.comfy-settings-btn{font-size:12px}.comfyui-easyuse .comfy-menu-btns{margin-bottom:4px}.comfyui-easyuse .comfy-menu button,.comfyui-easyuse .comfy-modal button{font-size:14px;padding:4px 0;margin-bottom:4px}.comfyui-easyuse .comfy-menu-btns button,.comfyui-easyuse .comfy-list-actions button{font-size:10px}.comfyui-easyuse .comfy-menu>button,.comfyui-easyuse .comfy-menu-btns button,.comfyui-easyuse .comfy-menu .comfy-list button,.comfyui-easyuse .comfy-modal button{border-width:1px}.comfyui-easyuse #comfy-dev-save-api-button{justify-content:center}.comfyui-easyuse #queue-button{position:relative;overflow:hidden;min-height:30px;z-index:1}.comfyui-easyuse #queue-button:after{clear:both;content:attr(data-attr);background:green;color:#fff;width:var(--process-bar-width);height:100%;position:absolute;top:0;left:0;z-index:0;text-align:center;display:flex;justify-content:center;align-items:center}.comfyui-easyuse #shareButton{background:linear-gradient(to left,var(--theme-color),var(--theme-color-light))!important;color:#fff!important}.comfyui-easyuse .litegraph.litecontextmenu{--height: 24px;--padding: 6px;font-family:var(--font-family);padding:var(--padding) 0;border-radius:var(--border-radius);-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .litegraph.litecontextmenu .litemenu-title{padding:var(--padding)}.comfyui-easyuse .litegraph.litecontextmenu>div:first-child.litemenu-title{margin-top:-6px}.comfyui-easyuse .litegraph.litecontextmenu .submenu{height:var(--height);line-height:var(--height);padding:0 18px 0 12px;margin:0;background:transparent!important}.comfyui-easyuse .litegraph.litecontextmenu .submenu.has_submenu{border-right:none;position:relative}.comfyui-easyuse .litegraph.litecontextmenu .submenu.has_submenu:after{content:"";display:block;width:0;height:0;border-width:4px 4px 0;border-style:solid;border-color:var(--input-text) transparent transparent;transform:translateY(-50%) rotate(-90deg);top:50%;position:absolute}.comfyui-easyuse .litegraph.litecontextmenu .submenu.separator{height:1px;width:100%;background-color:var(--border-color)!important;margin:var(--padding) 0;border:none}.comfyui-easyuse .litegraph.litecontextmenu .submenu:last-child.separator{display:none}.comfyui-easyuse .litegraph.litecontextmenu .submenu:hover:not(.separator){background:var(--theme-color)!important}.comfyui-easyuse .litegraph.lite-search-item{background-color:var(--comfy-input-bg)!important;filter:brightness(100%)}.comfyui-easyuse .litegraph.lite-search-item:hover{filter:brightness(120%);color:var(--input-text)}.comfyui-easyuse .graphdialog{-webkit-backdrop-filter:blur(8px) brightness(120%);backdrop-filter:blur(8px) brightness(120%)}.comfyui-easyuse .graphdialog button{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center}.comfyui-easyuse .comfyui-menu{background:var(--comfy-menu-bg);border-bottom:1px solid var(--bg-color)}.comfyui-easyuse .side-tool-bar-container{background:var(--comfy-menu-bg);border-right:1px solid var(--bg-color)}.comfyui-easyuse .comfy-modal-content{width:100%}.comfyui-easyuse-poseEditor{display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-justify-content:center;-khtml-justify-content:center;-moz-justify-content:center;-ms-justify-content:center;-o-justify-content:center;justify-content:center;-webkit-align-items:center;-khtml-align-items:center;-moz-align-items:center;-ms-align-items:center;-o-align-items:center;align-items:center;text-align:center;font-size:18px;line-height:1.5}')),document.head.appendChild(e)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}}(); +var e;import{$ as s,l as t,a as l,t as a,b as o,s as n,g as i,c as r,u,N as d,j as c,d as p,e as v,f as m,h as g}from"./assets/extensions-DCFcU_BW.js";import{r as y,w as h,e as f,b as A,c as w,I as S,d as x,F as E,C as M,J as b,K as C,L as _,M as k,z as B,G as I,o as N,N as H,D as Q,O as z,E as j,P as R,x as D,Q as L,R as Z,S as Y}from"./assets/vue-DjzFgvDF.js";import{d as O,s as V,a as P,b as G,c as W,T as U,e as F}from"./assets/vendor-DT1J-jWa.js";import{c as T}from"./assets/lodash-CZi7izHi.js";import{P as X}from"./assets/primevue-BSs2m5Wu.js";import"./assets/primeuix-Be3xdh47.js";const J=O("graphStore",{state:e=>({selectors:[],selectors_styles:{},seg_selectors:[],slider_controls:[]}),actions:{setSelectors(e){this.selectors=T(e)},setStyles(e,s){this.selectors_styles[e]||(this.selectors_styles[e]=s)},setSegSelectors(e){this.seg_selectors=T(e)},setSliderControls(e){this.slider_controls=T(e)}}}),q=["data-id"],K=[x("i",{class:"mdi mdi-trash-can"},null,-1)],$=x("i",{class:"mdi mdi-magnify"},null,-1),ee=["placeholder"],se=["onMouseenter","onMouseleave"],te=["onClick"],le=["name","checked"],ae=["src"],oe={key:0},ne=x("span",{class:"comfyui-easyuse-success"},"positive:",-1),ie={key:1},re=x("span",{class:"comfyui-easyuse-error"},"negative:",-1),ue="comfyui-easyuse-selector",de={__name:"stylesSelector",props:{id:{type:String|Number,default:""},type:{type:String,default:""},selectedStyles:{type:Array,default:[]},show:{type:Boolean,default:!1}},emits:["chooseStyle"],setup(e,{emit:o}){const n=e,i=J(),{selectors_styles:r}=V(i),u=y([]);h((e=>n.type),(async e=>{u.value=[],e&&await(async e=>{if(r.value[n.type])return!0;const t=await l.fetchApi(`/easyuse/prompt/styles?name=${e}`);if(200===t.status){let e=(await t.json()).map(((e,s)=>(e.index=s,e)));return await i.setStyles(n.type,e),!0}return a.error(s("Get styles list Failed")),!1})(e)&&c()}),{immediate:!0});const d=o,c=e=>{const s=n.selectedStyles,t=T(r.value[n.type]);u.value=t.sort(((e,s)=>e.index-s.index)).sort(((e,t)=>s.includes(t.name)-s.includes(e.name)))},p=y(""),v=e=>e.toLowerCase(),m=f({}),g=e=>{m.src="",m.name="",m.positive="",m.negative=""},N=async e=>{const s=await l.fetchApi(`/easyuse/prompt/styles/image?name=${e}&styles_name=${n.type}`);if(200===s.status){const t=await s.text();if(t.startsWith("http"))return t;return`/easyuse/prompt/styles/image?name=${e}&styles_name=${n.type}`}},H=e=>{e.target.src="data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QNLaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA5LjEtYzAwMSA3OS4xNDYyODk5Nzc3LCAyMDIzLzA2LzI1LTIzOjU3OjE0ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoMjAyMzA5MDUubS4yMzE2IDk3OWM4NmQpICAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjA3NEU1QzNCNUJBMTFFRUExMUVDNkZDRjI0NzlBN0QiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjA3NEU1QzRCNUJBMTFFRUExMUVDNkZDRjI0NzlBN0QiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGMDc0RTVDMUI1QkExMUVFQTExRUM2RkNGMjQ3OUE3RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGMDc0RTVDMkI1QkExMUVFQTExRUM2RkNGMjQ3OUE3RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEAAYEBAQFBAYFBQYJBgUGCQsIBgYICwwKCgsKCgwQDAwMDAwMEAwODxAPDgwTExQUExMcGxsbHB8fHx8fHx8fHx8BBwcHDQwNGBAQGBoVERUaHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH//AABEIAIAAgAMBEQACEQEDEQH/xACLAAEAAgMBAQEAAAAAAAAAAAAABAUCAwYBBwgBAQADAQEBAAAAAAAAAAAAAAABAgMEBQYQAAEEAgECAwUHAwUAAAAAAAEAAgMEEQUhEgYxEwdBYSIyFFFxgVJyIxWRoTOxwdFiJBEBAAICAQQBBAIDAAAAAAAAAAECEQMxIUESBBOB0SIyUXGCIwX/2gAMAwEAAhEDEQA/AP1SgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICDXJYgj+d4afsVopM8KWvEcy8it1pXdMcjXO/Lnn+im2u0cwV2VniW1UXEBAQEBAQEBAQEBAQRNlc+mgyDh7zhv+5WunX5Sw37fCHM2dh48r06ank7N6rn2Ja7qa4hw5BBwQV010uK+/DsO29v/J68SOI86Jxjl95HIP4gryPc0fHfHaXu+j7Py68zzHSVquV2iAgICAgICAgICDyTr6HdHz4PTnwypjnqic46OauNbY6mGX99p+L8w9xaeV6OufHt0eXtr59M9VFb194E9LmuH3kf6rv17avO2ets7YVcuuuk/uOa3PgBlxP4BdMbq9nLPqbJ5xDbSM9azFXpyujuSO+Bo5kcf0NPyj25We2YtEzaPxdfr6519Kz+UvqEIlELBKQZQ0eYRwC7HOPxXzVsZ6cPpK5x15ZKEiAgICAgICAgICCNc1tG40CzA2XHg4j4h9zhyFpr22p+s4Z7NNL/ALRlTX+1dVFBJOJrcTI2lxZHYcBx+sldWv3bzOMVn6fZy39OkRnNo+v3aoOx9JOxks8tqwHDPS+1IW8+IzGWZVrf9DZHSMR/j9yvo656zMz9V1rdLqdYwsoVIqwd87mNAc79Tvmd+JXJt332ftMy6temlP1jCasmggICAgICAgICAgwlmiib1SPDB7zhWrWZ4VtaI5QXb2l5ojYHvLjjIGB/dbR61sZlhPtVziFb3PYdd0luCvAZbXludVZ1huZQPgyTx4/atvWj4rxaZ6d/6Ye1/t1zSI6zx/bzti5YqaOpBeg8u41n/oa14cA4ccH7lPs1jZebVn8eyPUtOrXFbR+XdYx9xa90pjeXROaSCXDj+oysZ9S+Mx1bR7uvOJ6LGOWKVgfG8PafAtOQueazHLqraJjMMlCRAQEBAQEBAQRLNp4HTFx/2/4WtKR3Y32T2Udl8j3knk/aeSu6kREPPvaZlpY3DmyY8DyrzPZWv8tkvmFv7bg12RyR1DGeeMj2KnjE9JaeUx1hi1sgaet/U7JIOMcE8Dj7FMREcK2zPKMasr5XO6fmOVt5xEOadVplYU45IAOhxa72kLm2TFuXXqrNeF1WtlwDZeHfmHguO+vHDupszylLJsICAgICAg8cMjCQiYR5IVpFmc1Q5qLXHPgfbhbV2MLaYlqNQAYA4V/kV+PDA1fcp81fjYurtYMu4CmLZRNYhtZWBAI8CqzdaKN8df3LObtIokxwe5ZzZrFUloIGFnLWHqhIgICAgICAgxMbSpyjDAwAq3kr4MTWCnzR4MX02PGHDISNmETqieWba7QABwB4KJumKNgjaFXK0VZYChYQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEHzvuv1G7k1W9s6/Xamtaq15oaonmnsCR008HntaI4K8/s4HOeEGXZXqTud7uqtG7r6kNa5HdMU9aaw9zZde+FkrHsnr1+M2MZBPIKDRe9cO2K2mjs/V0m7X61lWzq32W+ZFEbfkSSO4B+GL9zw4QWm99TqFVmjsaSu7fUtxeNM2aTmSMBbHI9zWHqHVJlnDTxjPKCJL6sea502t1D7Ouhr0rNqxNM2CSNuwnkgjAi6ZOotdEc/Egibf1j/j+7JNL9DWdWg84TWn2ywtdFKyMZb5Tg0nLyG55x48IJ3bXqe/ea/a26dFtyTXtldDUqyOdNL5VqaDHS5gwXRxMe3xz1Y9iDKP1Sa7uefUnR7TyYqUVoEU5jY6pJZIz1RY4ZiMYd7TkexBA749Wr2gtCKlrIpGs17NjK29LLWmPmMsyiFkbIZsPEdKQu6y0eAQWdD1E2L93W1tzRyCDY3paev2NaxVlhIjidMfMb5vmse1kbi9pZ7MeKDt0BAQEBAQfEPU+lFY2++q2K1uSSezTnrReVsTTmiZVYHOd9LVuQyubIwANkbxz4FA7FsQ0NrrLNXX7N0eo1+3darGDYPjb5j6prxVRajjDetsRAjj4yM4CDre2uxO7q2hqtm7nua6w9rp5tfXgoSxwyTOMr42PlrPe4Nc8jJJQRDb3Oz1fYFrcV7As0mu3u7nbWkBZ9LSfG5nlxs/yySWRiNozwcBBx9EXadGTXz62+LG41+jZS6adhzS6vfnlkEjgzEZax7T8ePFBu3nbPdUXqJZsw6S5cqbCW1YdIY2lxhhfEGMjfHtoG9HxucwPEZy4/A7kMC87aq2Kmv7mdvxuqGmklFjUU4G2Yp21rdyW00t+kJkFl88pY9vDgwNDvEoK9np73FBcHdkrt2+rZd5FjQx7O0b8WvbzDKZhN1SSse573QdeAHkN+Ichj3p2rBvZq9vUnY2tcNQPqpZYZpJ44GxXqzHdVlzZZpib73mLHViI85c1BZ6OpsIe/6/XSuntevdsz6+8+pI0/yM1dtWVr2Z644P8rmyuj6S53jxkh9aQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBB/9k="};return(l,a)=>(A(),w("div",{class:E(ue+` ${ue}-styles`),"data-id":e.id,onMouseleave:g},[u.value.length>0&&e.show?(A(),w(S,{key:0},[x("div",{class:E(ue+"__header")},[x("div",{class:E(ue+"__header_button"),onClick:a[0]||(a[0]=e=>(d("chooseStyle",[]),void(p.value="")))},K,2),x("div",{class:E(ue+"__header_search")},[$,M(x("textarea",{class:"search","onUpdate:modelValue":a[1]||(a[1]=e=>p.value=e),dir:"ltr",rows:1,placeholder:C(s)("Type here to search styles ...")},null,8,ee),[[b,p.value]])],2)],2),x("div",{class:E(ue+"__content"),onMouseleave:c},[(A(!0),w(S,null,_(u.value,((s,l)=>(A(),w("div",{class:E(ue+"-item"),key:l,onMouseenter:e=>(async e=>{if(!e.imageSrc){if(e.imageLoading)return;e.imageLoading=!0;const s=await N(e.imgName).finally((()=>e.imageLoading=!1));e.imageSrc=s}m.name="zh-CN"==t&&e.name_cn?e.name_cn:e.name,m.positive=e.prompt,m.negative=e.negative_prompt,m.src=e.imageSrc})(s),onMouseleave:k((e=>g()),["stop"])},[x("span",{class:E([ue+"-item__tag",{hide:!(e.selectedStyles.includes(s.name)||-1!=v(s.name).indexOf(v(p.value))||s.name_cn&&-1!=v(s.name_cn).indexOf(v(p.value)))}]),onClick:e=>(e=>{let s=n.selectedStyles;s.includes(e.name)?s=s.filter((s=>s!==e.name)):s.push(e.name),d("chooseStyle",s)})(s)},[x("input",{type:"checkbox",name:s.name,checked:e.selectedStyles.includes(s.name)},null,8,le),x("span",null,B("zh-CN"==C(t)&&s.name_cn?s.name_cn:s.name),1)],10,te)],42,se)))),128))],34),(null==m?void 0:m.src)?(A(),w("div",{key:0,class:E(ue+"-preview")},[x("img",{src:m.src,ref:"image",alt:"preview",onError:H},null,40,ae),x("div",{class:E(ue+"-preview__text")},[x("b",null,B(m.name),1),x("div",{class:E(ue+"-preview__prompt")},[m.positive?(A(),w("h6",oe,[ne,x("span",null,B(m.positive),1)])):I("",!0),m.negative?(A(),w("h6",ie,[re,x("span",null,B(m.negative),1)])):I("",!0)],2)],2)],2)):I("",!0)],64)):I("",!0)],42,q))}},ce=["data-id"],pe=["onClick"],ve=["name","checked"],me="comfyui-easyuse-selector",ge={__name:"segSelector",props:{id:{type:String|Number,default:""},type:{type:String,default:""},selected:{type:Array,default:[]},show:{type:Boolean,default:!1}},emits:["select"],setup(e,{emit:t}){const l=e,a=y([]);h((e=>l.type),(async e=>{switch(e){case"selfie_multiclass_256x256":a.value=["Background","Hair","Body","Face","Clothes","Others"];break;case"human_parsing_lip":a.value=["Background","Hat","Hair","Glove","Sunglasses","Upper-clothes","Dress","Coat","Socks","Pants","Jumpsuits","Scarf","Skirt","Face","Left-arm","Right-arm","Left-leg","Right-leg","Left-shoe","Right-shoe"];break;case"human_parts (deeplabv3p)":a.value=["Background","Face","Hair","Glasses","Top-clothes","Bottom-clothes","Torso-skin","Left-arm","Right-arm","Left-leg","Right-leg","Left-foot","Right-foot"]}}),{immediate:!0});const o=t;return(t,n)=>{var i;return A(),w("div",{class:E(me+` ${me}-seg`),"data-id":e.id},[(null==(i=a.value)?void 0:i.length)>0&&e.show?(A(!0),w(S,{key:0},_(a.value,((t,a)=>(A(),w("div",{class:E(me+"-item"),key:a},[x("span",{class:E(me+"-item__tag"),onClick:e=>(e=>{let s=T(l.selected);s.includes(e)?s=s.filter((s=>s!==e)):s.push(e),o("select",s)})(a)},[x("input",{type:"checkbox",name:t,checked:e.selected.includes(a)},null,8,ve),x("span",null,B(C(s)(t)),1)],10,pe)],2)))),128)):I("",!0)],10,ce)}}},ye=["data-id"],he=["onMousedown","onDblclick"],fe="comfyui-easyuse-slider",Ae="ipadapter layer weights",we={__name:"sliderControl",props:{id:{type:String|Number,default:""},mode:{type:String,default:""},type:{type:String,default:""},values:{type:Array,default:[]},show:{type:Boolean,default:!1}},emits:["changeValues","showSlider"],setup(e,{emit:s}){const t=e,l=s,a=(e,s,t)=>(e-s)/(t-s)*100,o=(e,s,l=void 0)=>{if(t.mode===Ae){let t={3:2.5,6:1}[s]||0;return{default:12==e?t:0,min:-1,max:3,step:.05,value:void 0!==l?l:12==e?t:0,top:void 0!==l?100-a(l,-1,3)+"%":null,height:void 0!==l?a(l,-1,3)+"%":null}}};h((e=>t.mode),(async(e,s)=>{var a;if(e!==s&&e===Ae)if(!s&&(null==(a=t.values)?void 0:a.length)>0){const e=t.values.map((e=>{const s=e.split(":");return o(t.values.length,s[0],parseFloat(s[1]))}));await l("changeValues",e)}else{let e="sd1"==t.type?16:12,s=Array.from({length:e},((s,t)=>o(e,t)));await l("changeValues",s)}l("showSlider")}),{immediate:!0}),h((e=>t.type),((e,s)=>{if(e!=s&&t.mode==Ae){let e="sd1"==t.type?16:12,s=Array.from({length:e},((s,t)=>o(e,t)));l("changeValues",s)}}));const n=y(null),i=y(null);return N((()=>{document.onmouseup=e=>document.onmousemove=null})),(s,o)=>{var r;return A(),w("div",{class:E(fe),"data-id":e.id},[(null==(r=e.values)?void 0:r.length)>0&&e.show?(A(!0),w(S,{key:0},_(e.values,((s,o)=>(A(),w("div",{class:E([fe+"-item",{positive:3==o&&"sdxl"==e.type&&e.mode==Ae},{negative:6==o&&"sdxl"==e.type&&e.mode==Ae}]),key:o},[x("div",{class:E(fe+"-item-input")},B(s.value),3),x("div",{class:E(fe+"-item-scroll"),ref_for:!0,ref_key:"scroll",ref:n},[x("div",{class:E(fe+"-item-bar"),ref_for:!0,ref_key:"bar",ref:i,style:H({top:s.top||100-a(s.default,s.min,s.max)+"%"}),onMousedown:e=>((e,s,a)=>{let o=e||window.event,r=n.value[a],u=i.value[a],d=T(t.values),c=o.clientY-u.offsetTop;document.onmousemove=e=>{let t=(e||window.event).clientY-c;t<0?t=0:t>r.offsetHeight-u.offsetHeight&&(t=r.offsetHeight-u.offsetHeight);let o=(s.max-s.min)/s.step,n=(r.offsetHeight-u.offsetHeight)/o;t=Math.round(t/n)*n;const i=Math.floor(t/(r.offsetHeight-u.offsetHeight)*100)+"%",p=Math.floor((r.offsetHeight-u.offsetHeight-t)/(r.offsetHeight-u.offsetHeight)*100)+"%",v=parseFloat(parseFloat(s.max-(s.max-s.min)*(t/(r.offsetHeight-u.offsetHeight))).toFixed(2));d[a]={...d[a],top:i,height:p,value:v},l("changeValues",d),window.getSelection?window.getSelection().removeAllRanges():document.selection.empty()}})(e,s,o),onDblclick:e=>((e,s,a)=>{let o=T(t.values);o[a]={...o[a],top:null,height:null,value:s.default},l("changeValues",o)})(0,s,o)},null,46,he),x("div",{class:E(fe+"-item-area"),style:H({height:s.height||a(s.default,s.min,s.max)+"%"})},null,6)],2),x("div",{class:E(fe+"-item-label")},[x("span",null,B(s.label),1)],2)],2)))),128)):I("",!0)],8,ye)}}},Se={__name:"index",setup(e){const s=J(),{selectors:t,seg_selectors:l,slider_controls:a}=V(s),u=y({}),d=async e=>{var l,a,o,r,d,c,p;await n(1);const v=i(e,"styles"),m=(null==(l=e.properties.values)?void 0:l.length)>0?e.properties.values:[];let g=T(t.value);g.push({id:e.id,type:v.value,value:m,show:!1});const y=g.length-1;await s.setSelectors(g),(null==(a=e.flags)?void 0:a.collapsed)&&e.collapse();let h=null==(d=null==(r=null==(o=u.value[e.id])?void 0:o._)?void 0:r.vnode)?void 0:d.el;if(!h)return;let f=e.addDOMWidget("select_styles","btn",h);e.properties.values||e.setProperty("values",[]),g[y].show=!0,await s.setSelectors(g);let A=v.value;Object.defineProperty(v,"value",{set:t=>{A=t,g[y].type=t,e.properties.values=[],g[y].value=[],s.setSelectors(g)},get:e=>A}),Object.defineProperty(f,"value",{set:e=>{setTimeout((t=>{g[y].value=e.split(","),s.setSelectors(g)}),150)},get:s=>{var l,a;return e.properties.values=(null==(a=null==(l=t.value)?void 0:l[y])?void 0:a.value)||[],e.properties.values.join(",")}}),((null==(c=e.size)?void 0:c[0])<150||(null==(p=e.size)?void 0:p[1])<150)&&e.setSize([425,500]);const w=e.onRemoved;e.onRemoved=function(){if(w&&(null==w||w.apply(this,arguments)),void 0!==t.value.findIndex((s=>s.id==e.id))){let e=T(t.value);e.splice(y,1),s.setSelectors(e)}return w}},c=y({}),p=async e=>{var t,a,o,u,d;await n(1);const p=i(e,"method"),v=(null==(t=e.properties.values)?void 0:t.length)>0?e.properties.values:[];let m=T(l.value);m.push({id:e.id,type:p.value,value:v,show:!1});const g=m.length-1;await s.setSegSelectors(m),(null==(a=e.flags)?void 0:a.collapsed)&&e.collapse();let y=null==(d=null==(u=null==(o=c.value[e.id])?void 0:o._)?void 0:u.vnode)?void 0:d.el;if(!y)return;let h=e.addDOMWidget("mask_components","btn",y);e.properties.values||e.setProperty("values",[]),m[g].show=!0,await s.setSegSelectors(m);let f=p.value;Object.defineProperty(p,"value",{set:t=>{f=t,m[g].type=t,e.properties.values=[],m[g].value=[],r(e,i(e,"confidence"),"selfie_multiclass_256x256"===f),e.setSize([300,"selfie_multiclass_256x256"===f?260:400]),s.setSegSelectors(m)},get:e=>f}),Object.defineProperty(h,"value",{set:e=>{setTimeout((t=>{m[g].value=e.split(","),s.setSegSelectors(m)}),150)},get:s=>{var t;return e.properties.values=(null==(t=l.value)?void 0:t[g].value)||[],e.properties.values.join(",")}}),r(e,i(e,"confidence"),"selfie_multiclass_256x256"===f),e.setSize([300,"selfie_multiclass_256x256"===f?260:500]);const A=e.onRemoved;e.onRemoved=function(){if(A&&(null==A||A.apply(this,arguments)),void 0!==l.value.findIndex((s=>s.id==e.id))){let e=T(l.value);e.splice(g,1),s.setSegSelectors(e)}return A}},v=y({}),m=async e=>{var t,l,o,r,u;await n(1);const d=i(e,"mode"),c=i(e,"model_type"),p=(null==(t=e.properties.values)?void 0:t.length)>0?e.properties.values:[];(null==(l=e.flags)?void 0:l.collapsed)&&e.collapse();let m=T(a.value);m.push({id:e.id,type:c.value,mode:d.value,value:p,show:!1});const g=m.length-1;await s.setSliderControls(m);let y=null==(u=null==(r=null==(o=v.value[e.id])?void 0:o._)?void 0:r.vnode)?void 0:u.el;if(!y)return;let h=e.addDOMWidget("values","btn",y);e.properties.values||e.setProperty("values",[]),Object.defineProperty(h,"value",{set:function(){},get:s=>{var t;const l=(null==(t=a.value)?void 0:t[g].value)||[];return e.properties.values=l.map(((e,s)=>`${s}:${e.value}`)),e.properties.values.join(",")}}),e.setSize("sdxl"==c.value?[375,320]:[455,320]),c.callback=t=>{m=T(a.value),m[g].type!=t&&(e.setSize("sdxl"==t?[375,320]:[455,320]),m[g].value=[],m[g].type=t,s.setSliderControls(m))};const f=e.onRemoved;e.onRemoved=function(){if(f&&(null==f||f.apply(this,arguments)),void 0!==a.value.findIndex((s=>s.id==e.id))){let e=T(a.value);e.splice(g,1),s.setSliderControls(e)}return f}};return N((e=>{o.registerExtension({name:"Comfy.EasyUse.Components",async beforeRegisterNodeDef(e,s){const t=e.prototype.onNodeCreated;"easy stylesSelector"==s.name&&(e.prototype.onNodeCreated=async function(){return t&&(null==t||t.apply(this,arguments)),await d(this),t}),"easy humanSegmentation"==s.name&&(e.prototype.onNodeCreated=async function(){return t&&(null==t||t.apply(this,arguments)),await p(this),t}),"easy sliderControl"==s.name&&(e.prototype.onNodeCreated=async function(){return t&&(null==t||t.apply(this,arguments)),await m(this),t}),"easy poseEditor"==s.name&&(e.prototype.onNodeCreated=async function(){t&&(null==t||t.apply(this,arguments));const e=document.createElement("div");return e.className="comfyui-easyuse-poseEditor",e.innerHTML='
This node is about to be removed, you can use ComfyUI_Custom_Nodes_AlekPet to replace it.
',this.addDOMWidget("editor","btn",e),t})}})})),(e,o)=>(A(),w(S,null,[(A(!0),w(S,null,_(C(t),((e,l)=>(A(),Q(de,{ref_for:!0,ref:s=>{s&&(u.value[e.id]=s)},type:e.type,key:l,id:e.id,show:e.show,selectedStyles:e.value,onChooseStyle:e=>((e,l)=>{let a=T(t.value);a[l].value=e,s.setSelectors(a)})(e,l)},null,8,["type","id","show","selectedStyles","onChooseStyle"])))),128)),(A(!0),w(S,null,_(C(l),((e,t)=>(A(),Q(ge,{ref_for:!0,ref:s=>{s&&(c.value[e.id]=s)},type:e.type,key:t,id:e.id,show:e.show,selected:e.value,onSelect:e=>((e,t)=>{let a=T(l.value);a[t].value=e,s.setSegSelectors(a)})(e,t)},null,8,["type","id","show","selected","onSelect"])))),128)),(A(!0),w(S,null,_(C(a),((e,t)=>(A(),Q(we,{ref_for:!0,ref:s=>{s&&(v.value[e.id]=s)},type:e.type,key:t,id:e.id,show:e.show,mode:e.mode,values:e.value,onChangeValues:e=>((e,t)=>{let l=T(a.value);l[t].value=e,s.setSliderControls(l)})(e,t),onShowSlider:e=>(e=>{let t=T(a.value);t[e].show=!0,s.setSliderControls(t)})(t)},null,8,["type","id","show","mode","values","onChangeValues","onShowSlider"])))),128))],64))}},xe={class:"no-result-placeholder"},Ee={class:"no-result-placeholder-content"},Me={key:0},be={__name:"noResultsPlaceholder",props:{icon:{type:String,default:"",required:!1},iconSize:{type:String,default:"3rem",required:!1},title:{type:String,required:!0},message:{type:String,required:!1},buttonLabel:{type:String,default:"",required:!1}},emits:["action"],setup:e=>(s,t)=>(A(),w("div",xe,[z(C(G),null,{content:j((()=>[x("div",Ee,[x("i",{class:E(e.icon),style:H({"font-size":e.iconSize,"margin-bottom":".5rem"})},null,6),x("h3",null,B(e.title),1),e.message?(A(),w("p",Me,B(e.message),1)):I("",!0),e.buttonLabel?(A(),Q(C(P),{key:1,label:e.buttonLabel,onClick:t[0]||(t[0]=e=>s.$emit("action")),class:"p-button-text"},null,8,["label"])):I("",!0)])])),_:1})]))},Ce={class:"left flex-1"},_e={key:1,class:"edit"},ke={key:2,class:"label"},Be={class:"right toolbar"},Ie={key:0,class:"nodes"},Ne={__name:"group",props:{item:{type:Object,default:{}}},emits:["mousedown","mouseup","changeMode"],setup(e){const s=e,t=u(),l=y(!1),a=y(null),n=y(""),i=e=>{var l,a;let n=s.item;if(!(null==(l=n.info)?void 0:l.is_edit)&&(null==(a=n.children)?void 0:a.length)>0){let e=o.canvas.graph._groups.find((e=>e.pos[0]==n.info.pos[0]&&e.pos[1]==n.info.pos[1]));e&&(e.show_nodes=!e.show_nodes,t.setGroups(o.canvas.graph._groups))}},r=async()=>{let e=s.item,a=o.canvas.graph._groups.find((s=>s.pos[0]==e.info.pos[0]&&s.pos[1]==e.info.pos[1]));a?(a.is_edit=!1,a.title=n.value,await t.setGroups(o.canvas.graph._groups),l.value=!1):l.value=!1};return(u,c)=>{var p,v,m;return A(),w(S,null,[x("div",{class:E("comfyui-easyuse-map-nodes-group"),onClick:i},[x("div",Ce,[e.item.children?(A(),w("i",{key:0,class:E(["icon",e.item.info.show_nodes?"pi pi-folder-open":"pi pi-folder"]),style:H({color:e.item.info.color})},null,6)):I("",!0),(null==(p=e.item.info)?void 0:p.is_edit)?(A(),w("div",_e,[z(C(W),{ref_key:"modifyRef",ref:a,modelValue:n.value,"onUpdate:modelValue":c[0]||(c[0]=e=>n.value=e),variant:"outline",size:"small",type:"text",onBlur:r,onKeydown:[R(r,["enter"]),R(r,["esc"])],style:{width:"100%"}},null,8,["modelValue"])])):(A(),w("div",ke,[x("span",{onDblclick:c[1]||(c[1]=k((i=>(async()=>{var e,i;if(l.value)return;let r=s.item,u=o.canvas.graph._groups.find((e=>e.pos[0]==r.info.pos[0]&&e.pos[1]==r.info.pos[1]));u&&(u.is_edit=!u.is_edit,n.value=u.is_edit?r.info.title:"",await t.setGroups(o.canvas.graph._groups),l.value=!0,null==(i=null==(e=a.value)?void 0:e[0])||i.$el.focus())})(e.item)),["stop"]))},B(e.item.info.title),33)]))]),x("div",Be,[(null==(v=e.item.children)?void 0:v.length)>0?(A(),Q(C(P),{key:0,size:"small",icon:e.item.children.find((e=>e.mode==C(d).ALWAYS))?"pi pi-eye":"pi pi-eye-slash",text:"",rounded:"",severity:"secondary",onClick:c[2]||(c[2]=k((e=>u.$emit("changeMode")),["stop"])),onMousedown:c[3]||(c[3]=k((e=>u.$emit("mousedown")),["stop"])),onMouseup:c[4]||(c[4]=k((e=>u.$emit("mouseup")),["stop"]))},null,8,["icon"])):I("",!0)])]),(null==(m=e.item.children)?void 0:m.length)>0&&e.item.info.show_nodes?(A(),w("div",Ie,[D(u.$slots,"default")])):I("",!0)],64)}}},He={key:1,class:"label error"},Qe={class:"right toolbar"},ze={__name:"node",props:{node:{type:Object,default:{}}},emits:["mousedown","mouseup","changeMode"],setup:e=>(s,t)=>(A(),w("div",{draggable:!1,class:E(["comfyui-easyuse-map-nodes-node",{never:void 0!==e.node.mode&&e.node.mode==C(d).NEVER},{bypass:void 0!==e.node.mode&&e.node.mode==C(d).BYPASS}])},[void 0!==e.node.title?(A(),w("span",{key:0,class:"label",onDblclick:t[0]||(t[0]=k((s=>C(c)(e.node.id)),["stop"]))},B(e.node.title),33)):(A(),w("span",He,B(e.node.type),1)),x("div",Qe,[z(C(P),{size:"small",icon:e.node.mode==C(d).ALWAYS?"pi pi-eye":"pi pi-eye-slash",text:"",rounded:"",severity:"secondary",onClick:t[1]||(t[1]=k((e=>s.$emit("changeMode")),["stop"])),onMousedown:t[2]||(t[2]=k((e=>s.$emit("mousedown")),["stop"])),onMouseup:t[3]||(t[3]=k((e=>s.$emit("mouseup")),["stop"]))},null,8,["icon"])])],2))},je={class:"title"},Re={class:"toolbar"},De={key:0},Le=["onDragstart","onDragend","onDragover"],Ze={key:1,class:"no_result",style:{height:"100%"}},Ye="comfyui-easyuse-map-nodes",Oe={__name:"nodesMap",emits:["handleHeader"],setup(e){const t=u(),{groups_nodes:l,groups:n}=V(t),i=y(!1),r=e=>{i.value=!i.value,o.canvas.graph._groups.forEach((e=>{e.show_nodes=i.value})),t.setGroups(o.canvas.graph._groups)};let c,v=0,m=0,g=!1;const h=(e,s=!1)=>{if(g)return void(g=!1);const l=e.children.find((e=>e.mode==d.ALWAYS)),a=e.children.map((e=>e.id));o.canvas.graph._nodes.forEach((e=>{a.includes(e.id)&&(e.mode=l?s?d.NEVER:d.BYPASS:d.ALWAYS,e.graph.change())})),t.setNodes(o.canvas.graph._nodes)},f=(e,s=!1)=>{if(g)return void(g=!1);const l=e.mode==d.ALWAYS,a=o.canvas.graph._nodes.find((s=>s.id==e.id));a&&(a.mode=l?s?d.NEVER:d.BYPASS:d.ALWAYS,a.graph.change(),t.setNodes(o.canvas.graph._nodes))},b=(e,s="group")=>{v=(new Date).getTime(),clearTimeout(c),c=setTimeout((t=>{"group"==s?h(e,!0):f(e,!0)}),500)},N=e=>{m=(new Date).getTime(),m-v>500&&(g=!0),clearTimeout(c)};let H=y(null),R=y(null);y(!1);return(e,u)=>{var d,c;return A(),w("div",{class:E(Ye)},[x("div",{class:E(Ye+"__header"),onMousedown:u[0]||(u[0]=s=>e.$emit("handleHeader",s))},[x("div",je,B(C(s)("Nodes Map",!0)),1),x("div",Re,[(null==(d=C(n))?void 0:d.length)>0?M((A(),Q(C(P),{key:0,icon:i.value?"pi pi-angle-double-down":"pi pi-angle-double-up",text:"",rounded:"",severity:"secondary",onClick:k(r,["stop"]),size:"small"},null,8,["icon"])),[[C(U),i.value?C(s)("Collapse All"):C(s)("Expand All"),void 0,{top:!0}]]):I("",!0),D(e.$slots,"icon")])],34),x("div",{class:E(Ye+"__content")},[(null==(c=C(l))?void 0:c.length)>0?(A(),w("ol",De,[(A(!0),w(S,null,_(C(l),((e,l)=>(A(),w("li",{key:l,onDragstart:e=>((e,s)=>{H.value=s,e.currentTarget.style.opacity="0.6",e.currentTarget.style.border="1px dashed yellow",e.dataTransfer.effectAllowed="move"})(e,l),onDragend:e=>(e=>{if(e.target.style.opacity="1",e.currentTarget.style.border="1px dashed transparent","Manual drag&drop sorting"!==p("EasyUse.NodesMap.Sorting"))return void a.warn(s("For drag and drop sorting, please find Nodes map sorting mode in Settings->EasyUse and change it to manual"));let l=o.canvas.graph._groups,n=l[H.value],i=l[R.value];o.canvas.graph._groups[H.value]=i,o.canvas.graph._groups[R.value]=n,t.setGroups(o.canvas.graph._groups)})(e),onDragover:e=>((e,s)=>{e.preventDefault(),e.currentIndex!=H.value&&(R.value=s)})(e,l),draggable:!0},[void 0!==e.children?(A(),Q(Ne,{key:0,item:e,onChangeMode:s=>h(e),onMousedown:s=>b(e,"group"),onMouseup:N},{default:j((()=>[(A(!0),w(S,null,_(e.children,((e,s)=>(A(),Q(ze,{key:s,node:e,onChangeMode:s=>f(e),onMousedown:s=>b(e,"node"),onMouseup:N},null,8,["node","onChangeMode","onMousedown"])))),128))])),_:2},1032,["item","onChangeMode","onMousedown"])):(A(),Q(ze,{key:1,node:e.info,onChangeMode:s=>f(e.info),onMousedown:s=>b(e.info,"node"),onMouseup:N},null,8,["node","onChangeMode","onMousedown"]))],40,Le)))),128))])):(A(),w("div",Ze,[z(be,{icon:"pi pi-sitemap",title:C(s)("No Nodes",!0),message:C(s)("No nodes found in the map",!0)},null,8,["title","message"])]))],2)])}}},Ve=[x("svg",{class:"icon",t:"1714565543756",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"22538",width:"200",height:"200"},[x("path",{d:"M871.616 64H152.384c-31.488 0-60.416 25.28-60.416 58.24v779.52c0 32.896 26.24 58.24 60.352 58.24h719.232c34.112 0 60.352-25.344 60.352-58.24V122.24c0.128-32.96-28.8-58.24-60.288-58.24zM286.272 512c-23.616 0-44.672-20.224-44.672-43.008 0-22.784 20.992-43.008 44.608-43.008 23.616 0 44.608 20.224 44.608 43.008A43.328 43.328 0 0 1 286.272 512z m0-202.496c-23.616 0-44.608-20.224-44.608-43.008 0-22.784 20.992-43.008 44.608-43.008 23.616 0 44.608 20.224 44.608 43.008a43.456 43.456 0 0 1-44.608 43.008zM737.728 512H435.904c-23.68 0-44.672-20.224-44.672-43.008 0-22.784 20.992-43.008 44.608-43.008h299.264c23.616 0 44.608 20.224 44.608 43.008a42.752 42.752 0 0 1-41.984 43.008z m0-202.496H435.904c-23.616 0-44.608-20.224-44.608-43.008 0-22.784 20.992-43.008 44.608-43.008h299.264c23.616 0 44.608 20.224 44.608 43.008a42.88 42.88 0 0 1-42.048 43.008z","p-id":"22539",fill:"currentColor"})],-1)],Pe=[x("svg",{class:"icon",t:"1714565020764",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"7999",width:"200",height:"200"},[x("path",{d:"M810.438503 379.664884l-71.187166-12.777183C737.426025 180.705882 542.117647 14.602496 532.991087 7.301248c-12.777184-10.951872-32.855615-10.951872-47.45811 0-9.12656 7.301248-204.434938 175.229947-206.26025 359.586453l-67.536542 10.951871c-18.253119 3.650624-31.030303 18.253119-31.030303 36.506239v189.832442c0 10.951872 5.475936 21.903743 12.777184 27.379679 7.301248 5.475936 14.602496 9.12656 23.729055 9.12656h5.475936l133.247772-23.729055c40.156863 47.458111 91.265597 73.012478 151.500891 73.012477 60.235294 0 111.344029-27.379679 151.500891-74.837789l136.898396 23.729055h5.475936c9.12656 0 16.427807-3.650624 23.729055-9.12656 9.12656-7.301248 12.777184-16.427807 12.777184-27.379679V412.520499c1.825312-14.602496-10.951872-29.204991-27.379679-32.855615zM620.606061 766.631016H401.568627c-20.078431 0-36.506239 16.427807-36.506238 36.506239v109.518716c0 14.602496 9.12656 29.204991 23.729055 34.680927 14.602496 5.475936 31.030303 1.825312 40.156863-9.126559l16.427807-18.25312 32.855615 80.313726c5.475936 14.602496 18.253119 23.729055 34.680927 23.729055 16.427807 0 27.379679-9.12656 34.680927-23.729055l32.855615-80.313726 16.427807 18.25312c10.951872 10.951872 25.554367 14.602496 40.156863 9.126559 14.602496-5.475936 23.729055-18.253119 23.729055-34.680927v-109.518716c-3.650624-20.078431-20.078431-36.506239-40.156862-36.506239z",fill:"currentColor","p-id":"8000"})],-1)],Ge="comfyui-easyuse-toolbar",We={__name:"index",setup(e){const t=u(),l=y(!1);h((e=>l.value),(e=>{e?t.watchGraph(!0):t.unwatchGraph()}));const a=y(null),o=e=>{const s=a.value;var t=e.clientX||0,l=e.clientY||0,o=s.offsetLeft,n=s.offsetTop;function i(e){var a=e.clientX,i=e.clientY,r=a-t,u=i-l;s.style.left=o+r+"px",s.style.top=n+u+"px"}document.addEventListener("mousemove",i),document.addEventListener("mouseup",(function e(){document.removeEventListener("mousemove",i),document.removeEventListener("mouseup",e)}))};return(e,t)=>(A(),w(S,null,[x("div",{class:E(["flex-c",Ge])},[x("div",{class:E(["group flex-c",Ge+"-icon"]),onClick:t[0]||(t[0]=e=>l.value=!l.value)},Ve,2),x("div",{class:E(["rocket flex-c",Ge+"-icon"]),onClick:t[1]||(t[1]=(...e)=>C(v)&&C(v)(...e))},Pe,2)]),l.value?(A(),w("div",{key:0,ref_key:"nodesMapRef",ref:a,class:E(Ge+"-nodes-map")},[z(Oe,{onHandleHeader:o},{icon:j((()=>[M(z(C(P),{icon:"pi pi-times",text:"",rounded:"",severity:"secondary",onClick:t[2]||(t[2]=e=>l.value=!1),size:"small"},null,512),[[C(U),C(s)("Close"),void 0,{top:!0}]])])),_:1})],2)):I("",!0)],64))}},Ue={__name:"index",setup(e){const s=u();return N((e=>{s.watchGraph()})),(e,s)=>(A(),w("div",{class:E("comfyui-easyuse-map")},[z(Oe)]))}},Fe="Comfy.UseNewMenu",Te={__name:"App",setup(e){const t=y(null);return N((e=>{try{p("EasyUse.NodesMap.Enable",null,!0)&&o.extensionManager.registerSidebarTab({id:m,icon:"pi pi-sitemap",title:s("Nodes Map",!0),tooltip:s("Nodes Map",!0),type:"custom",render:e=>{e.style.height="100%",L(Z(Ue,{}),e)}}),t.value=p(Fe),g(Fe,(e=>{t.value=e}))}catch(l){}})),(e,s)=>(A(),w(S,null,[z(Se),"Disabled"==t.value?(A(),Q(We,{key:0})):I("",!0)],64))}},Xe=null==(e=document.getElementsByClassName("graph-canvas-container"))?void 0:e[0],Je=document.createElement("div");Je.id="comfyui-easyuse-components",Xe?Xe.append(Je):document.body.append(Je);const qe=Y(Te);qe.use(X),qe.use(F()),qe.mount("#"+Je.id);