修复桥接预览图像被覆盖的bug
This commit is contained in:
+107
-161
@@ -1,4 +1,6 @@
|
||||
from .md import *
|
||||
import json
|
||||
|
||||
CATEGORY_TYPE = "🎈LAOGOU/Utils"
|
||||
|
||||
def execute_command_with_realtime_output(cmd, cwd, message_path, clear_first=True, start_message=""):
|
||||
@@ -61,31 +63,10 @@ def execute_command_with_realtime_output(cmd, cwd, message_path, clear_first=Tru
|
||||
})
|
||||
return False, str(e)
|
||||
|
||||
pb_id_cnt = time.time()
|
||||
preview_bridge_image_id_map = {}
|
||||
preview_bridge_image_name_map = {}
|
||||
# 缓存相关的全局变量
|
||||
preview_bridge_cache = {}
|
||||
preview_bridge_last_mask_cache = {}
|
||||
# 存储每个节点的图片hash,用于检测图片内容变化
|
||||
preview_bridge_image_hashes = {}
|
||||
|
||||
def set_previewbridge_image(node_id, file, item):
|
||||
global pb_id_cnt
|
||||
|
||||
if file in preview_bridge_image_name_map:
|
||||
pb_id = preview_bridge_image_name_map[node_id, file]
|
||||
if pb_id.startswith(f"${node_id}"):
|
||||
return pb_id
|
||||
|
||||
pb_id = f"${node_id}-{pb_id_cnt}"
|
||||
|
||||
preview_bridge_image_id_map[pb_id] = (file, item)
|
||||
preview_bridge_image_name_map[node_id, file] = (pb_id, item)
|
||||
|
||||
pb_id_cnt += 1
|
||||
|
||||
return pb_id
|
||||
|
||||
|
||||
class CachePreviewBridge:
|
||||
@classmethod
|
||||
@@ -122,66 +103,57 @@ class CachePreviewBridge:
|
||||
except:
|
||||
return None
|
||||
@staticmethod
|
||||
def load_image(pb_id):
|
||||
clipspace_dir = os.path.join(folder_paths.get_input_directory(), "clipspace")
|
||||
files = [f for f in os.listdir(clipspace_dir)
|
||||
if f.lower().startswith('clipspace') and f.lower().endswith('.png')]
|
||||
|
||||
# 初始化默认值
|
||||
def load_image_from_fileinfo(file_info_json):
|
||||
"""从 JSON 文件信息加载图片"""
|
||||
# 初始化默认值(64*64 遮罩表示无效遮罩)
|
||||
image = torch.zeros((1, 512, 512, 3), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((512, 512), dtype=torch.float32, device="cpu")
|
||||
mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu")
|
||||
ui_item = {
|
||||
"filename": 'empty.png',
|
||||
"subfolder": '',
|
||||
"type": 'temp'
|
||||
}
|
||||
|
||||
if files:
|
||||
latest_file = max(files, key=lambda f: os.path.getmtime(os.path.join(clipspace_dir, f)))
|
||||
latest_path = os.path.join(clipspace_dir, latest_file)
|
||||
latest_mtime = os.path.getmtime(latest_path)
|
||||
if not file_info_json:
|
||||
final_mask = mask.unsqueeze(0) if len(mask.shape) == 2 else mask
|
||||
return image, final_mask, ui_item
|
||||
|
||||
try:
|
||||
# 只支持 JSON 格式
|
||||
file_info = json.loads(file_info_json)
|
||||
filename = file_info.get('filename')
|
||||
subfolder = file_info.get('subfolder', '')
|
||||
file_type = file_info.get('type', 'input')
|
||||
|
||||
current_path = None
|
||||
if pb_id in preview_bridge_image_id_map:
|
||||
current_path, ui_item = preview_bridge_image_id_map[pb_id]
|
||||
current_mtime = os.path.getmtime(current_path) if os.path.exists(current_path) else 0
|
||||
if not filename:
|
||||
final_mask = mask.unsqueeze(0) if len(mask.shape) == 2 else mask
|
||||
return image, final_mask, ui_item
|
||||
|
||||
if current_path is None or latest_mtime > current_mtime:
|
||||
preview_dir = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge')
|
||||
os.makedirs(preview_dir, exist_ok=True)
|
||||
|
||||
new_filename = f"PB-{os.path.splitext(latest_file)[0]}.png"
|
||||
new_path = os.path.join(preview_dir, new_filename)
|
||||
|
||||
# 检查是否存在同名文件
|
||||
if os.path.exists(new_path):
|
||||
if pb_id in preview_bridge_image_id_map:
|
||||
current_path, ui_item = preview_bridge_image_id_map[pb_id]
|
||||
if os.path.exists(current_path):
|
||||
new_path = current_path
|
||||
else:
|
||||
import shutil
|
||||
shutil.copy2(latest_path, new_path)
|
||||
ui_item = {
|
||||
"filename": new_filename,
|
||||
"subfolder": 'PreviewBridge',
|
||||
"type": 'temp'
|
||||
}
|
||||
preview_bridge_image_id_map[pb_id] = (new_path, ui_item)
|
||||
else:
|
||||
import shutil
|
||||
shutil.copy2(latest_path, new_path)
|
||||
ui_item = {
|
||||
"filename": new_filename,
|
||||
"subfolder": 'PreviewBridge',
|
||||
"type": 'temp'
|
||||
}
|
||||
preview_bridge_image_id_map[pb_id] = (new_path, ui_item)
|
||||
|
||||
current_path = new_path
|
||||
# 构建文件路径
|
||||
if file_type == 'input':
|
||||
base_dir = folder_paths.get_input_directory()
|
||||
elif file_type == 'output':
|
||||
base_dir = folder_paths.get_output_directory()
|
||||
elif file_type == 'temp':
|
||||
base_dir = folder_paths.get_temp_directory()
|
||||
else:
|
||||
base_dir = folder_paths.get_input_directory()
|
||||
|
||||
try:
|
||||
i = Image.open(current_path)
|
||||
if subfolder:
|
||||
file_path = os.path.join(base_dir, subfolder, filename)
|
||||
else:
|
||||
file_path = os.path.join(base_dir, filename)
|
||||
|
||||
# 更新 ui_item
|
||||
ui_item = {
|
||||
"filename": filename,
|
||||
"subfolder": subfolder,
|
||||
"type": file_type
|
||||
}
|
||||
|
||||
# 加载图片
|
||||
if os.path.exists(file_path):
|
||||
i = Image.open(file_path)
|
||||
i = ImageOps.exif_transpose(i)
|
||||
image = i.convert("RGB")
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
@@ -192,112 +164,86 @@ class CachePreviewBridge:
|
||||
mask = 1. - torch.from_numpy(mask)
|
||||
else:
|
||||
mask = torch.zeros((image.shape[1], image.shape[2]), dtype=torch.float32, device="cpu")
|
||||
except Exception as e:
|
||||
print(f"Error loading image: {e}")
|
||||
# 出错时使用默认值
|
||||
except Exception as e:
|
||||
print(f"[CachePreviewBridge] 加载图片失败: {e}")
|
||||
|
||||
final_mask = mask.unsqueeze(0) if len(mask.shape) == 2 else mask
|
||||
return image, final_mask, ui_item
|
||||
|
||||
def doit(self, image, unique_id, images=None, extra_pnginfo=None):
|
||||
# 智能决定使用模式:基于图片内容hash检测
|
||||
current_hash = self.calculate_image_hash(images) if images is not None else None
|
||||
last_hash = preview_bridge_image_hashes.get(unique_id)
|
||||
|
||||
# 检测图片是否发生改变
|
||||
has_changed = current_hash != last_hash
|
||||
has_valid_images = images is not None
|
||||
|
||||
# 决定使用模式:
|
||||
# - 如果没有输入图片,使用 cache 模式
|
||||
# - 如果有输入图片且内容发生改变,使用 input 模式
|
||||
# - 如果有输入图片但内容没有改变,使用 cache 模式
|
||||
if not has_valid_images:
|
||||
use_cache = True
|
||||
mode_reason = "No images input"
|
||||
elif has_changed:
|
||||
use_cache = False
|
||||
mode_reason = f"Images changed (hash: {last_hash} -> {current_hash})"
|
||||
else:
|
||||
use_cache = True
|
||||
mode_reason = f"Images unchanged (hash: {current_hash})"
|
||||
|
||||
print(f"[CachePreviewBridge] Node {unique_id}: {mode_reason}, using {'cache' if use_cache else 'input'} mode")
|
||||
|
||||
# 更新存储的hash值
|
||||
if current_hash is not None:
|
||||
preview_bridge_image_hashes[unique_id] = current_hash
|
||||
|
||||
# 检查是否有新的剪贴板图片需要更新缓存
|
||||
if images is None and image and image not in preview_bridge_image_id_map:
|
||||
print("Component value changed, updating cache")
|
||||
# 生成新的pb_id并更新缓存
|
||||
pb_id = f"${unique_id}-{time.time()}"
|
||||
pixels, mask, path_item = CachePreviewBridge.load_image(pb_id)
|
||||
if path_item["type"] != "temp" or path_item["filename"] != "empty.png":
|
||||
return {
|
||||
"ui": {"images": [path_item]},
|
||||
"result": (pixels, mask),
|
||||
}
|
||||
|
||||
# 原有的判断逻辑
|
||||
if images is None and not image:
|
||||
empty_image = torch.zeros((1, 512, 512, 3), dtype=torch.float32, device="cpu")
|
||||
empty_mask = torch.zeros((1, 512, 512), dtype=torch.float32, device="cpu")
|
||||
return {
|
||||
"ui": {"images": []},
|
||||
"result": (empty_image, empty_mask),
|
||||
}
|
||||
|
||||
if use_cache:
|
||||
if not image:
|
||||
|
||||
empty_image = torch.zeros((1, 512, 512, 3), dtype=torch.float32, device="cpu")
|
||||
empty_mask = torch.zeros((1, 512, 512), dtype=torch.float32, device="cpu")
|
||||
return {
|
||||
"ui": {"images": []},
|
||||
"result": (empty_image, empty_mask),
|
||||
}
|
||||
|
||||
if image.startswith('$'):
|
||||
node_id = image.split('-')[0][1:]
|
||||
related_pb_ids = [pb_id for pb_id in preview_bridge_image_id_map.keys()
|
||||
if pb_id.startswith(f"${node_id}-")]
|
||||
|
||||
if related_pb_ids:
|
||||
latest_pb_id = max(related_pb_ids, key=lambda x: float(x.split('-')[1]))
|
||||
image = latest_pb_id
|
||||
pixels, mask, path_item = CachePreviewBridge.load_image(image)
|
||||
return {
|
||||
"ui": {"images": [path_item]},
|
||||
"result": (pixels, mask),
|
||||
}
|
||||
|
||||
# 非缓存模式或需要创建新缓存
|
||||
"""
|
||||
简化逻辑:
|
||||
1. 如果有 images 输入,保存并返回
|
||||
2. 如果没有 images 输入但有 image (文件信息),从文件加载
|
||||
3. 否则返回空图像
|
||||
"""
|
||||
# 情况1: 有图像输入 - 从 clipspace 加载遮罩,或使用输入的图像
|
||||
if images is not None:
|
||||
mask = torch.zeros((1, images.shape[1], images.shape[2]), dtype=torch.float32, device="cpu")
|
||||
# 更新 hash
|
||||
current_hash = self.calculate_image_hash(images)
|
||||
preview_bridge_image_hashes[unique_id] = current_hash
|
||||
|
||||
# 默认遮罩(64*64 表示无效遮罩)
|
||||
mask = torch.zeros((1, 64, 64), dtype=torch.float32, device="cpu")
|
||||
ui_item = None
|
||||
|
||||
# 如果有文件信息,尝试从文件加载遮罩
|
||||
if image and image.strip():
|
||||
_, loaded_mask, path_item = CachePreviewBridge.load_image_from_fileinfo(image)
|
||||
|
||||
# 如果成功加载了遮罩,使用它
|
||||
if path_item["filename"] != 'empty.png':
|
||||
# 调整遮罩尺寸以匹配图像
|
||||
if loaded_mask.shape[-2:] == (images.shape[1], images.shape[2]):
|
||||
mask = loaded_mask
|
||||
ui_item = path_item # 保存 clipspace 文件信息用于 UI 预览
|
||||
|
||||
# 如果有 clipspace 文件,直接返回 clipspace 的预览
|
||||
if ui_item:
|
||||
return {
|
||||
"ui": {"images": [ui_item]},
|
||||
"result": (images, mask),
|
||||
}
|
||||
|
||||
# 否则保存新预览图并返回默认的 64*64 遮罩
|
||||
res = PreviewImage().save_images(
|
||||
images,
|
||||
filename_prefix=f"PreviewBridge/PB-{unique_id}-",
|
||||
extra_pnginfo=extra_pnginfo
|
||||
)
|
||||
|
||||
image2 = res['ui']['images']
|
||||
pixels = images
|
||||
|
||||
path = os.path.join(folder_paths.get_temp_directory(), 'PreviewBridge', image2[0]['filename'])
|
||||
pb_id = set_previewbridge_image(unique_id, path, image2[0])
|
||||
|
||||
preview_bridge_cache[unique_id] = (images, image2)
|
||||
# 缓存结果
|
||||
preview_bridge_cache[unique_id] = (images, res['ui']['images'])
|
||||
|
||||
return {
|
||||
"ui": {"images": image2},
|
||||
"result": (pixels, mask),
|
||||
"ui": {"images": res['ui']['images']},
|
||||
"result": (images, mask),
|
||||
}
|
||||
|
||||
# 如果走到这里,说明既没有有效的缓存也没有新的图像输入
|
||||
# 情况2: 没有图像输入,但有文件信息 - 从文件加载
|
||||
if image and image.strip():
|
||||
pixels, mask, path_item = CachePreviewBridge.load_image_from_fileinfo(image)
|
||||
|
||||
# 检查是否成功加载
|
||||
if path_item["filename"] != 'empty.png':
|
||||
return {
|
||||
"ui": {"images": [path_item]},
|
||||
"result": (pixels, mask),
|
||||
}
|
||||
|
||||
# 情况3: 检查是否有缓存
|
||||
if unique_id in preview_bridge_cache:
|
||||
cached_images, cached_ui = preview_bridge_cache[unique_id]
|
||||
# 返回 64*64 遮罩表示无效遮罩
|
||||
mask = torch.zeros((1, 64, 64), dtype=torch.float32, device="cpu")
|
||||
return {
|
||||
"ui": {"images": cached_ui},
|
||||
"result": (cached_images, mask),
|
||||
}
|
||||
|
||||
# 情况4: 没有任何数据 - 返回空图像和 64*64 遮罩
|
||||
empty_image = torch.zeros((1, 512, 512, 3), dtype=torch.float32, device="cpu")
|
||||
empty_mask = torch.zeros((1, 512, 512), dtype=torch.float32, device="cpu")
|
||||
empty_mask = torch.zeros((1, 64, 64), dtype=torch.float32, device="cpu")
|
||||
return {
|
||||
"ui": {"images": []},
|
||||
"result": (empty_image, empty_mask),
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_lg_tools"
|
||||
description = "This is a toolset designed for ComfyUI by LAOGOU-666, providing a series of practical image processing and operation nodes, making our operation more intuitive and convenient"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
license = { text = "MIT License" }
|
||||
dependencies = ["opencv-contrib-python"]
|
||||
|
||||
|
||||
@@ -2,71 +2,6 @@ import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { MultiButtonWidget } from "./multi_button_widget.js";
|
||||
|
||||
function getFileItem(baseType, path) {
|
||||
try {
|
||||
let pathType = baseType;
|
||||
|
||||
if (path.endsWith("[output]")) {
|
||||
pathType = "output";
|
||||
path = path.slice(0, -9);
|
||||
} else if (path.endsWith("[input]")) {
|
||||
pathType = "input";
|
||||
path = path.slice(0, -8);
|
||||
} else if (path.endsWith("[temp]")) {
|
||||
pathType = "temp";
|
||||
path = path.slice(0, -7);
|
||||
}
|
||||
|
||||
const subfolder = path.substring(0, path.lastIndexOf('/'));
|
||||
const filename = path.substring(path.lastIndexOf('/') + 1);
|
||||
|
||||
return {
|
||||
filename: filename,
|
||||
subfolder: subfolder,
|
||||
type: pathType
|
||||
};
|
||||
}
|
||||
catch(exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function loadImageFromUrl(image, node_id, v, need_to_load) {
|
||||
let item = getFileItem('temp', v);
|
||||
|
||||
if(item) {
|
||||
let params = `?node_id=${node_id}&filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`;
|
||||
|
||||
let res = await api.fetchApi('/impact/set/pb_id_image'+params, { cache: "no-store" });
|
||||
if(res.status == 200) {
|
||||
let pb_id = await res.text();
|
||||
if(need_to_load) {;
|
||||
image.src = api.apiURL(`/view?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`);
|
||||
}
|
||||
return pb_id;
|
||||
}
|
||||
else {
|
||||
return `$${node_id}-0`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return `$${node_id}-0`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadImageFromId(image, v) {
|
||||
let res = await api.fetchApi('/impact/get/pb_id_image?id='+v, { cache: "no-store" });
|
||||
if(res.status == 200) {
|
||||
let item = await res.json();
|
||||
image.src = api.apiURL(`/view?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function loadLatestImage(node, folder_type) {
|
||||
// 获取指定目录中的最新图片
|
||||
const res = await api.fetchApi(`/lg/get/latest_image?type=${folder_type}`, { cache: "no-store" });
|
||||
@@ -76,22 +11,22 @@ async function loadLatestImage(node, folder_type) {
|
||||
const imageWidget = node.widgets.find(w => w.name === 'image');
|
||||
if (!imageWidget) return false;
|
||||
|
||||
// 创建映射
|
||||
const params = `?node_id=${node.id}&filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`;
|
||||
const mapRes = await api.fetchApi('/impact/set/pb_id_image' + params, { cache: "no-store" });
|
||||
// 保存文件信息的JSON字符串
|
||||
const fileInfo = JSON.stringify({
|
||||
filename: item.filename,
|
||||
subfolder: item.subfolder || '',
|
||||
type: item.type || 'temp'
|
||||
});
|
||||
|
||||
if (mapRes.status == 200) {
|
||||
const pb_id = await mapRes.text();
|
||||
imageWidget._lock = true;
|
||||
imageWidget._value = pb_id;
|
||||
imageWidget._lock = false;
|
||||
|
||||
// 加载新图像
|
||||
const image = new Image();
|
||||
image.src = api.apiURL(`/view?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`);
|
||||
node._imgs = [image];
|
||||
return true;
|
||||
}
|
||||
// 保存到节点属性和 widget
|
||||
node._latestFileInfo = fileInfo;
|
||||
imageWidget.value = fileInfo;
|
||||
|
||||
// 加载并显示图像
|
||||
const image = new Image();
|
||||
image.src = api.apiURL(`/view?filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder || ''}`);
|
||||
node._imgs = [image];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -104,6 +39,19 @@ app.registerExtension({
|
||||
if (node.comfyClass !== "CachePreviewBridge") return;
|
||||
|
||||
let imageWidget = node.widgets.find(w => w.name === 'image');
|
||||
if (!imageWidget) return;
|
||||
|
||||
// 存储当前文件信息
|
||||
node._latestFileInfo = null;
|
||||
|
||||
// 重写序列化方法,确保执行时使用最新值
|
||||
imageWidget.serializeValue = function(nodeId, widgetIndex) {
|
||||
if (node._latestFileInfo) {
|
||||
return node._latestFileInfo;
|
||||
}
|
||||
return this.value || "";
|
||||
};
|
||||
|
||||
node._imgs = [new Image()];
|
||||
node.imageIndex = 0;
|
||||
|
||||
@@ -135,170 +83,46 @@ app.registerExtension({
|
||||
]));
|
||||
refreshWidget.serialize = false;
|
||||
|
||||
// 添加粘贴功能
|
||||
node.pasteFile = async function(file) {
|
||||
const uploadWidget = node.widgets.find(w => w.name === 'image');
|
||||
if (!uploadWidget) return;
|
||||
|
||||
// 上传到 clipspace 目录
|
||||
const fileName = `clipspace_${Date.now()}.png`;
|
||||
const formData = new FormData();
|
||||
formData.append('image', file, fileName);
|
||||
formData.append('type', 'input');
|
||||
formData.append('subfolder', 'clipspace');
|
||||
|
||||
try {
|
||||
const resp = await fetch('/upload/image', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data.name) {
|
||||
// 创建一个临时的 item 对象
|
||||
const item = {
|
||||
filename: data.name,
|
||||
type: 'input',
|
||||
subfolder: 'clipspace'
|
||||
};
|
||||
|
||||
// 使用 set/pb_id_image 创建映射
|
||||
const params = `?node_id=${node.id}&filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`;
|
||||
const res = await api.fetchApi('/impact/set/pb_id_image'+params, { cache: "no-store" });
|
||||
|
||||
if (res.status == 200) {
|
||||
const pb_id = await res.text();
|
||||
imageWidget._lock = true;
|
||||
imageWidget._value = pb_id;
|
||||
imageWidget._lock = false;
|
||||
|
||||
// 加载图像
|
||||
const image = new Image();
|
||||
if (await loadImageFromId(image, pb_id)) {
|
||||
node._imgs = [image];
|
||||
app.graph.setDirtyCanvas(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to upload file:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 简化后的 value 属性重写
|
||||
Object.defineProperty(imageWidget, 'value', {
|
||||
async set(v) {
|
||||
if (imageWidget._lock) return;
|
||||
|
||||
const stackTrace = new Error().stack;
|
||||
if (stackTrace.includes('presetText.js')) return;
|
||||
|
||||
var image = new Image();
|
||||
|
||||
// 处理节点反馈的图像
|
||||
if (v && v.constructor === String && v.startsWith('$')) {
|
||||
let need_to_load = node._imgs[0].src === '';
|
||||
if (await loadImageFromId(image, v, need_to_load)) {
|
||||
imageWidget._value = v;
|
||||
if (node._imgs[0].src === '') {
|
||||
node._imgs = [image];
|
||||
}
|
||||
} else {
|
||||
imageWidget._value = `$${node.id}-0`;
|
||||
}
|
||||
}
|
||||
// 处理从剪贴板导入的图像
|
||||
else {
|
||||
imageWidget._lock = true;
|
||||
imageWidget._value = await loadImageFromUrl(image, node.id, v, false);
|
||||
imageWidget._lock = false;
|
||||
}
|
||||
},
|
||||
get() {
|
||||
if (imageWidget._value === undefined) {
|
||||
imageWidget._value = `$${node.id}-0`;
|
||||
}
|
||||
return imageWidget._value;
|
||||
}
|
||||
});
|
||||
|
||||
// 重写 imgs 属性,处理来自 MaskEditor 的粘贴
|
||||
Object.defineProperty(node, 'imgs', {
|
||||
set(v) {
|
||||
const stackTrace = new Error().stack;
|
||||
if (!v || v.length === 0) return;
|
||||
|
||||
const stackTrace = new Error().stack;
|
||||
|
||||
// 来自 MaskEditor 的粘贴
|
||||
if (stackTrace.includes('pasteFromClipspace')) {
|
||||
// 检查是否来自ComfyUI系统的clipspace
|
||||
if (v[0].src && v[0].src.includes('/api/view')) {
|
||||
(async () => {
|
||||
try {
|
||||
// 从原始图像创建 blob
|
||||
const response = await fetch(v[0].src);
|
||||
const blob = await response.blob();
|
||||
|
||||
// 使用相同的命名方式
|
||||
const fileName = `clipspace_${Date.now()}.png`;
|
||||
const formData = new FormData();
|
||||
formData.append('image', blob, fileName);
|
||||
formData.append('type', 'input');
|
||||
formData.append('subfolder', 'clipspace');
|
||||
|
||||
// 上传到 clipspace 目录
|
||||
const resp = await fetch('/upload/image', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
if (v[0] && v[0].src) {
|
||||
const urlParts = v[0].src.split("?");
|
||||
if (urlParts.length > 1) {
|
||||
const sp = new URLSearchParams(urlParts[1]);
|
||||
const filename = sp.get('filename');
|
||||
const type = sp.get('type') || 'input';
|
||||
const subfolder = sp.get('subfolder') || '';
|
||||
|
||||
if (filename) {
|
||||
// 保存文件信息的JSON字符串
|
||||
const fileInfo = JSON.stringify({
|
||||
filename: filename,
|
||||
subfolder: subfolder,
|
||||
type: type
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
if (data.name) {
|
||||
// 创建临时 item 对象
|
||||
const item = {
|
||||
filename: data.name,
|
||||
type: 'input',
|
||||
subfolder: 'clipspace'
|
||||
};
|
||||
|
||||
// 创建映射
|
||||
const params = `?node_id=${node.id}&filename=${item.filename}&type=${item.type}&subfolder=${item.subfolder}`;
|
||||
const res = await api.fetchApi('/impact/set/pb_id_image'+params, { cache: "no-store" });
|
||||
|
||||
if (res.status == 200) {
|
||||
const pb_id = await res.text();
|
||||
imageWidget._lock = true;
|
||||
imageWidget._value = pb_id;
|
||||
imageWidget._lock = false;
|
||||
|
||||
// 加载新图像
|
||||
const image = new Image();
|
||||
if (await loadImageFromId(image, pb_id)) {
|
||||
node._imgs = [image];
|
||||
app.graph.setDirtyCanvas(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to process clipspace paste:", error);
|
||||
// 保存到节点属性,序列化时会使用
|
||||
node._latestFileInfo = fileInfo;
|
||||
imageWidget.value = fileInfo;
|
||||
|
||||
// 直接使用传入的图像
|
||||
node._imgs = v;
|
||||
app.graph.setDirtyCanvas(true);
|
||||
|
||||
return;
|
||||
}
|
||||
})();
|
||||
} else {
|
||||
// 原有的处理逻辑
|
||||
let sp = new URLSearchParams(v[0].src.split("?")[1]);
|
||||
let str = "";
|
||||
if (sp.get('subfolder')) {
|
||||
str += sp.get('subfolder') + '/';
|
||||
}
|
||||
str += `${sp.get("filename")} [${sp.get("type")}]`;
|
||||
|
||||
imageWidget.value = str;
|
||||
node._imgs = v;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 其他情况直接设置
|
||||
node._imgs = v;
|
||||
},
|
||||
get() {
|
||||
@@ -306,4 +130,4 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user