feat: 新增Show Any (UTK)节点和Extract Video Frames (UTK)节点

- 新增Show Any (UTK)节点:支持显示任意类型数据,参考comfyui-easy-use实现
- 新增Extract Video Frames (UTK)节点:支持从视频或图片序列中智能抽取帧
- 更新版本号至1.4.9
- 添加WEB_DIRECTORY导出以支持JavaScript文件加载
This commit is contained in:
Cyber Dick Lang
2025-12-04 01:48:10 +08:00
parent ca49258023
commit 522ff9b5ec
5 changed files with 695 additions and 3 deletions
+35 -2
View File
@@ -8,13 +8,22 @@ A comprehensive toolkit for ComfyUI that provides various utility nodes for imag
:license: MIT, see LICENSE for more details.
"""
__version__ = "1.4.8"
__version__ = "1.4.9"
__author__ = "CyberDickLang"
__email__ = "286878701@qq.com"
__url__ = "https://github.com/whmc76"
# 更新日志
CHANGELOG = {
"1.4.9": [
"新增Show Any (UTK)节点:",
"- 参考comfyui-easy-use的showAnything节点实现",
"- 支持显示任意类型的数据(string、int、float、json、list、torch.Tensor等)",
"- 在节点内部文本框中显示数据类型和内容",
"- 支持数据透传,不做任何修改",
"- 支持工作流保存和加载时恢复显示内容",
"- 分类:UniversalToolkit/Tools",
],
"1.4.8": [
"版本更新和代码优化:",
"- 更新插件版本号为 1.4.8",
@@ -777,6 +786,14 @@ try:
NODE_CLASS_MAPPINGS as GET_IMAGE_RANGE_MAPPINGS
from .nodes.tools.get_image_range_from_batch import \
NODE_DISPLAY_NAME_MAPPINGS as GET_IMAGE_RANGE_DISPLAY
from .nodes.tools.load_video_frames import \
NODE_CLASS_MAPPINGS as EXTRACT_VIDEO_FRAMES_MAPPINGS
from .nodes.tools.load_video_frames import \
NODE_DISPLAY_NAME_MAPPINGS as EXTRACT_VIDEO_FRAMES_DISPLAY
from .nodes.tools.show_any import \
NODE_CLASS_MAPPINGS as SHOW_ANY_MAPPINGS
from .nodes.tools.show_any import \
NODE_DISPLAY_NAME_MAPPINGS as SHOW_ANY_DISPLAY
from .nodes.tools.optimal_context_window_node import \
NODE_CLASS_MAPPINGS as BEST_CONTEXT_WINDOW_MAPPINGS
from .nodes.tools.optimal_context_window_node import \
@@ -789,9 +806,14 @@ try:
NODE_CLASS_MAPPINGS as RESIZE_VER_KJ_MAPPINGS
from .nodes.image.resize_image_ver_kj import \
NODE_DISPLAY_NAME_MAPPINGS as RESIZE_VER_KJ_DISPLAY
except ImportError:
except ImportError as e:
print(f"[UniversalToolkit] 导入错误: {e}")
GET_IMAGE_RANGE_MAPPINGS = {}
GET_IMAGE_RANGE_DISPLAY = {}
EXTRACT_VIDEO_FRAMES_MAPPINGS = {}
EXTRACT_VIDEO_FRAMES_DISPLAY = {}
SHOW_ANY_MAPPINGS = {}
SHOW_ANY_DISPLAY = {}
BEST_CONTEXT_WINDOW_MAPPINGS = {}
BEST_CONTEXT_WINDOW_DISPLAY = {}
BLOCKIFY_MASK_MAPPINGS = {}
@@ -839,6 +861,8 @@ NODE_CLASS_MAPPINGS.update(COLOR_TO_MASK_MAPPINGS)
NODE_CLASS_MAPPINGS.update(LAZY_SWITCH_MAPPINGS)
NODE_CLASS_MAPPINGS.update(TEXT_TRANSLATOR_API_MAPPINGS)
NODE_CLASS_MAPPINGS.update(GET_IMAGE_RANGE_MAPPINGS)
NODE_CLASS_MAPPINGS.update(EXTRACT_VIDEO_FRAMES_MAPPINGS)
NODE_CLASS_MAPPINGS.update(SHOW_ANY_MAPPINGS)
NODE_CLASS_MAPPINGS.update(BEST_CONTEXT_WINDOW_MAPPINGS)
NODE_CLASS_MAPPINGS.update(BLOCKIFY_MASK_MAPPINGS)
NODE_CLASS_MAPPINGS.update(RESIZE_VER_KJ_MAPPINGS)
@@ -882,6 +906,8 @@ NODE_DISPLAY_NAME_MAPPINGS.update(COLOR_TO_MASK_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(LAZY_SWITCH_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(TEXT_TRANSLATOR_API_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(GET_IMAGE_RANGE_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(EXTRACT_VIDEO_FRAMES_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(SHOW_ANY_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(BEST_CONTEXT_WINDOW_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(BLOCKIFY_MASK_DISPLAY)
NODE_DISPLAY_NAME_MAPPINGS.update(RESIZE_VER_KJ_DISPLAY)
@@ -926,16 +952,23 @@ NODE_CATEGORIES = {
"APIImageGenerator_UTK",
"TextTranslatorAPI_UTK",
"GetImageRangeFromBatch_UTK",
"Extract_Video_Frames_UTK",
"ShowAny_UTK",
"BestContextWindow_UTK",
"BlockifyMask_UTK",
"ResizeImageVerKJ_UTK",
]
}
# 导出WEB_DIRECTORY以便ComfyUI加载JavaScript文件
import os
WEB_DIRECTORY = os.path.join(os.path.dirname(__file__), "web")
__all__ = [
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"NODE_CATEGORIES",
"WEB_DIRECTORY",
"__version__",
"__author__",
"__email__",
+386
View File
@@ -0,0 +1,386 @@
"""
Load Video Frames Node for ComfyUI Universal Toolkit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Load frames from video files or image sequences and intelligently sample frames
based on target frame count and sampling mode.
:copyright: (c) 2024 by May
:license: MIT, see LICENSE for more details.
"""
import os
import cv2
import torch
from typing import Optional, Tuple, List
from PIL import Image
from ..image.image_converters import pil2tensor
class Extract_Video_Frames_UTK:
"""
从视频文件或图片序列中智能抽取帧
支持多种抽取模式:
- 平均抽取:均匀分布在整个视频/序列中
- 前面较多:前半部分抽取更多帧
- 后面较多:后半部分抽取更多帧
- 中间较多:中间部分抽取更多帧
- 两端较多:开头和结尾抽取更多帧
"""
CATEGORY = "UniversalToolkit/Tools"
RETURN_TYPES = ("IMAGE", "INT")
RETURN_NAMES = ("images", "frames_count")
FUNCTION = "load_frames"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video_path": ("STRING", {
"default": "",
"tooltip": "视频文件路径(支持mp4, avi, mov等格式)"
}),
"target_frames": ("INT", {
"default": 8,
"min": 1,
"max": 1000,
"step": 1,
"tooltip": "目标抽取的帧数"
}),
"mode": (["average", "front_heavy", "back_heavy", "middle_heavy", "ends_heavy"], {
"default": "average",
"tooltip": "Frame extraction mode"
}),
},
"optional": {
"images": ("IMAGE", {
"tooltip": "图片序列输入(如果提供,将优先使用图片序列而不是视频)"
}),
}
}
def calculate_frame_indices(self, total_frames: int, target_frames: int, mode: str) -> List[int]:
"""
根据模式和目标帧数计算要抽取的帧索引
Args:
total_frames: 总帧数
target_frames: 目标抽取帧数
mode: 抽取模式
Returns:
帧索引列表
"""
if total_frames <= 0:
return []
if target_frames >= total_frames:
# 如果目标帧数大于等于总帧数,返回所有帧
return list(range(total_frames))
indices = []
if mode == "average":
# 均匀分布
step = total_frames / target_frames
indices = [int(i * step) for i in range(target_frames)]
# 确保最后一个索引不超过总帧数
indices[-1] = min(indices[-1], total_frames - 1)
elif mode == "front_heavy":
# 前半部分抽取60%,后半部分抽取40%
front_count = int(target_frames * 0.6)
back_count = target_frames - front_count
# 前半部分均匀抽取
if front_count > 0:
front_step = (total_frames // 2) / front_count
front_indices = [int(i * front_step) for i in range(front_count)]
else:
front_indices = []
# 后半部分均匀抽取
if back_count > 0:
back_start = total_frames // 2
back_step = (total_frames - back_start) / back_count
back_indices = [back_start + int(i * back_step) for i in range(back_count)]
else:
back_indices = []
indices = front_indices + back_indices
elif mode == "back_heavy":
# 前半部分抽取40%,后半部分抽取60%
front_count = int(target_frames * 0.4)
back_count = target_frames - front_count
# 前半部分均匀抽取
if front_count > 0:
front_step = (total_frames // 2) / front_count
front_indices = [int(i * front_step) for i in range(front_count)]
else:
front_indices = []
# 后半部分均匀抽取
if back_count > 0:
back_start = total_frames // 2
back_step = (total_frames - back_start) / back_count
back_indices = [back_start + int(i * back_step) for i in range(back_count)]
else:
back_indices = []
indices = front_indices + back_indices
elif mode == "middle_heavy":
# 开头20%,中间60%,结尾20%
start_count = int(target_frames * 0.2)
middle_count = int(target_frames * 0.6)
end_count = target_frames - start_count - middle_count
# 开头部分
if start_count > 0:
start_step = (total_frames // 4) / max(start_count, 1)
start_indices = [int(i * start_step) for i in range(start_count)]
else:
start_indices = []
# 中间部分
if middle_count > 0:
middle_start = total_frames // 4
middle_end = total_frames * 3 // 4
middle_step = (middle_end - middle_start) / max(middle_count, 1)
middle_indices = [middle_start + int(i * middle_step) for i in range(middle_count)]
else:
middle_indices = []
# 结尾部分
if end_count > 0:
end_start = total_frames * 3 // 4
end_step = (total_frames - end_start) / max(end_count, 1)
end_indices = [end_start + int(i * end_step) for i in range(end_count)]
else:
end_indices = []
indices = start_indices + middle_indices + end_indices
elif mode == "ends_heavy":
# 开头40%,中间20%,结尾40%
start_count = int(target_frames * 0.4)
middle_count = int(target_frames * 0.2)
end_count = target_frames - start_count - middle_count
# 开头部分
if start_count > 0:
start_step = (total_frames // 3) / max(start_count, 1)
start_indices = [int(i * start_step) for i in range(start_count)]
else:
start_indices = []
# 中间部分
if middle_count > 0:
middle_start = total_frames // 3
middle_end = total_frames * 2 // 3
middle_step = (middle_end - middle_start) / max(middle_count, 1)
middle_indices = [middle_start + int(i * middle_step) for i in range(middle_count)]
else:
middle_indices = []
# 结尾部分
if end_count > 0:
end_start = total_frames * 2 // 3
end_step = (total_frames - end_start) / max(end_count, 1)
end_indices = [end_start + int(i * end_step) for i in range(end_count)]
else:
end_indices = []
indices = start_indices + middle_indices + end_indices
# 去重并排序
indices = sorted(list(set(indices)))
# 确保索引在有效范围内
indices = [idx for idx in indices if 0 <= idx < total_frames]
# 如果去重后数量不足,补充帧
while len(indices) < target_frames and len(indices) < total_frames:
# 找到最大的间隔并补充
if len(indices) == 0:
indices.append(0)
elif len(indices) == 1:
if indices[0] < total_frames - 1:
indices.append(total_frames - 1)
else:
break
else:
max_gap = 0
insert_pos = 0
for i in range(len(indices) - 1):
gap = indices[i + 1] - indices[i]
if gap > max_gap:
max_gap = gap
insert_pos = i + 1
insert_value = (indices[i] + indices[i + 1]) // 2
if max_gap > 1:
indices.insert(insert_pos, insert_value)
indices.sort()
else:
# 如果没有大间隔,在两端补充
if indices[0] > 0:
indices.insert(0, indices[0] - 1)
elif indices[-1] < total_frames - 1:
indices.append(min(indices[-1] + 1, total_frames - 1))
else:
break
return indices[:target_frames]
def load_video_frames(self, video_path: str, indices: List[int]) -> List[torch.Tensor]:
"""
从视频文件中加载指定索引的帧
Args:
video_path: 视频文件路径
indices: 要加载的帧索引列表
Returns:
帧张量列表
"""
# 路径预处理
video_path = video_path.strip().strip('"').strip("'")
video_path = video_path.replace("\\", "/")
if not os.path.isfile(video_path):
raise FileNotFoundError(f"视频文件不存在: {video_path}")
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"无法打开视频文件: {video_path}")
# 为了保持原始顺序,先按索引顺序读取并存储到字典中
frame_dict = {}
sorted_indices = sorted(set(indices)) # 去重并排序以提高效率
for target_idx in sorted_indices:
# 跳转到目标帧
cap.set(cv2.CAP_PROP_POS_FRAMES, target_idx)
ret, frame = cap.read()
if not ret:
print(f"警告: 无法读取第 {target_idx} 帧")
continue
# 转换BGR到RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# 转换为PIL图像
pil_image = Image.fromarray(frame_rgb)
# 转换为tensor
tensor = pil2tensor(pil_image)
frame_dict[target_idx] = tensor
cap.release()
if len(frame_dict) == 0:
raise RuntimeError("未能从视频中加载任何帧")
# 按照原始indices顺序返回帧
frames = [frame_dict[idx] for idx in indices if idx in frame_dict]
return frames
def load_image_sequence_frames(self, images: torch.Tensor, indices: List[int]) -> List[torch.Tensor]:
"""
从图片序列中提取指定索引的帧
Args:
images: 图片批次张量 [batch, height, width, channels]
indices: 要提取的帧索引列表
Returns:
帧张量列表
"""
frames = []
for idx in indices:
if 0 <= idx < len(images):
frames.append(images[idx])
else:
print(f"警告: 索引 {idx} 超出图片序列范围 [0, {len(images)-1}]")
return frames
def load_frames(
self,
video_path: str,
target_frames: int,
mode: str,
images: Optional[torch.Tensor] = None
) -> Tuple[torch.Tensor]:
"""
加载并抽取帧
Args:
video_path: 视频文件路径
target_frames: 目标帧数
mode: 抽取模式
images: 可选的图片序列输入
Returns:
抽取的帧批次张量
"""
# 优先使用图片序列输入
if images is not None:
total_frames = len(images)
print(f"📸 从图片序列中抽取帧: 总帧数={total_frames}, 目标帧数={target_frames}, 模式={mode}")
indices = self.calculate_frame_indices(total_frames, target_frames, mode)
print(f"📊 计算得到的帧索引: {indices}")
# 按照indices的顺序提取帧(保持计算出的顺序)
frame_tensors = self.load_image_sequence_frames(images, indices)
elif video_path and video_path.strip():
# 使用视频文件
video_path = video_path.strip().strip('"').strip("'")
# 先获取视频总帧数
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise RuntimeError(f"无法打开视频文件: {video_path}")
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = cap.get(cv2.CAP_PROP_FPS)
cap.release()
print(f"🎬 从视频中抽取帧: 文件={video_path}, 总帧数={total_frames}, FPS={fps:.2f}, 目标帧数={target_frames}, 模式={mode}")
indices = self.calculate_frame_indices(total_frames, target_frames, mode)
print(f"📊 计算得到的帧索引: {indices}")
frame_tensors = self.load_video_frames(video_path, indices)
else:
raise ValueError("必须提供视频路径或图片序列输入")
if len(frame_tensors) == 0:
raise RuntimeError("未能加载任何帧")
# 将所有帧堆叠成批次
batch_tensor = torch.stack(frame_tensors, dim=0)
frames_count = len(frame_tensors)
print(f"✅ 成功加载 {frames_count} 帧,输出形状: {batch_tensor.shape}")
return (batch_tensor, frames_count)
# 节点映射
NODE_CLASS_MAPPINGS = {
"Extract_Video_Frames_UTK": Extract_Video_Frames_UTK
}
NODE_DISPLAY_NAME_MAPPINGS = {
"Extract_Video_Frames_UTK": "Extract Video Frames (UTK)"
}
+212
View File
@@ -0,0 +1,212 @@
"""
Show Any Node for ComfyUI Universal Toolkit
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Display any type of input data in a text field, showing data type and content.
Acts as a passthrough node for debugging and inspection.
Based on comfyui-easy-use's showAnything node implementation.
:copyright: (c) 2024 by May
:license: MIT, see LICENSE for more details.
"""
import json
import torch
import numpy as np
from typing import Any, List
# Import AnyType for accepting any input type
try:
from comfy.comfy_types.node_typing import IO
ANY_TYPE = IO.ANY
except ImportError:
try:
from comfy_extras.nodes_custom_sampler import AnyType
ANY_TYPE = AnyType("*")
except ImportError:
from .any_type import AnyType
ANY_TYPE = AnyType("*")
class ShowAny_UTK:
"""
显示任意类型数据的节点
功能:
- 接受任何类型的输入数据
- 在文本框中显示数据类型和数据内容
- 直接输出输入的数据(不做任何修改)
- 用于调试和查看数据流
参考实现:comfyui-easy-use 的 showAnything 节点
"""
CATEGORY = "UniversalToolkit/Tools"
RETURN_TYPES = (ANY_TYPE,)
RETURN_NAMES = ("data",)
FUNCTION = "show_any"
INPUT_IS_LIST = True
OUTPUT_NODE = True
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"data": (ANY_TYPE, {
"tooltip": "输入任意类型的数据"
}),
},
"hidden": {
"unique_id": "UNIQUE_ID",
"extra_pnginfo": "EXTRA_PNGINFO",
}
}
def format_data(self, data: Any) -> str:
"""
格式化数据为可读的字符串
Args:
data: 要格式化的数据
Returns:
格式化后的字符串
"""
# 获取数据类型
data_type = type(data).__name__
# 根据不同类型格式化数据
if data is None:
return f"Type: NoneType\nValue: None"
elif isinstance(data, str):
return f"Type: string\nValue: {data}"
elif isinstance(data, (int, float)):
return f"Type: {data_type}\nValue: {data}"
elif isinstance(data, bool):
return f"Type: boolean\nValue: {data}"
elif isinstance(data, (list, tuple)):
# 列表或元组
try:
# 尝试转换为JSON格式
json_str = json.dumps(data, ensure_ascii=False, indent=2)
return f"Type: {data_type}\nLength: {len(data)}\nValue:\n{json_str}"
except (TypeError, ValueError):
# 如果无法序列化为JSON,显示repr
return f"Type: {data_type}\nLength: {len(data)}\nValue: {repr(data)}"
elif isinstance(data, dict):
# 字典
try:
json_str = json.dumps(data, ensure_ascii=False, indent=2)
return f"Type: dict\nKeys: {len(data)}\nValue:\n{json_str}"
except (TypeError, ValueError):
return f"Type: dict\nKeys: {len(data)}\nValue: {repr(data)}"
elif isinstance(data, torch.Tensor):
# PyTorch Tensor
shape = list(data.shape)
dtype = str(data.dtype)
device = str(data.device)
min_val = float(data.min().item()) if data.numel() > 0 else None
max_val = float(data.max().item()) if data.numel() > 0 else None
info = f"Type: torch.Tensor\nShape: {shape}\nDtype: {dtype}\nDevice: {device}"
if min_val is not None and max_val is not None:
info += f"\nMin: {min_val:.6f}\nMax: {max_val:.6f}"
info += f"\nNumel: {data.numel()}"
return info
elif isinstance(data, np.ndarray):
# NumPy Array
shape = data.shape
dtype = str(data.dtype)
min_val = float(data.min()) if data.size > 0 else None
max_val = float(data.max()) if data.size > 0 else None
info = f"Type: numpy.ndarray\nShape: {shape}\nDtype: {dtype}"
if min_val is not None and max_val is not None:
info += f"\nMin: {min_val:.6f}\nMax: {max_val:.6f}"
info += f"\nSize: {data.size}"
return info
else:
# 其他类型,尝试使用repr
try:
repr_str = repr(data)
# 限制长度,避免过长
if len(repr_str) > 500:
repr_str = repr_str[:500] + "..."
return f"Type: {data_type}\nValue: {repr_str}"
except Exception:
return f"Type: {data_type}\nValue: <无法显示>"
def show_any(self, unique_id=None, extra_pnginfo=None, **kwargs) -> dict:
"""
显示任意类型的数据
Args:
unique_id: 节点的唯一ID(用于保存工作流)
extra_pnginfo: 额外的PNG信息(用于保存工作流)
**kwargs: 输入的数据(任意类型)
Returns:
dict: 包含ui显示和结果的字典
"""
values = []
if "data" in kwargs:
for val in kwargs['data']:
try:
if isinstance(val, str):
values.append(val)
elif isinstance(val, list):
values = val
elif isinstance(val, (int, float, bool)):
values.append(str(val))
elif isinstance(val, torch.Tensor):
# 处理torch.Tensor(IMAGE类型)
shape = list(val.shape)
values.append(f"torch.Tensor(shape={shape}, dtype={val.dtype})")
else:
val = json.dumps(val)
values.append(str(val))
except Exception:
values.append(str(val))
pass
# 保存到工作流中(用于加载工作流时恢复显示)
if not extra_pnginfo:
pass
elif not isinstance(extra_pnginfo, list) or len(extra_pnginfo) == 0:
pass
elif (not isinstance(extra_pnginfo[0], dict) or "workflow" not in extra_pnginfo[0]):
pass
else:
workflow = extra_pnginfo[0]["workflow"]
if unique_id and isinstance(unique_id, list) and len(unique_id) > 0:
node = next((x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), None)
if node:
node["widgets_values"] = [values]
# 返回结果(完全按照comfyui-easy-use格式)
if isinstance(values, list) and len(values) == 1:
return {"ui": {"text": values}, "result": (values[0],)}
else:
return {"ui": {"text": values}, "result": (values,)}
# 节点映射
NODE_CLASS_MAPPINGS = {
"ShowAny_UTK": ShowAny_UTK
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ShowAny_UTK": "Show Any (UTK)"
}
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "universaltoolkit"
description = "A comprehensive toolkit based on ComfyUI, providing image, mask, audio, and tools nodes, fully modular and v3 compatible."
version = "1.4.8"
version = "1.4.9"
license = {file = "LICENSE"}
dependencies = [
"torch>=1.9.0",
+61
View File
@@ -0,0 +1,61 @@
import { app } from "../../scripts/app.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
app.registerExtension({
name: "ShowAny_UTK",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "ShowAny_UTK") {
function populate(text) {
if (!text || !Array.isArray(text)) {
return;
}
if (this.widgets) {
const pos = this.widgets.findIndex((w) => w.name === "text");
if (pos !== -1) {
for (let i = pos; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = pos;
}
}
for (const list of text) {
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
w.value = list;
}
requestAnimationFrame(() => {
const sz = this.computeSize();
if (sz[0] < this.size[0]) {
sz[0] = this.size[0];
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1];
}
this.onResize?.(sz);
app.graph.setDirtyCanvas(true, false);
});
}
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
if (message && message.text) {
populate.call(this, message.text);
}
};
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values);
}
};
}
}
});