🗒初墨
🍊Hello,各位好,我是面包!
厌倦了千篇一律的AI流程?教你用Python打造专属ComfyUI插件,让Stable Diffusion听你指挥!
节点文件模板
# 导入依赖库
import random
import torch
# 定义一个类,一个节点就是一个类,comfyui会引入这个类作为一个节点
class your_node:
def __init__(self)
pass
# 定义节点索引
@classmethod
def INPUT_TYPES(cls):
inputs = {
# 必选输入,不输入会报错
"required": {
"pipe": ("PIPE_LINE",),
"整数": ("INT", {
"default": 0,
"min": 0 ,
"max": 1000,
"step": 2,
"display":"number"}),
"浮点数": ("FLOAT", {
"default": 0,
"min": 0 ,
"max": 1000,
"step": 2,
"round":0.001,
"display":"slider"}),
"字符串": ("STRING", {
"default": "字符串",
"multiline": True}),
"布尔值": ("BOOLEAN", {
"default": True,}),
"下拉选择框": (["None"] + ["enable", "disable"]),
}
# 可选输入
"optional":{
"model":("MODEL",),
"vae":("VAE",),
"clip":("CLIP",),
"latent":("LATENT",),
"image":("IMAGE",),
"pos":("CONDITIONING",),
"neg":("CONDITIONING",),
"xyPlot":("XYPLOT",),
}
# 隐藏输入
"hidden":{
"my_unique_id":{"UNIQUE_ID"},
}
}
return inputs
OUTPUT_NODE = True
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("1个整数",)
FUNCTION = "test"
def run(self,)
passfrom .example1 import A,B,C
MODE_CLASS_MAPPINGS = {
# ui界面搜索节点名称
"funtion A":A,
"funtion A":A,
"funtion A":A,
"run_node":run_node,
}实现一个最简单的运算计算器的功能插件
首先我们在./comfyui/ComfyUI/custom_nodes新建一个comfyui_breadq文件夹
然后在其中创建__init__.py和Calculator.py两个文件。
.
comfyui
└─ .ComfyUI
└─ custom_nodes
└─ comfyui_breadq
├─ __init__.py
└─ Calculator.pyCalculator.py
class SimpleCalculator:
@classmethod
def INPUT_TYPES(cls):
inputs = {
"required": {
"input1": ("INT", {"default": 0, "label": "Input 1"}),
"input2": ("INT", {"default": 0, "label": "Input 2"}),
"operation": (["add", "subtract", "multiply", "divide"], {"default": "add", "label": "Operation"})
}
}
return inputs
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("RESULT",)
FUNCTION = "calculate"
CATEGORY = "bread/SimpleCalculator"
def calculate(self, input1, input2, operation):
if operation == "add":
result = input1 + input2
elif operation == "subtract":
result = input1 - input2
elif operation == "multiply":
result = input1 * input2
elif operation == "divide":
if input2 != 0:
result = input1 // input2
else:
raise ValueError("Division by zero is not allowed.")
else:
raise ValueError(f"Unsupported operation: {operation}")
return (result,)参数
INPUT_TYPES定义节点输入RETURN_TYPES定义节点将生成的输出RETURN_NAMES输出点标识FUNCTION在节点执行时调用的函数名称
__init__.py
from .Calculator import SimpleCalculator
NODE_CLASS_MAPPINGS = {
"breadq_Calculator": SimpleCalculator
}
NODE_DISPLAY_NAMES_MAPPINGS = {
"breadq_Calculator": "My Tutorial - SimpleCalculator"
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAMES_MAPPINGS']参数
NODE_CLASS_MAPPINGS这是一个字典,用于将节点的唯一标识符key映射到具体的类valueNODE_DISPLAY_NAMES_MAPPINGS定义节点在图形化界面中显示的友好名称,但好像没起作用
效果展示

小纸条
修改完插件记得重启一下comfyui,不然插件不会更新!
面向GPT编程的图片嵌入插件
利用Kimi编写py代码,实现了指定大小、指定数量的图片随机且不重叠地嵌入指定大小的背景图里
代码组
import os
import random
import torch
from PIL import Image
import numpy as np
class ImageSelector:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"folder_path": ("STRING", {"default": "", "multiline": False}), # 文件夹路径
"num_images": ("INT", {"default": 1, "min": 1, "max": 100}), # 抽取的图片数量
"scale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.1}), # 缩放倍率
"background_width": ("INT", {"default": 800, "min": 1, "max": 8192}), # 背景宽度
"background_height": ("INT", {"default": 600, "min": 1, "max": 8192}), # 背景高度
"background_color": ("COLOR", {"default": (255, 255, 255)}), # 背景颜色
"seed_value": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}), # 随机种子值
}
}
RETURN_TYPES = ("IMAGE",) # 定义节点的输出类型
FUNCTION = "choose_image" # 定义节点执行时调用的函数名称
CATEGORY = "Image Processing" # 定义节点分类
def choose_image(self, folder_path, num_images, scale_factor, background_width, background_height, background_color, seed_value):
"""
节点的主要逻辑:选择图片、缩放并嵌入背景
"""
# 设置随机种子(仅影响嵌入位置)
random.seed(seed_value) # 设置 Python 的随机种子
torch.manual_seed(seed_value) # 设置 PyTorch 的随机种子
np.random.seed(seed_value) # 设置 NumPy 的随机种子
print(f"随机种子设置为: {seed_value}")
# 获取文件夹中所有图片路径
image_extensions = ['.jpg', '.jpeg', '.png', '.bmp']
image_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path)
if os.path.splitext(f)[1].lower() in image_extensions]
if len(image_paths) < num_images:
raise ValueError("文件夹中图片数量不足!")
# 随机选择指定数量的图片
selected_paths = random.sample(image_paths, num_images)
# 加载并缩放图片
scaled_images = []
for path in selected_paths:
img = Image.open(path).convert("RGB") # 确保是 RGB 格式
width, height = img.size
new_size = (int(width * scale_factor), int(height * scale_factor))
scaled_img = img.resize(new_size, Image.LANCZOS) # 使用 LANCZOS 替代 ANTIALIAS
scaled_images.append(scaled_img)
# 创建背景并嵌入图片
background = Image.new('RGB', (background_width, background_height), background_color)
placed_positions = []
for img in scaled_images:
img_width, img_height = img.size
max_x = background_width - img_width
max_y = background_height - img_height
while True:
x = random.randint(0, max_x)
y = random.randint(0, max_y)
overlap = any(self.check_overlap(x, y, img_width, img_height, pos) for pos in placed_positions)
if not overlap:
break
background.paste(img, (x, y))
placed_positions.append((x, y, img_width, img_height))
# 将 PIL 图像转换为 torch.Tensor
background_tensor = self.pil_to_tensor(background)
return (background_tensor,)
@staticmethod
def check_overlap(x1, y1, w1, h1, pos):
"""
检查两个矩形是否重叠
"""
x2, y2, w2, h2 = pos
return not (x1 + w1 <= x2 or x2 + w2 <= x1 or y1 + h1 <= y2 or y2 + h2 <= y1)
@staticmethod
def pil_to_tensor(image):
"""
将 PIL 图像转换为 torch.Tensor
"""
# 将 PIL 图像转换为 NumPy 数组
img_array = np.array(image).astype(np.float32) / 255.0
# 转换为 torch.Tensor 并调整维度顺序为 [B, H, W, C]
img_tensor = torch.from_numpy(img_array).unsqueeze(0) # 添加批处理维度
return img_tensorfrom .bread import ImageSelector
NODE_CLASS_MAPPINGS = {
"Image Selector": ImageSelector,
}
__all__ = ['NODE_CLASS_MAPPINGS'].
comfyui
└─ .ComfyUI
└─ custom_nodes
└─ comfyui_bread
├─ __init__.py
└─ bread.py工作流

图片展示

问题
- 图像的数据类型是 torch.Tensor,其形状为 [B,H,W,C],其中 B 是批处理大小,C 是通道数
- 随机数只能在0~2^32之间,ComfyUI自带的随机数节点使用随机模式时超过了这个范围,因此不能使用随机模式
解读进阶插件的源码
以ComfyUI-BiRefNet插件为例
utils.py
import os
def check_download_model(model_path, repo_id="ViperYX/BiRefNet"):
"""
检查指定路径下是否存在模型文件,如果不存在,则从 Hugging Face Hub 下载模型。
参数:
model_path (str): 目标模型文件的完整路径(包括文件名)。
repo_id (str): Hugging Face Hub 上的仓库 ID,默认为 "ViperYX/BiRefNet"。
返回:
bool: 如果下载了模型文件,则返回 True;如果文件已存在,则返回 False。
"""
# 检查目标模型文件是否存在
if not os.path.exists(model_path):
# 获取模型文件所在的目录路径
folder_path = os.path.dirname(model_path)
# 如果目录不存在,则创建它(包括所有父目录)
if not os.path.exists(folder_path):
os.makedirs(folder_path)
# 提取目标文件名
file_name = os.path.basename(model_path)
# 打印下载信息,告知用户正在下载模型
print(f"Downloading BiRefNet model to: {model_path}")
# 导入 Hugging Face Hub 的 snapshot_download 函数
from huggingface_hub import snapshot_download
# 从 Hugging Face Hub 下载模型文件
# 参数说明:
# - repo_id: 指定仓库 ID
# - allow_patterns: 指定下载匹配文件名的文件
# - local_dir: 指定下载到的本地目录
# - local_dir_use_symlinks: 设置为 False,直接下载文件,不使用符号链接
snapshot_download(repo_id=repo_id,
allow_patterns=[f"*{file_name}*"],
local_dir=folder_path,
local_dir_use_symlinks=False)
# 如果成功下载,返回 True
return True
# 如果文件已存在,返回 False
return FalseINFO
Os库提供通用的、基本的操作系统交互功能
- Os库是python标准库,包含几百个函数
- 常用路径操作、进程管理、环境参数等几类
- 路径操作:os.path子库,处理文件路径及信息
- 进程管理:启动系统中其他程序
- 环境参数:获得系统软件硬件信息等环境参数
它是怎么找到huggingface指定下载链接的?
在代码中,模型文件的下载链接并不是直接指定的,而是通过 Hugging Face Hub 的 API 动态获取的。 具体来说,代码使用了 huggingface_hub 库中的 snapshot_download 函数来实现下载功能。以下是代码中下载链接的查找和下载过程的详细解释:
- Hugging Face Hub 的工作原理
Hugging Face Hub 是一个托管和共享机器学习模型的平台。每个模型或资源都被存储在一个仓库(repository)中,仓库的唯一标识是 repo_id。例如,repo_id="ViperYX/BiRefNet" 表示用户 ViperYX 下的 BiRefNet 仓库。
仓库结构:Hugging Face Hub 上的仓库可以包含多个文件,例如模型权重文件、配置文件、文档等。
API 访问:通过 Hugging Face 提供的 API 和工具(如 huggingface_hub 库),可以访问这些仓库中的文件。
- 代码中的下载逻辑
在代码中,snapshot_download 函数负责从 Hugging Face Hub 下载文件。以下是关键步骤:
- 指定仓库和文件模式
snapshot_download(repo_id=repo_id,
allow_patterns=[f"*{file_name}*"],
local_dir=folder_path,
local_dir_use_symlinks=False)repo_id:指定 Hugging Face Hub 上的仓库 ID。例如,ViperYX/BiRefNet 表示从用户 ViperYX 的 BiRefNet 仓库中下载。
allow_patterns:指定要下载的文件模式。代码中使用了 f"{file_name}",这是一个通配符模式,表示下载仓库中与目标文件名匹配的所有文件。
例如,如果目标文件名是 model.pth,则会匹配仓库中所有包含 model.pth` 的文件。
- 动态查找下载链接
snapshot_download 函数的工作原理如下: 访问 Hugging Face Hub 的 API: 通过 repo_id,函数会访问 Hugging Face Hub 的 API,获取指定仓库的文件列表。 API 会返回仓库中所有文件的元数据,包括文件名、大小、存储路径等。 匹配文件: 使用 allow_patterns 参数,函数会从仓库的文件列表中筛选出符合条件的文件。例如,如果 allow_patterns=["model.pth"],则会筛选出仓库中所有文件名包含 model.pth 的文件。 获取下载链接: Hugging Face Hub 的 API 会为每个匹配的文件提供一个下载链接。 这些链接通常是 Hugging Face 的 CDN(内容分发网络)地址,用于高效地下载文件。 下载文件: snapshot_download 函数会使用这些下载链接,将文件下载到本地的 local_dir 目录中。 如果 local_dir_use_symlinks=False,则会直接将文件存储到本地路径,而不是创建符号链接。 3. 总结:如何找到下载链接 代码并没有直接指定下载链接,而是通过 Hugging Face Hub 的 API 动态获取。 snapshot_download 函数通过以下步骤找到下载链接: 使用 repo_id 访问 Hugging Face Hub 的仓库。 通过 API 获取仓库中所有文件的元数据。 使用 allow_patterns 筛选出目标文件。 从 API 返回的元数据中提取文件的下载链接。 使用这些链接下载文件到本地。
小纸条
向量数据:2D 张量,形状为 (samples, features)。
时间序列数据或序列数据:3D 张量,形状为 (samples, timesteps, features)。
图像:4D 张量,形状为 (samples, height, width, channels) 或 (samples, channels,height, width)。
视频:5D 张量,形状为 (samples, frames, height, width, channels) 或 (samples, frames, channels, height, width)。
requirements.txt文件格式:
requests==1.2.0
Flask==0.10.1requests==1.2.0
Flask==0.10.1
