├── 0x_erthor_node ├── __init__.py └── a000_example │ ├── a1基础格式.py │ ├── a2基础数据类型.py │ ├── a3基础调用流程.py │ ├── a4一个可以运行的节点.py │ └── a5最简格式.py ├── LICENSE └── README.md /0x_erthor_node/__init__.py: -------------------------------------------------------------------------------- 1 | # 从py中引入一个类作为引用依据 2 | # from py名称 import 类1,类2,类3 3 | from .a000_example.a1基础格式 import a1 4 | from .a000_example.a2基础数据类型 import a2 5 | from .a000_example.a3基础调用流程 import a3 6 | from .a000_example.a4一个可以运行的节点 import a4 7 | from .a000_example.a5最简格式 import a5 8 | 9 | 10 | # (必填)填写 import的类名称,命名需要唯一,key或value与其他插件冲突可能引用不了。这是决定是否能引用的关键。 11 | # key(自定义):value(import的类名称) 12 | NODE_CLASS_MAPPINGS = { 13 | # a000_example 14 | "a1": a1,"a2": a2,"a3": a3,"a4":a4,"a5":a5, 15 | 16 | } 17 | 18 | 19 | # (可不写)填写 ui界面显示名称,命名会显示在节点ui左上角,如不写会用类的名称显示在节点ui上 20 | # key(自定义):value(ui显示的名称) 21 | NODE_DISPLAY_NAME_MAPPINGS = { 22 | # a000_example 23 | "a1": "a1基础格式~","a2": "a2基础数据类型~","a3": "a3基础调用流程~","a4":"a4一个可以运行的节点~","a5":"a5最简格式~", 24 | 25 | } 26 | 27 | 28 | # 引入以上两个字典的内容 29 | all = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] -------------------------------------------------------------------------------- /0x_erthor_node/a000_example/a1基础格式.py: -------------------------------------------------------------------------------- 1 | # 0 定义一个类,一个节点就是一个类。comfyui会引入这个类作为一个节点============================== 2 | class a1: 3 | def __init__(self): 4 | pass 5 | 6 | # 1 定义这个节点在ui界面中的位置======================================================= 7 | CATEGORY = "0x_erthor_node👾👾👾/a000_example" 8 | 9 | 10 | #====================定义输入=========================================================== 11 | @classmethod 12 | def INPUT_TYPES(s):# 固定格式,输入参数种类 13 | # 返回一个包含所需输入类型的字典 14 | return { 15 | "required": { 16 | 17 | # 2 左边的输入点在这里定义================================================= 18 | "左边的输入": ("STRING", {"forceInput": True}), 19 | 20 | 21 | # 3 中间的参数栏在这里定义================================================= 22 | "参数:整数": ("INT", { 23 | "default": 20, # 默认 24 | "min": 1, 25 | "max": 10000, 26 | "step": 2, # 步长 27 | "display": "number"}), # 数值调整 28 | 29 | }, 30 | } 31 | 32 | 33 | # 4 右边的输出点在这里定义============================================================= 34 | OUTPUT_NODE = True # 表明它是一个输出节点 35 | # 输出的数据类型,需要大写 36 | RETURN_TYPES = ("INT",) 37 | # 自定义输出名称 38 | RETURN_NAMES = ("1整数",) 39 | 40 | 41 | # 5 节点的核心功能逻辑在这里定义======================================================== 42 | FUNCTION = "test" # 核心功能函数名称,将运行这个类中的这个方法 43 | 44 | 45 | def test(self,): 46 | pass 47 | 48 | # 注释:实现了以上结构,就能在comfyui中引入这个节点,但这个节点现在是不能工作的 49 | 50 | -------------------------------------------------------------------------------- /0x_erthor_node/a000_example/a2基础数据类型.py: -------------------------------------------------------------------------------- 1 | class a2: 2 | def __init__(self): 3 | pass 4 | 5 | CATEGORY = "0x_erthor_node👾👾👾/a000_example" 6 | 7 | @classmethod 8 | def INPUT_TYPES(s): 9 | return { 10 | # 1 以下是节点中大部分的输入类型,包括必选输入,可选输入,隐藏输入。这些输入会传递给函数,作为参数。========= 11 | # 必选输入 12 | "required": { 13 | 14 | "pipe": ("PIPE_LINE",), 15 | 16 | "参数:整数": ("INT", { 17 | "default": 20, # 默认 18 | "min": 1, 19 | "max": 10000, 20 | "step": 2, # 步长 21 | "display": "number"}), # 数值调整 22 | 23 | "参数:浮点数": ("FLOAT", { 24 | "default": 1.0, 25 | "min": -10.0, 26 | "max": 10.0, 27 | "step": 0.01, 28 | "round": 0.001, # 精度 29 | "display": "slider"}), # 滑动调整 30 | 31 | "参数:字符串": ("STRING", { 32 | "default": "啊啊啊啊啊啊", # 默认存在内容 33 | "multiline": True}), # 是否允许多行输入 34 | 35 | "参数:布尔值": ("BOOLEAN", { 36 | "default": True}), 37 | 38 | "下拉选择框": (["None"] + ["enable", "disable"],), # 括号里是一个列表 39 | }, 40 | 41 | # 可选输入 42 | "optional": { 43 | "model": ("MODEL",), 44 | "vae": ("VAE",), 45 | "clip": ("CLIP",), 46 | 47 | "latent": ("LATENT",), 48 | "image": ("IMAGE",), 49 | 50 | "pos": ("CONDITIONING",), 51 | "neg": ("CONDITIONING",), 52 | 53 | "xyPlot": ("XYPLOT",), 54 | }, 55 | 56 | # 隐藏输入 57 | "hidden": {"my_unique_id": "UNIQUE_ID"}, # comfyui内部任务id 58 | } 59 | 60 | # 2 以下是节点中大部分的输出类型。输出类型必须大写,ui显示名称可自定义。======================================= 61 | OUTPUT_NODE = True 62 | # 输出的数据类型,需要大写 63 | RETURN_TYPES = ("PIPE_LINE","MODEL","VAE","CLIP","LATENT","IMAGE","CONDITIONING","CONDITIONING","INT", "FLOAT", "STRING",) 64 | # 自定义输出名称 65 | RETURN_NAMES = ("0pepe","1model","2vae","3clip","4latent","5image","6pos","7neg","8整数","9浮点数","10字符串",) 66 | 67 | 68 | FUNCTION = "test" 69 | def test(self,): 70 | pass 71 | 72 | -------------------------------------------------------------------------------- /0x_erthor_node/a000_example/a3基础调用流程.py: -------------------------------------------------------------------------------- 1 | # 提醒,需要把python解释器地址锁定到comfyui本身的python.exe上,才可以引用以下包 2 | import folder_paths # comfyui对模型名称的调用(ckpt,vae,clip,lora等) 3 | import comfy.samplers # comfyui对采样器,调度器的调用 4 | import comfy.model_management # comfyui对模型加载进行操作 5 | 6 | class a3: 7 | def __init__(self): 8 | pass 9 | 10 | CATEGORY = "0x_erthor_node👾👾👾/a000_example" 11 | 12 | @classmethod 13 | def INPUT_TYPES(s): 14 | return { 15 | "required": { 16 | # 1 调用本地参数模型============================================================ 17 | "获取本地checkpoint": (["None"] + folder_paths.get_filename_list("checkpoints"),), 18 | "获取本地vae": (["None"] + folder_paths.get_filename_list("vae"),), 19 | "获取本地lora": (["None"] + folder_paths.get_filename_list("loras"),), 20 | 21 | "获取本地sampler": (comfy.samplers.KSampler.SAMPLERS,), # 采样器 22 | "获取本地scheduler": (comfy.samplers.KSampler.SCHEDULERS,), # 调度器 23 | 24 | # 随机种子 25 | # 种子会占两行,第二行会决定种子使用过后如何变化 26 | "seed": ("INT", {"default": 123, "min": 0, "max": 0xffffffffffffffff, "step": 1}), 27 | }, 28 | } 29 | 30 | 31 | OUTPUT_NODE = True 32 | RETURN_TYPES = ("INT",) 33 | RETURN_NAMES = ("1整数",) 34 | 35 | 36 | 37 | FUNCTION = "test" 38 | def test(self,): 39 | # 2 对加载模型进行操作===================================================================== 40 | 41 | # 在现存中清除所有加载的模型 42 | comfy.model_management.unload_all_models() 43 | 44 | # 获取当前可用的设备 45 | device = comfy.model_management.get_torch_device() 46 | 47 | # 设置随机种子 48 | seed = '123' 49 | torch.manual_seed(seed) 50 | 51 | pass 52 | -------------------------------------------------------------------------------- /0x_erthor_node/a000_example/a4一个可以运行的节点.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import random 3 | import comfy.model_management 4 | 5 | class a4: 6 | def __init__(self): 7 | pass 8 | CATEGORY = "0x_erthor_node👾👾👾/a000_example" 9 | 10 | @classmethod 11 | def INPUT_TYPES(s): 12 | return { 13 | "required": { 14 | "width": ("INT", {"default": 256, "min": 128, "max": 2048, "step": 128}), 15 | "height": ("INT", {"default": 256, "min": 128, "max": 2048, "step": 128}), 16 | }, 17 | } 18 | 19 | OUTPUT_NODE = True 20 | RETURN_TYPES = ("IMAGE",) 21 | RETURN_NAMES = ("image(torch.Tensor)",) 22 | 23 | 24 | FUNCTION = "test" 25 | def test(self,width,height,): 26 | # 1加载模型======================================================================== 27 | # 清空所有加载模型 28 | comfy.model_management.unload_all_models() 29 | # 加载模型略过,因为需要引入额外的包 30 | 31 | # 2设置参数======================================================================== 32 | # width 33 | # height 34 | 35 | seed = random.randint(0, 0xffffffffffffffff) # 随机生成种子int格式 36 | torch.manual_seed(seed) # 设置种子 37 | 38 | # 3进行推理======================================================================== 39 | noise = torch.randn((1, 3, width, height), device="cuda") 40 | 41 | # 4数据格式处理===================================================================== 42 | # 注释:输出往往是一个PIL.image的数据类型,需要把图片数据转化成torch.Tensor数据类型才可以被comfyui中的previeimage节点接收 43 | 44 | # [PIL.Image.Image]->[torch.Tensor] 45 | # torch.Tensor列表 = [ToTensor()(img) for img in 图片列表] 46 | 47 | # [torch.Tensor]->torch.Tensor 48 | # 合并的torch.Tensor = torch.stack(torch.Tensor列表) 49 | 50 | # 调整维度顺序(2,3,1024,1024)->(2,1024,1024,3) 51 | # 调整顺序之后的tensor=调整顺序之前的tensor.permute(0, 2, 3, 1).cpu() 52 | tensor=noise.permute(0, 2, 3, 1).cpu() 53 | 54 | # 5输出=========================================================================== 55 | return (tensor,) 56 | -------------------------------------------------------------------------------- /0x_erthor_node/a000_example/a5最简格式.py: -------------------------------------------------------------------------------- 1 | import os 2 | # 动态获取路径 3 | dir = os.path.dirname(__file__) # 当前脚本目录 4 | last1=os.path.basename(dir) # 最后一个目录 5 | last2=os.path.basename(os.path.dirname(dir)) # 倒数第二个目录 6 | gategory=f"{last2}👾👾👾/{last1}" # 动态获取的当前文件夹路径 7 | 8 | class a5: 9 | def __init__(self): 10 | pass 11 | CATEGORY = gategory 12 | 13 | @classmethod 14 | def INPUT_TYPES(s): 15 | return { 16 | "required": { 17 | 18 | }, 19 | } 20 | 21 | OUTPUT_NODE = True 22 | RETURN_TYPES = ("INT",) 23 | RETURN_NAMES = ("1整数",) 24 | 25 | FUNCTION = "test" 26 | def test(self,): 27 | pass 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 erthor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 一个创建comfyui自定义节点的指南 2 | 3 | 😈😈 4 | 5 | 6 | 安装:不需要安装任何其他依赖文件,只需要把0x_erthor_node文件夹复制到custom_nodes文件夹下,就能安装成功。 7 | 8 | a1:展示了代码结构,表明了每一块代码的作用是什么,哪里是输入,哪里是参数栏,哪里是输出。 9 | 10 | a2:如何加入各种comfyui支持的数据类型,包括img,latent等。👇 11 | 12 | ![111](https://github.com/erthorpabar/guide-to-write-comfyui-custom-node/assets/161300602/af6bdef8-4728-45b5-8833-85c6f430f4ef) 13 | 14 | 15 | a3:如何使用comfyui自带的库去索引参数,如ckpt,vae,clip等。👇 16 | 17 | ![333](https://github.com/erthorpabar/guide-to-write-comfyui-custom-node/assets/161300602/5e8aba93-0e6d-4fb1-b57e-9db492a408b5) 18 | 19 | 20 | a4:一个最简的可以运行的节点,它创建一个空的torch.Tensor向量空间,也叫噪点图,并输出。只需要连接最基础的preview image节点就能展示出来。👇 21 | 22 | ![44](https://github.com/erthorpabar/guide-to-write-comfyui-custom-node/assets/161300602/59673717-5a72-4af2-9105-b3fd5ce06ff8) 23 | 24 | 25 | a5:通用最简模板,可直接复制此节点并在此基础上进行创作。 26 | 27 | 28 | 29 | 30 | --------------------------------------------------------------------------------