From 9f21ff13ae56564091b76d72dbfed172e9fd67aa Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Wed, 6 Aug 2025 23:08:02 +0200 Subject: [PATCH] Add clipspace utils with full backward support Refactored clipspace handling into ClipspaceUtils with validateAndFixClipspace() and safeClipspacePaste() for consistent, defensive logic. Ensures full backward compatibility with all ComfyUI versions and eliminates duplicated code. --- js/SAMDetectorIntegration.js | 34 ++++++++++ js/utils/ClipboardManager.js | 10 ++- js/utils/ClipspaceUtils.js | 99 +++++++++++++++++++++++++++++ src/SAMDetectorIntegration.ts | 41 ++++++++++++ src/utils/ClipboardManager.ts | 9 ++- src/utils/ClipspaceUtils.ts | 114 ++++++++++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 js/utils/ClipspaceUtils.js create mode 100644 src/utils/ClipspaceUtils.ts diff --git a/js/SAMDetectorIntegration.js b/js/SAMDetectorIntegration.js index af183e7..ef10215 100644 --- a/js/SAMDetectorIntegration.js +++ b/js/SAMDetectorIntegration.js @@ -6,6 +6,7 @@ import { uploadCanvasAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.j import { processImageToMask } from "./utils/MaskProcessingUtils.js"; import { convertToImage } from "./utils/ImageUtils.js"; import { updateNodePreview } from "./utils/PreviewUtils.js"; +import { validateAndFixClipspace } from "./utils/ClipspaceUtils.js"; const log = createModuleLogger('SAMDetectorIntegration'); /** * SAM Detector Integration for LayerForge @@ -324,6 +325,8 @@ async function handleSAMDetectorResult(node, resultImage) { node.samOriginalImgSrc = null; } } +// Store original onClipspaceEditorSave function to restore later +let originalOnClipspaceEditorSave = null; // Function to setup SAM Detector hook in menu options export function setupSAMDetectorHook(node, options) { // Hook into "Open in SAM Detector" with delay since Impact Pack adds it asynchronously @@ -347,8 +350,39 @@ export function setupSAMDetectorHook(node, options) { // Set the image to the node for clipspace node.imgs = [uploadResult.imageElement]; node.clipspaceImg = uploadResult.imageElement; + // Ensure proper clipspace structure for updated ComfyUI + if (!ComfyApp.clipspace) { + ComfyApp.clipspace = {}; + } + // Set up clipspace with proper indices + ComfyApp.clipspace.imgs = [uploadResult.imageElement]; + ComfyApp.clipspace.selectedIndex = 0; + ComfyApp.clipspace.combinedIndex = 0; + ComfyApp.clipspace.img_paste_mode = 'selected'; // Copy to ComfyUI clipspace ComfyApp.copyToClipspace(node); + // Override onClipspaceEditorSave to fix clipspace structure before pasteFromClipspace + if (!originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave = ComfyApp.onClipspaceEditorSave; + ComfyApp.onClipspaceEditorSave = function () { + log.debug("SAM Detector onClipspaceEditorSave called, using unified clipspace validation"); + // Use the unified clipspace validation function + const isValid = validateAndFixClipspace(); + if (!isValid) { + log.error("Clipspace validation failed, cannot proceed with paste"); + return; + } + // Call the original function + if (originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave.call(ComfyApp); + } + // Restore the original function after use + if (originalOnClipspaceEditorSave) { + ComfyApp.onClipspaceEditorSave = originalOnClipspaceEditorSave; + originalOnClipspaceEditorSave = null; + } + }; + } // Start monitoring for SAM Detector results startSAMDetectorMonitoring(node); log.info("Canvas automatically sent to clipspace and monitoring started"); diff --git a/js/utils/ClipboardManager.js b/js/utils/ClipboardManager.js index 991ead4..e7fb998 100644 --- a/js/utils/ClipboardManager.js +++ b/js/utils/ClipboardManager.js @@ -1,10 +1,9 @@ import { createModuleLogger } from "./LoggerUtils.js"; import { showNotification, showInfoNotification } from "./NotificationUtils.js"; import { withErrorHandling, createValidationError, createNetworkError, createFileError } from "../ErrorHandler.js"; +import { safeClipspacePaste } from "./ClipspaceUtils.js"; // @ts-ignore import { api } from "../../../scripts/api.js"; -// @ts-ignore -import { ComfyApp } from "../../../scripts/app.js"; const log = createModuleLogger('ClipboardManager'); export class ClipboardManager { constructor(canvas) { @@ -39,7 +38,12 @@ export class ClipboardManager { */ this.tryClipspacePaste = withErrorHandling(async (addMode) => { log.info("Attempting to paste from ComfyUI Clipspace"); - ComfyApp.pasteFromClipspace(this.canvas.node); + // Use the unified clipspace validation and paste function + const pasteSuccess = safeClipspacePaste(this.canvas.node); + if (!pasteSuccess) { + log.debug("Safe clipspace paste failed"); + return false; + } if (this.canvas.node.imgs && this.canvas.node.imgs.length > 0) { const clipspaceImage = this.canvas.node.imgs[0]; if (clipspaceImage && clipspaceImage.src) { diff --git a/js/utils/ClipspaceUtils.js b/js/utils/ClipspaceUtils.js new file mode 100644 index 0000000..bf09afb --- /dev/null +++ b/js/utils/ClipspaceUtils.js @@ -0,0 +1,99 @@ +import { createModuleLogger } from "./LoggerUtils.js"; +// @ts-ignore +import { ComfyApp } from "../../../scripts/app.js"; +const log = createModuleLogger('ClipspaceUtils'); +/** + * Validates and fixes ComfyUI clipspace structure to prevent 'Cannot read properties of undefined' errors + * @returns {boolean} - True if clipspace is valid and ready to use, false otherwise + */ +export function validateAndFixClipspace() { + log.debug("Validating and fixing clipspace structure"); + // Check if clipspace exists + if (!ComfyApp.clipspace) { + log.debug("ComfyUI clipspace is not available"); + return false; + } + // Validate clipspace structure + if (!ComfyApp.clipspace.imgs || ComfyApp.clipspace.imgs.length === 0) { + log.debug("ComfyUI clipspace has no images"); + return false; + } + log.debug("Current clipspace state:", { + hasImgs: !!ComfyApp.clipspace.imgs, + imgsLength: ComfyApp.clipspace.imgs?.length, + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode + }); + // Ensure required indices are set + if (ComfyApp.clipspace.selectedIndex === undefined || ComfyApp.clipspace.selectedIndex === null) { + ComfyApp.clipspace.selectedIndex = 0; + log.debug("Fixed clipspace selectedIndex to 0"); + } + if (ComfyApp.clipspace.combinedIndex === undefined || ComfyApp.clipspace.combinedIndex === null) { + ComfyApp.clipspace.combinedIndex = 0; + log.debug("Fixed clipspace combinedIndex to 0"); + } + if (!ComfyApp.clipspace.img_paste_mode) { + ComfyApp.clipspace.img_paste_mode = 'selected'; + log.debug("Fixed clipspace img_paste_mode to 'selected'"); + } + // Ensure indices are within bounds + const maxIndex = ComfyApp.clipspace.imgs.length - 1; + if (ComfyApp.clipspace.selectedIndex > maxIndex) { + ComfyApp.clipspace.selectedIndex = maxIndex; + log.debug(`Fixed clipspace selectedIndex to ${maxIndex} (max available)`); + } + if (ComfyApp.clipspace.combinedIndex > maxIndex) { + ComfyApp.clipspace.combinedIndex = maxIndex; + log.debug(`Fixed clipspace combinedIndex to ${maxIndex} (max available)`); + } + // Verify the image at combinedIndex exists and has src + const combinedImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!combinedImg || !combinedImg.src) { + log.debug("Image at combinedIndex is missing or has no src, trying to find valid image"); + // Try to use the first available image + for (let i = 0; i < ComfyApp.clipspace.imgs.length; i++) { + if (ComfyApp.clipspace.imgs[i] && ComfyApp.clipspace.imgs[i].src) { + ComfyApp.clipspace.combinedIndex = i; + log.debug(`Fixed combinedIndex to ${i} (first valid image)`); + break; + } + } + // Final check - if still no valid image found + const finalImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!finalImg || !finalImg.src) { + log.error("No valid images found in clipspace after attempting fixes"); + return false; + } + } + log.debug("Final clipspace structure:", { + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode, + imgsLength: ComfyApp.clipspace.imgs?.length, + combinedImgSrc: ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]?.src?.substring(0, 50) + '...' + }); + return true; +} +/** + * Safely calls ComfyApp.pasteFromClipspace after validating clipspace structure + * @param {any} node - The ComfyUI node to paste to + * @returns {boolean} - True if paste was successful, false otherwise + */ +export function safeClipspacePaste(node) { + log.debug("Attempting safe clipspace paste"); + if (!validateAndFixClipspace()) { + log.debug("Clipspace validation failed, cannot paste"); + return false; + } + try { + ComfyApp.pasteFromClipspace(node); + log.debug("Successfully called pasteFromClipspace"); + return true; + } + catch (error) { + log.error("Error calling pasteFromClipspace:", error); + return false; + } +} diff --git a/src/SAMDetectorIntegration.ts b/src/SAMDetectorIntegration.ts index 72e1919..c6aa4b0 100644 --- a/src/SAMDetectorIntegration.ts +++ b/src/SAMDetectorIntegration.ts @@ -7,6 +7,7 @@ import { uploadCanvasAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.j import { processImageToMask } from "./utils/MaskProcessingUtils.js"; import { convertToImage } from "./utils/ImageUtils.js"; import { updateNodePreview } from "./utils/PreviewUtils.js"; +import { validateAndFixClipspace } from "./utils/ClipspaceUtils.js"; import type { ComfyNode } from './types'; const log = createModuleLogger('SAMDetectorIntegration'); @@ -376,6 +377,9 @@ async function handleSAMDetectorResult(node: ComfyNode, resultImage: HTMLImageEl } +// Store original onClipspaceEditorSave function to restore later +let originalOnClipspaceEditorSave: (() => void) | null = null; + // Function to setup SAM Detector hook in menu options export function setupSAMDetectorHook(node: ComfyNode, options: any[]) { // Hook into "Open in SAM Detector" with delay since Impact Pack adds it asynchronously @@ -408,9 +412,46 @@ export function setupSAMDetectorHook(node: ComfyNode, options: any[]) { node.imgs = [uploadResult.imageElement]; (node as any).clipspaceImg = uploadResult.imageElement; + // Ensure proper clipspace structure for updated ComfyUI + if (!ComfyApp.clipspace) { + ComfyApp.clipspace = {}; + } + + // Set up clipspace with proper indices + ComfyApp.clipspace.imgs = [uploadResult.imageElement]; + ComfyApp.clipspace.selectedIndex = 0; + ComfyApp.clipspace.combinedIndex = 0; + ComfyApp.clipspace.img_paste_mode = 'selected'; + // Copy to ComfyUI clipspace ComfyApp.copyToClipspace(node); + // Override onClipspaceEditorSave to fix clipspace structure before pasteFromClipspace + if (!originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave = ComfyApp.onClipspaceEditorSave; + ComfyApp.onClipspaceEditorSave = function() { + log.debug("SAM Detector onClipspaceEditorSave called, using unified clipspace validation"); + + // Use the unified clipspace validation function + const isValid = validateAndFixClipspace(); + if (!isValid) { + log.error("Clipspace validation failed, cannot proceed with paste"); + return; + } + + // Call the original function + if (originalOnClipspaceEditorSave) { + originalOnClipspaceEditorSave.call(ComfyApp); + } + + // Restore the original function after use + if (originalOnClipspaceEditorSave) { + ComfyApp.onClipspaceEditorSave = originalOnClipspaceEditorSave; + originalOnClipspaceEditorSave = null; + } + }; + } + // Start monitoring for SAM Detector results startSAMDetectorMonitoring(node); diff --git a/src/utils/ClipboardManager.ts b/src/utils/ClipboardManager.ts index ecf4ef6..ffc49fb 100644 --- a/src/utils/ClipboardManager.ts +++ b/src/utils/ClipboardManager.ts @@ -1,6 +1,7 @@ import {createModuleLogger} from "./LoggerUtils.js"; import { showNotification, showInfoNotification } from "./NotificationUtils.js"; import { withErrorHandling, createValidationError, createNetworkError, createFileError } from "../ErrorHandler.js"; +import { safeClipspacePaste } from "./ClipspaceUtils.js"; // @ts-ignore import {api} from "../../../scripts/api.js"; @@ -56,7 +57,13 @@ export class ClipboardManager { */ tryClipspacePaste = withErrorHandling(async (addMode: AddMode): Promise => { log.info("Attempting to paste from ComfyUI Clipspace"); - ComfyApp.pasteFromClipspace(this.canvas.node); + + // Use the unified clipspace validation and paste function + const pasteSuccess = safeClipspacePaste(this.canvas.node); + if (!pasteSuccess) { + log.debug("Safe clipspace paste failed"); + return false; + } if (this.canvas.node.imgs && this.canvas.node.imgs.length > 0) { const clipspaceImage = this.canvas.node.imgs[0]; diff --git a/src/utils/ClipspaceUtils.ts b/src/utils/ClipspaceUtils.ts new file mode 100644 index 0000000..7402da4 --- /dev/null +++ b/src/utils/ClipspaceUtils.ts @@ -0,0 +1,114 @@ +import { createModuleLogger } from "./LoggerUtils.js"; +// @ts-ignore +import { ComfyApp } from "../../../scripts/app.js"; + +const log = createModuleLogger('ClipspaceUtils'); + +/** + * Validates and fixes ComfyUI clipspace structure to prevent 'Cannot read properties of undefined' errors + * @returns {boolean} - True if clipspace is valid and ready to use, false otherwise + */ +export function validateAndFixClipspace(): boolean { + log.debug("Validating and fixing clipspace structure"); + + // Check if clipspace exists + if (!ComfyApp.clipspace) { + log.debug("ComfyUI clipspace is not available"); + return false; + } + + // Validate clipspace structure + if (!ComfyApp.clipspace.imgs || ComfyApp.clipspace.imgs.length === 0) { + log.debug("ComfyUI clipspace has no images"); + return false; + } + + log.debug("Current clipspace state:", { + hasImgs: !!ComfyApp.clipspace.imgs, + imgsLength: ComfyApp.clipspace.imgs?.length, + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode + }); + + // Ensure required indices are set + if (ComfyApp.clipspace.selectedIndex === undefined || ComfyApp.clipspace.selectedIndex === null) { + ComfyApp.clipspace.selectedIndex = 0; + log.debug("Fixed clipspace selectedIndex to 0"); + } + + if (ComfyApp.clipspace.combinedIndex === undefined || ComfyApp.clipspace.combinedIndex === null) { + ComfyApp.clipspace.combinedIndex = 0; + log.debug("Fixed clipspace combinedIndex to 0"); + } + + if (!ComfyApp.clipspace.img_paste_mode) { + ComfyApp.clipspace.img_paste_mode = 'selected'; + log.debug("Fixed clipspace img_paste_mode to 'selected'"); + } + + // Ensure indices are within bounds + const maxIndex = ComfyApp.clipspace.imgs.length - 1; + if (ComfyApp.clipspace.selectedIndex > maxIndex) { + ComfyApp.clipspace.selectedIndex = maxIndex; + log.debug(`Fixed clipspace selectedIndex to ${maxIndex} (max available)`); + } + if (ComfyApp.clipspace.combinedIndex > maxIndex) { + ComfyApp.clipspace.combinedIndex = maxIndex; + log.debug(`Fixed clipspace combinedIndex to ${maxIndex} (max available)`); + } + + // Verify the image at combinedIndex exists and has src + const combinedImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!combinedImg || !combinedImg.src) { + log.debug("Image at combinedIndex is missing or has no src, trying to find valid image"); + // Try to use the first available image + for (let i = 0; i < ComfyApp.clipspace.imgs.length; i++) { + if (ComfyApp.clipspace.imgs[i] && ComfyApp.clipspace.imgs[i].src) { + ComfyApp.clipspace.combinedIndex = i; + log.debug(`Fixed combinedIndex to ${i} (first valid image)`); + break; + } + } + + // Final check - if still no valid image found + const finalImg = ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]; + if (!finalImg || !finalImg.src) { + log.error("No valid images found in clipspace after attempting fixes"); + return false; + } + } + + log.debug("Final clipspace structure:", { + selectedIndex: ComfyApp.clipspace.selectedIndex, + combinedIndex: ComfyApp.clipspace.combinedIndex, + img_paste_mode: ComfyApp.clipspace.img_paste_mode, + imgsLength: ComfyApp.clipspace.imgs?.length, + combinedImgSrc: ComfyApp.clipspace.imgs[ComfyApp.clipspace.combinedIndex]?.src?.substring(0, 50) + '...' + }); + + return true; +} + +/** + * Safely calls ComfyApp.pasteFromClipspace after validating clipspace structure + * @param {any} node - The ComfyUI node to paste to + * @returns {boolean} - True if paste was successful, false otherwise + */ +export function safeClipspacePaste(node: any): boolean { + log.debug("Attempting safe clipspace paste"); + + if (!validateAndFixClipspace()) { + log.debug("Clipspace validation failed, cannot paste"); + return false; + } + + try { + ComfyApp.pasteFromClipspace(node); + log.debug("Successfully called pasteFromClipspace"); + return true; + } catch (error) { + log.error("Error calling pasteFromClipspace:", error); + return false; + } +}